规整规整项目

This commit is contained in:
2025-03-25 11:09:50 +08:00
parent decfe18b9a
commit 2b43d8a7b7
33 changed files with 41 additions and 324 deletions

View File

@@ -0,0 +1,175 @@
package com.ycwl.basic.service.mobile.impl;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ycwl.basic.config.WechatConfig;
import com.ycwl.basic.constant.BaseContextHandler;
import com.ycwl.basic.constant.NumberConstant;
import com.ycwl.basic.constant.WeiXinConstant;
import com.ycwl.basic.enums.AgreementEnum;
import com.ycwl.basic.enums.BizCodeEnum;
import com.ycwl.basic.enums.WechatErrorCodeEnum;
import com.ycwl.basic.exception.AppException;
import com.ycwl.basic.mapper.MemberMapper;
import com.ycwl.basic.model.jwt.JwtInfo;
import com.ycwl.basic.model.mobile.weChat.DTO.WeChatUserInfoDTO;
import com.ycwl.basic.model.mobile.weChat.DTO.WeChatUserInfoUpdateDTO;
import com.ycwl.basic.model.pc.member.entity.MemberEntity;
import com.ycwl.basic.model.pc.member.req.MemberReqQuery;
import com.ycwl.basic.model.pc.member.resp.MemberRespVO;
import com.ycwl.basic.model.pc.mp.MpConfigEntity;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.service.mobile.AppMemberService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.BeanCopierUtils;
import com.ycwl.basic.utils.JwtTokenUtil;
import com.ycwl.basic.utils.SnowFlakeUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Author:songmingsong
*/
@Slf4j
@Service
public class AppMemberServiceImpl implements AppMemberService {
@Autowired
private MemberMapper memberMapper;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private ScenicRepository scenicRepository;
@Override
public Map<String, Object> getOpenId(Long scenicId, String code) {
Map<String, Object> paramMap = new HashMap<>(NumberConstant.FOUR);
MpConfigEntity scenicMpConfig = scenicRepository.getScenicMpConfig(scenicId);
paramMap.put("appid", scenicMpConfig.getAppId());
paramMap.put("secret", scenicMpConfig.getAppSecret());
paramMap.put("js_code", code);
paramMap.put("grant_type", "authorization_code");
try {
String response = HttpUtil.post(WeiXinConstant.GET_OPEN_ID, paramMap);
if (StringUtils.isBlank(response)) {
return null;
}
return JSONObject.parseObject(response);
} catch (Exception e) {
log.error("getOpenId", e);
throw new AppException(BizCodeEnum.SERVER_INTERNAL_ERROR);
}
}
@Override
public ApiResponse login(Long scenicId, String code, WeChatUserInfoDTO userInfoDTO) throws Exception {
Map<String, Object> weixinResponse = this.getOpenId(scenicId, code);
if (CollectionUtils.isEmpty(weixinResponse)) {
throw new AppException(BizCodeEnum.SERVER_INTERNAL_ERROR);
}
Object errcode = weixinResponse.get("errcode");
if (errcode != null) {
WechatErrorCodeEnum errorCode = WechatErrorCodeEnum.getErrorCode(errcode.toString());
throw new AppException(BizCodeEnum.PARAM_ERROR.getCode(), errorCode.getDetail());
}
// 获取openId
Object openId = weixinResponse.get("openid");
if (openId == null) {
log.warn("warning={}", "未获取到当前用户openId");
throw new AppException(BizCodeEnum.SERVER_UNKONWN_ERROR, "未获取到当前用户openId");
}
MemberRespVO memberRespVO = new MemberRespVO();
JwtInfo jwtInfo = new JwtInfo();
// 根据返回的openId,判断用户是否是新用户,是的话,将用户信息存到数据库;
MemberReqQuery memberReqQuery = new MemberReqQuery();
memberReqQuery.setOpenId(openId.toString());
List<MemberRespVO> list = memberMapper.list(memberReqQuery);
if (list.isEmpty()) {
MemberEntity memberEntity = new MemberEntity();
BeanCopierUtils.copyProperties(userInfoDTO, memberEntity);
memberEntity.setId(SnowFlakeUtil.getLongId());
memberEntity.setScenicId(scenicId);
memberEntity.setOpenId(openId.toString());
memberMapper.add(memberEntity);
BeanCopierUtils.copyProperties(memberEntity, memberRespVO);
} else {
BeanCopierUtils.copyProperties(list.get(NumberConstant.ZERO), memberRespVO);
}
jwtInfo.setUserId(memberRespVO.getId());
jwtInfo.setName(memberRespVO.getNickname());
jwtInfo.setPhone(memberRespVO.getPhone());
String jwt = jwtTokenUtil.generateToken(jwtInfo);
Map<String, Object> resMap = new HashMap<>();
resMap.put("token", jwt);
resMap.put("user", memberRespVO);
return ApiResponse.success(resMap);
}
@Override
public ApiResponse<MemberRespVO> getUserInfo() {
MemberRespVO respVO = memberMapper.getById(Long.parseLong(BaseContextHandler.getUserId()));
return ApiResponse.success(respVO);
}
@Override
public ApiResponse<?> update(WeChatUserInfoUpdateDTO userInfoUpdateDTO) {
MemberEntity memberEntity = new MemberEntity();
memberEntity.setId(Long.parseLong(BaseContextHandler.getUserId()));
BeanCopierUtils.copyProperties(userInfoUpdateDTO, memberEntity);
return ApiResponse.success(memberMapper.update(memberEntity));
}
@Override
public ApiResponse<?> agreement() {
MemberEntity memberEntity = new MemberEntity();
memberEntity.setId(Long.parseLong(BaseContextHandler.getUserId()));
memberEntity.setAgreement(AgreementEnum.AGREE.getType());
return ApiResponse.success(memberMapper.update(memberEntity));
}
@Override
public ApiResponse updateScenicServiceNoticeStatus(Long scenicId) {
JwtInfo worker = JwtTokenUtil.getWorker();
Integer scenicServiceNoticeStatus = memberMapper.getScenicServiceNoticeStatus(scenicId, worker.getUserId());
//没有就初始化为开启
if(scenicServiceNoticeStatus==null){
int i = memberMapper.addScenicServiceNoticeStatus(scenicId, worker.getUserId());
if(i>0){
return ApiResponse.success("成功");
}else {
return ApiResponse.fail("失败");
}
}else {
// 有就修改
int i = memberMapper.updateScenicServiceNoticeStatus(scenicId, worker.getUserId());
if (i == 0) {
return ApiResponse.fail("失败");
}
return ApiResponse.success("成功");
}
}
@Override
public ApiResponse<Integer> getScenicServiceNoticeStatus(Long scenicId) {
Integer scenicServiceNoticeStatus = memberMapper.getScenicServiceNoticeStatus(scenicId, Long.parseLong(BaseContextHandler.getUserId()));
if(scenicServiceNoticeStatus==null){
scenicServiceNoticeStatus=0;
}
return ApiResponse.success(scenicServiceNoticeStatus);
}
}

View File

@@ -0,0 +1,251 @@
package com.ycwl.basic.service.mobile.impl;
import cn.hutool.core.bean.BeanUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.biz.OrderBiz;
import com.ycwl.basic.biz.TemplateBiz;
import com.ycwl.basic.constant.BaseContextHandler;
import com.ycwl.basic.mapper.*;
import com.ycwl.basic.model.jwt.JwtInfo;
import com.ycwl.basic.model.mobile.order.IsBuyRespVO;
import com.ycwl.basic.model.mobile.scenic.ScenicAppVO;
import com.ycwl.basic.model.mobile.scenic.ScenicDeviceCountVO;
import com.ycwl.basic.model.mobile.scenic.account.ScenicLoginReq;
import com.ycwl.basic.model.mobile.scenic.account.ScenicLoginRespVO;
import com.ycwl.basic.model.mobile.scenic.content.ContentPageVO;
import com.ycwl.basic.model.pc.device.resp.DeviceRespVO;
import com.ycwl.basic.model.pc.face.resp.FaceRespVO;
import com.ycwl.basic.model.pc.scenic.entity.ScenicAccountEntity;
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery;
import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO;
import com.ycwl.basic.model.pc.source.req.SourceReqQuery;
import com.ycwl.basic.model.pc.source.resp.SourceRespVO;
import com.ycwl.basic.model.pc.task.entity.TaskEntity;
import com.ycwl.basic.model.pc.video.entity.MemberVideoEntity;
import com.ycwl.basic.model.pc.video.entity.VideoEntity;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.repository.VideoRepository;
import com.ycwl.basic.repository.VideoTaskRepository;
import com.ycwl.basic.service.mobile.AppScenicService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.JwtTokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author:longbinbin
* @Date:2024/12/6 10:23
*/
@Slf4j
@Service
public class AppScenicServiceImpl implements AppScenicService {
@Autowired
private ScenicMapper scenicMapper;
@Autowired
private DeviceMapper deviceMapper;
@Autowired
private FaceMapper faceMapper;
@Autowired
private SourceMapper sourceMapper;
@Autowired
private VideoMapper videoMapper;
@Autowired
private TemplateMapper templateMapper;
@Autowired
private ScenicAccountMapper scenicAccountMapper;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private OrderBiz orderBiz;
@Autowired
private ScenicRepository scenicRepository;
@Autowired
private TemplateBiz templateBiz;
@Autowired
private VideoTaskRepository videoTaskRepository;
@Autowired
private VideoRepository videoRepository;
@Override
public ApiResponse<PageInfo<ScenicAppVO>> pageQuery(ScenicReqQuery scenicReqQuery) {
PageHelper.startPage(scenicReqQuery.getPageNum(), scenicReqQuery.getPageSize());
List<ScenicAppVO> list = scenicMapper.appList(scenicReqQuery);
PageInfo<ScenicAppVO> pageInfo = new PageInfo<>(list);
return ApiResponse.success(pageInfo);
}
@Override
public ApiResponse<ScenicDeviceCountVO> deviceCountByScenicId(Long scenicId) {
JwtInfo worker = JwtTokenUtil.getWorker();
ScenicDeviceCountVO scenicDeviceCountVO = deviceMapper.deviceCountByScenicId(scenicId, worker.getUserId());
return ApiResponse.success(scenicDeviceCountVO);
}
@Override
public ApiResponse<ScenicRespVO> getDetails(Long id) {
ScenicRespVO scenicRespVO = scenicMapper.getAppById(id);
ScenicDeviceCountVO scenicDeviceCountVO = deviceMapper.deviceCountByScenicId(id, -1L);
scenicRespVO.setLensNum(scenicDeviceCountVO.getTotalDeviceCount());
return ApiResponse.success(scenicRespVO);
}
@Override
public List<ContentPageVO> faceContentList(Long faceId) {
FaceRespVO faceRespVO = faceMapper.getById(faceId);
Long userId = faceRespVO.getMemberId();
if (faceRespVO == null) {
return Collections.emptyList();
}
List<ContentPageVO> contentList = templateMapper.listFor(faceRespVO.getScenicId());
contentList.forEach(contentPageVO -> {
List<MemberVideoEntity> memberVideoEntityList = videoMapper.userFaceTemplateVideo(userId, faceId, contentPageVO.getTemplateId());
contentPageVO.setGoodsType(0);
contentPageVO.setContentType(1);
if (!memberVideoEntityList.isEmpty()) {
contentPageVO.setIsBuy(memberVideoEntityList.get(0).getIsBuy());
contentPageVO.setContentId(memberVideoEntityList.get(0).getVideoId());
VideoEntity video = videoRepository.getVideo(contentPageVO.getContentId());
if (video != null) {
contentPageVO.setDuration(video.getDuration());
contentPageVO.setLockType(-1);
} else {
TaskEntity taskById = videoTaskRepository.getTaskById(memberVideoEntityList.get(0).getTaskId());
if (taskById == null) {
contentPageVO.setLockType(0);
} else if (taskById.getStatus() == 3) {
contentPageVO.setLockType(2);
} else {
contentPageVO.setLockType(0);
}
contentPageVO.setContentType(0);
}
} else {
contentPageVO.setContentType(0);
boolean canGenerate = templateBiz.determineTemplateCanGenerate(contentPageVO.getTemplateId(), faceId);
if (canGenerate) {
contentPageVO.setLockType(0);
} else {
contentPageVO.setLockType(1);
}
}
});
SourceReqQuery sourceReqQuery = new SourceReqQuery();
sourceReqQuery.setScenicId(faceRespVO.getScenicId());
sourceReqQuery.setFaceId(faceId);
sourceReqQuery.setMemberId(userId);
//查询源素材
List<SourceRespVO> sourceList = sourceMapper.queryByRelation(sourceReqQuery);
ContentPageVO sourceVideoContent = new ContentPageVO();
ContentPageVO sourceImageContent = new ContentPageVO();
sourceVideoContent.setName("录像集");
sourceImageContent.setName("照片集");
sourceVideoContent.setScenicId(faceRespVO.getScenicId());
sourceImageContent.setScenicId(faceRespVO.getScenicId());
sourceVideoContent.setGoodsType(1);
sourceImageContent.setGoodsType(2);
sourceVideoContent.setContentType(2);
sourceImageContent.setContentType(2);
sourceVideoContent.setLockType(-1);
sourceImageContent.setLockType(-1);
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(faceRespVO.getScenicId());
if (!Integer.valueOf(1).equals(scenicConfig.getDisableSourceImage())) {
IsBuyRespVO isBuyRespVO = orderBiz.isBuy(userId, faceRespVO.getScenicId(), 2, faceId);
sourceImageContent.setSourceType(isBuyRespVO.getGoodsType());
sourceImageContent.setContentId(isBuyRespVO.getGoodsId());
if (isBuyRespVO.isBuy()) {
sourceImageContent.setIsBuy(1);
} else {
sourceImageContent.setIsBuy(0);
}
contentList.add(sourceImageContent);
}
if (!Integer.valueOf(1).equals(scenicConfig.getDisableSourceVideo())) {
IsBuyRespVO isBuyRespVO = orderBiz.isBuy(userId, faceRespVO.getScenicId(), 1, faceId);
sourceVideoContent.setSourceType(isBuyRespVO.getGoodsType());
sourceVideoContent.setContentId(isBuyRespVO.getGoodsId());
if (isBuyRespVO.isBuy()) {
sourceVideoContent.setIsBuy(1);
} else {
sourceVideoContent.setIsBuy(0);
}
contentList.add(sourceVideoContent);
}
sourceList.stream().collect(Collectors.groupingBy(SourceRespVO::getType)).forEach((type, list) -> {
if (type == 1) {
sourceVideoContent.setSourceType(1);
sourceVideoContent.setLockType(-1);
sourceVideoContent.setTemplateCoverUrl(list.get(0).getUrl());
} else {
sourceImageContent.setSourceType(2);
sourceImageContent.setLockType(-1);
sourceImageContent.setTemplateCoverUrl(list.get(0).getUrl());
}
});
return contentList;
}
@Override
public ApiResponse<ScenicLoginRespVO> login(ScenicLoginReq scenicLoginReq) throws Exception {
ScenicAccountEntity scenicAccount = scenicAccountMapper.getByAccount(scenicLoginReq.getAccount());
if (scenicAccount == null) {
return ApiResponse.fail("账号不存在");
}
if (scenicAccount.getStatus() == 0) {
return ApiResponse.fail("账号已被禁用");
}
if (!scenicAccount.getPassword().equals(scenicLoginReq.getPassword())) {
return ApiResponse.fail("密码错误");
}
JwtInfo jwtInfo = new JwtInfo();
jwtInfo.setName(scenicAccount.getName());
jwtInfo.setAccount(scenicAccount.getAccount());
jwtInfo.setUserId(scenicAccount.getId());
jwtInfo.setScenicId(scenicAccount.getScenicId());
String token = jwtTokenUtil.generateToken(jwtInfo);
ScenicLoginRespVO scenicLoginRespVO = new ScenicLoginRespVO();
BeanUtil.copyProperties(scenicAccount,scenicLoginRespVO);
scenicLoginRespVO.setToken(token);
return ApiResponse.success(scenicLoginRespVO);
}
@Override
public ApiResponse<List<ContentPageVO>> contentListUseDefaultFace() {
FaceRespVO lastFaceByUserId = faceMapper.findLastFaceByUserId(BaseContextHandler.getUserId());
List<ContentPageVO> contentPageVOS = faceContentList(lastFaceByUserId.getId());
return ApiResponse.success(contentPageVOS);
}
@Override
public ApiResponse<ScenicRespVO> getMyScenic() {
String userId = BaseContextHandler.getUserId();
ScenicAccountEntity account = scenicAccountMapper.findAccountById(userId);
if (account == null) {
return ApiResponse.fail("用户未绑定景区");
}
return getDetails(account.getScenicId());
}
@Override
public ApiResponse<List<DeviceRespVO>> getMyDevices() {
String userId = BaseContextHandler.getUserId();
ScenicAccountEntity account = scenicAccountMapper.findAccountById(userId);
if (account == null) {
return ApiResponse.fail("用户未绑定景区");
}
List<DeviceRespVO> deviceRespVOList = deviceMapper.listByScenicIdWithWVP(account.getScenicId());
return ApiResponse.success(deviceRespVOList);
}
}

View File

@@ -0,0 +1,471 @@
package com.ycwl.basic.service.mobile.impl;
import com.ycwl.basic.enums.StatisticEnum;
import com.ycwl.basic.mapper.StatisticsMapper;
import com.ycwl.basic.model.jwt.JwtInfo;
import com.ycwl.basic.model.mobile.statistic.req.CommonQueryReq;
import com.ycwl.basic.model.mobile.statistic.req.StatisticsRecordAddReq;
import com.ycwl.basic.model.mobile.statistic.resp.AppSta1VO;
import com.ycwl.basic.model.mobile.statistic.resp.AppSta2VO;
import com.ycwl.basic.model.mobile.statistic.resp.AppSta3VO;
import com.ycwl.basic.model.mobile.statistic.resp.AppStatisticsFunnelVO;
import com.ycwl.basic.service.mobile.AppStatisticsService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.DateUtils;
import com.ycwl.basic.utils.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.Date;
import java.util.Map;
/**
* @Author:longbinbin
* @Date:2024/12/12 9:48
*/
@Service
public class AppStatisticsServiceImpl implements AppStatisticsService {
@Autowired
private StatisticsMapper statisticsMapper;
/**
* 支付订单金额、预览_支付转化率、扫码_付费用户转化率
* @param query
* @return
*/
@Override
public ApiResponse<AppSta1VO> oneStatistics(CommonQueryReq query) {
AppSta1VO vo = new AppSta1VO();
//订单金额格式
DecimalFormat orderAmountDf = new DecimalFormat("0.0");
//转化率格式
DecimalFormat df = new DecimalFormat("0.00");
if(query.getEndTime()==null && query.getStartTime()==null){
// 没有传时间,则代表用户没有自定义查询时间,使用standard来判断查询时间范围
Integer standard = query.getStandard();
if(standard==null){
query.setStandard(0);
}
//获取当前周期的具体时间范围
standardToNewSpecificTime(query);
//查询处理数据逻辑
oneStatisticsHandler(1,query,vo);
//----------------------------------------------------
//获取上一个周期的具体时间范围
standardToPreviousSpecificTime(query);
//查询处理数据逻辑
oneStatisticsHandler(2,query,vo);
}else{
//自定义时间查询,只有当前数据,没有往期对比数据
//查询处理数据逻辑
oneStatisticsHandler(1,query,vo);
}
return ApiResponse.success(vo);
}
@Override
public ApiResponse<AppSta2VO> twoStatistics(CommonQueryReq query) {
AppSta2VO vo = new AppSta2VO();
if(query.getEndTime()==null && query.getStartTime()==null){
// 没有传时间,则代表用户没有自定义查询时间,使用standard来判断查询时间范围
Integer standard = query.getStandard();
if(standard==null){
query.setStandard(0);
}
//获取当前周期的具体时间范围
standardToNewSpecificTime(query);
//查询处理数据逻辑
twoStatisticsHandler(1,query,vo);
//----------------------------------------------------
//获取当前周期的具体时间范围
standardToPreviousSpecificTime(query);
//查询处理数据逻辑
twoStatisticsHandler(2,query,vo);
}else{
//自定义时间查询,只有当前数据,没有往期对比数据
//查询处理数据逻辑
twoStatisticsHandler(1,query,vo);
}
return ApiResponse.success(vo);
}
@Override
public ApiResponse<AppSta3VO> freeStatistics(CommonQueryReq query) {
AppSta3VO vo = new AppSta3VO();
if(query.getEndTime()==null && query.getStartTime()==null){
// 没有传时间,则代表用户没有自定义查询时间,使用standard来判断查询时间范围
Integer standard = query.getStandard();
if(standard==null){
query.setStandard(0);
}
//获取当前周期的具体时间范围
standardToNewSpecificTime(query);
//查询处理数据逻辑
freeStatisticsHandler(1,query,vo);
//----------------------------------------------------
//获取当前周期的具体时间范围
standardToPreviousSpecificTime(query);
//查询处理数据逻辑
freeStatisticsHandler(2,query,vo);
}else{
//自定义时间查询,只有当前数据,没有往期对比数据
//查询处理数据逻辑
freeStatisticsHandler(1,query,vo);
}
return ApiResponse.success(vo);
}
@Override
public ApiResponse<AppStatisticsFunnelVO> userConversionFunnel(CommonQueryReq query) {
AppStatisticsFunnelVO vo = new AppStatisticsFunnelVO();
if(query.getEndTime()==null && query.getStartTime()==null){
// 没有传时间,则代表用户没有自定义查询时间,使用standard来判断查询时间范围
Integer standard = query.getStandard();
if(standard==null){
query.setStandard(0);
}
//获取当前周期的具体时间范围
standardToNewSpecificTime(query);
}
//镜头检测游客数
Integer cameraShotOfMemberNum=statisticsMapper.countCameraShotOfMember(query);
//扫码访问人数
// 扫小程序码或景区码进入访问的用户数,包括授权用户(使用OpenID进行精准统计)和未授权用户(使用 UUID统计访问)。但当用户授权时,获取OpenID并与UUID关联,删除本地UUID,避免重复记录。
Integer scanCodeVisitorOfMemberNum=statisticsMapper.countScanCodeOfMember(query);
//镜头检测游客数_扫码访问人数_转化率
// vo.setCsom_scaom(calculateConversionRate(scanCodeVisitorOfMemberNum,cameraShotOfMemberNum));
vo.setCsom_scaom("-");
//上传头像(人脸)人数
// 上传了人脸的用户数(包括本地临时ID和获取到OpenID的,同一设备微信获取到OpenID要覆盖掉之前生成的临时ID),上传多张人脸都只算一个人。
Integer uploadFaceOfMemberNum=statisticsMapper.countUploadFaceOfMember(query);
//扫码访问人数_上传头像人数_转化率
vo.setScaom_ufom(calculateConversionRate(uploadFaceOfMemberNum,scanCodeVisitorOfMemberNum));
//推送订阅人数
// 只要点了允许通知,哪怕只勾选1条订阅都算
Integer pushOfMemberNum =statisticsMapper.countPushOfMember(query);
//上传头像人数_推送订阅人数_转化率
vo.setUfom_pom((calculateConversionRate(pushOfMemberNum,uploadFaceOfMemberNum)));
//生成视频人数
// 生成过Vlog视频的用户ID数,要注意屏蔽掉以前没有片段也能生成的情况
Integer completeVideoOfMemberNum =statisticsMapper.countCompleteVideoOfMember(query);
//推送订阅人数_生成视频人数_转化率
vo.setPom_cvom((calculateConversionRate(completeVideoOfMemberNum,pushOfMemberNum)));
//预览视频人数
// 购买前播放了5秒的视频条数。
Integer previewVideoOfMemberNum =statisticsMapper.countPreviewVideoOfMember(query);
if (previewVideoOfMemberNum==null){
previewVideoOfMemberNum=0;
}
//生成视频人数_预览视频人数_转化率
vo.setCvom_pvom((calculateConversionRate(previewVideoOfMemberNum,completeVideoOfMemberNum)));
//点击购买人数
// 点了立即购买按钮的用户ID就算,包括支付的和未支付的都算,只要点击了。
Integer clickOnPayOfMemberNum =statisticsMapper.countClickPayOfMember(query);
//预览视频人数_点击购买人数_转化率
vo.setPvom_cpom((calculateConversionRate(clickOnPayOfMemberNum,previewVideoOfMemberNum)));
//支付订单人数
Integer payOfMemberNum =statisticsMapper.countPayOfMember(query);
//点击购买人数_支付订单人数_转化率
vo.setCpom_pom((calculateConversionRate(payOfMemberNum,clickOnPayOfMemberNum)));
//总访问人数
// 通过任何途径访问到小程序的总人数,包括授权用户和未授权用户。
Integer totalVisitorOfMemberNum =statisticsMapper.countTotalVisitorOfMember(query);
// Integer totalVisitorOfMemberNum =scanCodeVisitorOfMemberNum;
//生成视频条数
// 仅指代生成的Vlog条数,不包含录像原片。
Integer completeOfVideoNum =statisticsMapper.countCompleteOfVideo(query);
//预览视频条数
Integer previewOfVideoNum =statisticsMapper.countPreviewOfVideo(query);
//支付订单数
Integer payOfOrderNum =statisticsMapper.countPayOfOrder(query);
//支付订单金额
BigDecimal payOfOrderAmount =statisticsMapper.countOrderAmount(query);
//退款订单数
Integer refundOfOrderNum =statisticsMapper.countRefundOfOrder(query);
//退款订单金额
BigDecimal refundOfOrderAmount =statisticsMapper.countRefundAmount(query);
// vo.setCameraShotOfMemberNum(cameraShotOfMemberNum);
vo.setCameraShotOfMemberNum("-");
vo.setScanCodeVisitorOfMemberNum(scanCodeVisitorOfMemberNum);
vo.setUploadFaceOfMemberNum(uploadFaceOfMemberNum);
vo.setPushOfMemberNum(pushOfMemberNum);
vo.setCompleteVideoOfMemberNum(completeVideoOfMemberNum);
vo.setPreviewVideoOfMemberNum(previewVideoOfMemberNum);
vo.setClickOnPayOfMemberNum(clickOnPayOfMemberNum);
vo.setPayOfMemberNum(payOfMemberNum);
//转化率格式
DecimalFormat df = new DecimalFormat("0.0");
vo.setTotalVisitorOfMemberNum(totalVisitorOfMemberNum);
vo.setCompleteOfVideoNum(completeOfVideoNum);
vo.setPreviewOfVideoNum(previewOfVideoNum);
vo.setPayOfOrderNum(payOfOrderNum);
vo.setPayOfOrderAmount(df.format(payOfOrderAmount.setScale(2, RoundingMode.HALF_UP)));
vo.setRefundOfOrderNum(refundOfOrderNum);
vo.setRefundOfOrderAmount(df.format(refundOfOrderAmount.setScale(2, RoundingMode.HALF_UP)));
return ApiResponse.success(vo);
}
@Override
public ApiResponse addStatistics(StatisticsRecordAddReq req) {
// req.setId(SnowFlakeUtil.getLongId());
if (req.getMemberId() == null) {
try {
JwtInfo worker = JwtTokenUtil.getWorker();
Long userId = worker.getUserId();
req.setMemberId(userId);
} catch (Exception ignored) {
}
}
Integer type = req.getType();
if(type==null){
return ApiResponse.fail("类型不能为空");
}
Map<Integer, StatisticEnum> valueMap = StatisticEnum.cacheMap;
if(!valueMap.containsKey(type)){
return ApiResponse.fail("添加失败,类型不存在");
}
int i=statisticsMapper.addStatisticsRecord(req);
if(i==0){
return ApiResponse.fail("添加失败");
}else{
return ApiResponse.success("添加成功");
}
}
/**
*
* @param num1
* @param num2
* @return
*/
private String calculateConversionRate(Integer num1,Integer num2){
//转化率格式
DecimalFormat df = new DecimalFormat("0.00");
if(num2 == null || num2==0){
return "0.00";
}else {
BigDecimal result = new BigDecimal(num1).divide(new BigDecimal(num2), 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
return df.format(result);
}
}
/**
*
* @param cycle 周期 1当前 2往期
* @param query
* @param vo
*/
private void freeStatisticsHandler(Integer cycle,CommonQueryReq query,AppSta3VO vo){
//查询扫码访问人数
int sceneNum=statisticsMapper.countScanCodeOfMember(query);
//查询推送订阅人数
int pushNum=statisticsMapper.countPushOfMember(query);
// 查询预览视频人数
Integer previewNum=statisticsMapper.countPreviewVideoOfMember(query);
if(cycle==1){
//当前周期的扫码访问人数
vo.setNowScanCodeOfPeopleNum(sceneNum);
//当前周期的推送订阅人数
vo.setNowPushOfPeopleNum(pushNum);
//当前周期的预览视频人数
vo.setNowPreviewVideoOfPeopleNum(previewNum);
}else if(cycle==2){
//上一个周期的扫码访问人数
vo.setPreviousScanCodeOfPeopleNum(sceneNum);
//上一个周期的推送订阅人数
vo.setPreviousPushOfPeopleNum(pushNum);
//上一个周期的预览视频人数
vo.setPreviousPreviewVideoOfPeopleNum(previewNum);
}
}
/**
*
* @param cycle 周期 1当前 2往期
* @param query
* @param vo
*/
private void twoStatisticsHandler(Integer cycle,CommonQueryReq query,AppSta2VO vo){
//查询现场支付订单数
int sceneOrderNum=statisticsMapper.countSceneOrderNum(query);
//查询推送支付订单数
int pushOrderNum=statisticsMapper.countPushOrderNum(query);
// 当前周期的总支付订单数
Integer totalOrderNum=sceneOrderNum+pushOrderNum;
if(cycle==1){
//当前周期的总支付订单数
vo.setNowPayOrderNum(totalOrderNum);
//当前周期的现场支付订单数
vo.setNowSceneOrderNum(sceneOrderNum);
//当前周期的推送支付订单数
vo.setNowPushOrderNum(pushOrderNum);
}else if(cycle==2){
//上一个周期的总支付订单数
vo.setPreviousPayOrderNum(totalOrderNum);
//上一个周期的现场支付订单数
vo.setPreviousSceneOrderNum(sceneOrderNum);
//上一个周期的推送支付订单数
vo.setPreviousPushOrderNum(pushOrderNum);
}
}
/**
*
* @param cycle 周期 1当前 2往期
* @param query
* @param vo
*/
private void oneStatisticsHandler(Integer cycle,CommonQueryReq query,AppSta1VO vo){
//订单金额格式
DecimalFormat orderAmountDf = new DecimalFormat("0.0");
//转化率格式
DecimalFormat df = new DecimalFormat("0.00");
// 计算当前周期的支付订单金额
BigDecimal orderAmount=statisticsMapper.countOrderAmount(query).setScale(2, RoundingMode.HALF_UP);
//查询预览视频人数
int preview=statisticsMapper.countPreviewVideoOfMember(query);
//查询扫码人数
int scanCode=statisticsMapper.countScanCodeOfMember(query);
//查询付费人数
int pay=statisticsMapper.countPayOfMember(query);
int payCount=statisticsMapper.countPayOfOrder(query);
if(cycle==1){
// 支付过订单的金额,包含已退款的金额。
vo.setNowOrderAmount(orderAmountDf.format(orderAmount));
// 订单数÷预览人数。假设一共5个人预览,产生了3个订单,其实是2个人支付的(其中1人购买2单),预览-支付转化率是3÷5,而不是2÷5。
if(preview==0){
vo.setNowPreviewPay("0.00");
}else {
BigDecimal previewPay = new BigDecimal(payCount).divide(new BigDecimal(preview), 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
vo.setNowPreviewPay(df.format(previewPay));
}
// 付费的用户人数÷扫码的用户人数。此处是人数除以人数,而且是除以扫码人数,而不是预览人数。
if(scanCode==0){
vo.setNowScanCodePay("0.00");
}else {
BigDecimal scanCodePay = new BigDecimal(pay).divide(new BigDecimal(scanCode), 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
vo.setNowScanCodePay(df.format(scanCodePay));
}
}else if(cycle==2){
//上一个周期的支付订单金额
// 支付过订单的金额,包含已退款的金额。
vo.setPreviousOrderAmount(orderAmountDf.format(orderAmount));
// 订单数÷预览人数。假设一共5个人预览,产生了3个订单,其实是2个人支付的(其中1人购买2单),预览-支付转化率是3÷5,而不是2÷5。
if(preview==0){
vo.setPreviousPreviewPay("0.00");
}else {
BigDecimal previewPay = new BigDecimal(payCount).divide(new BigDecimal(preview), 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
vo.setPreviousPreviewPay(df.format(previewPay));
}
// 付费的用户人数÷扫码的用户人数。此处是人数除以人数,而且是除以扫码人数,而不是预览人数。
if(scanCode==0){
vo.setPreviousScanCodePay("0.00");
}else {
BigDecimal scanCodePay = new BigDecimal(pay).divide(new BigDecimal(scanCode), 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
vo.setPreviousScanCodePay(df.format(scanCodePay));
}
}
}
/**
* 根据standard来获取查询时间范围(当前周期)
* @param query
*/
private void standardToNewSpecificTime(CommonQueryReq query) {
Integer standard = query.getStandard();
Date newDate = new Date();
Date startDate = new Date();
Date endDate = new Date();
switch (standard) {
case 4://年
startDate = DateUtils.addDateYears(newDate, -1);
break;
case 3://月
startDate = DateUtils.addDateMonths(newDate, -1);
break;
case 2://周
startDate = DateUtils.addDateWeeks(newDate, -1);
break;
case 1://昨天
Date yesterday = DateUtils.addDateDays(newDate, -1);
startDate = DateUtils.getStartOfDay(yesterday);
endDate = DateUtils.getEndOfDay(yesterday);
break;
case 0://今天
startDate = DateUtils.getStartOfDay(newDate);
break;
case 9://历史累计
startDate = null;
endDate = null;
break;
default:
break;
}
query.setStartTime(startDate);
query.setEndTime(endDate);
}
/**
* 根据standard来获取查询时间范围(上一个周期)
* @param query
*/
private void standardToPreviousSpecificTime(CommonQueryReq query) {
Integer standard = query.getStandard();
Date newDate = new Date();
Date startDate = new Date();
Date endDate = new Date();
switch (standard) {
case 4://年,近一年的前一年
startDate = DateUtils.addDateYears(newDate, -2);
break;
case 3://月,近一月的前一个月
startDate = DateUtils.addDateMonths(newDate, -2);
endDate = DateUtils.addDateMonths(newDate, -1);
break;
case 2://周,近一周的前一周
startDate = DateUtils.addDateWeeks(newDate, -2);
endDate = DateUtils.addDateWeeks(newDate, -1);
break;
case 1://昨天,昨天的前一天
Date theDayBeforeYesterday = DateUtils.addDateDays(newDate, -2);
startDate = DateUtils.getStartOfDay(theDayBeforeYesterday);
endDate = DateUtils.getEndOfDay(theDayBeforeYesterday);
break;
case 0://今天,今天的前一天
Date yesterday = DateUtils.addDateDays(newDate, -1);
startDate = DateUtils.getStartOfDay(yesterday);
endDate = DateUtils.getEndOfDay(yesterday);
break;
case 9://历史累计
startDate = null;
endDate = null;
break;
default:
break;
}
query.setStartTime(startDate);
query.setEndTime(endDate);
}
}

View File

@@ -0,0 +1,652 @@
package com.ycwl.basic.service.mobile.impl;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ycwl.basic.biz.OrderBiz;
import com.ycwl.basic.biz.TaskStatusBiz;
import com.ycwl.basic.constant.BaseContextHandler;
import com.ycwl.basic.constant.StorageConstant;
import com.ycwl.basic.image.watermark.ImageWatermarkFactory;
import com.ycwl.basic.image.watermark.entity.WatermarkInfo;
import com.ycwl.basic.image.watermark.enums.ImageWatermarkOperatorEnum;
import com.ycwl.basic.image.watermark.exception.ImageWatermarkException;
import com.ycwl.basic.image.watermark.operator.IOperator;
import com.ycwl.basic.mapper.*;
import com.ycwl.basic.model.mobile.goods.*;
import com.ycwl.basic.model.mobile.order.IsBuyRespVO;
import com.ycwl.basic.model.mobile.order.PriceObj;
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
import com.ycwl.basic.model.pc.face.resp.FaceRespVO;
import com.ycwl.basic.model.pc.mp.MpConfigEntity;
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
import com.ycwl.basic.model.pc.source.entity.SourceWatermarkEntity;
import com.ycwl.basic.model.pc.source.req.SourceReqQuery;
import com.ycwl.basic.model.pc.source.resp.SourceRespVO;
import com.ycwl.basic.model.pc.task.entity.TaskEntity;
import com.ycwl.basic.model.pc.template.resp.TemplateRespVO;
import com.ycwl.basic.model.pc.video.entity.MemberVideoEntity;
import com.ycwl.basic.model.pc.video.req.VideoReqQuery;
import com.ycwl.basic.model.pc.video.resp.VideoRespVO;
import com.ycwl.basic.repository.FaceRepository;
import com.ycwl.basic.repository.OrderRepository;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.repository.VideoTaskRepository;
import com.ycwl.basic.service.mobile.GoodsService;
import com.ycwl.basic.repository.TemplateRepository;
import com.ycwl.basic.service.task.TaskService;
import com.ycwl.basic.storage.StorageFactory;
import com.ycwl.basic.storage.adapters.IStorageAdapter;
import com.ycwl.basic.storage.enums.StorageAcl;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.DateUtils;
import com.ycwl.basic.utils.WxMpUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* @Author:longbinbin
* @Date:2024/12/5 15:04
*/
@Slf4j
@Service
public class GoodsServiceImpl implements GoodsService {
@Autowired
private VideoMapper videoMapper;
@Autowired
private SourceMapper sourceMapper;
@Autowired
private ScenicMapper scenicMapper;
@Autowired
private FaceMapper faceMapper;
@Autowired
private TemplateRepository templateRepository;
@Autowired
private VideoTaskRepository videoTaskRepository;
@Autowired
private TaskService taskTaskService;
@Autowired
private ScenicRepository scenicRepository;
@Autowired
private OrderRepository orderRepository;
@Autowired
private OrderBiz orderBiz;
@Autowired
private FaceRepository faceRepository;
@Autowired
private TaskStatusBiz taskStatusBiz;
public ApiResponse<List<GoodsPageVO>> goodsList(GoodsReqQuery query) {
//查询原素材
List<GoodsPageVO> goodsList = new ArrayList<>();
VideoReqQuery videoReqQuery = new VideoReqQuery();
videoReqQuery.setScenicId(query.getScenicId());
videoReqQuery.setIsBuy(query.getIsBuy());
videoReqQuery.setFaceId(query.getFaceId());
videoReqQuery.setMemberId(Long.valueOf(BaseContextHandler.getUserId()));
//查询成片vlog
List<VideoRespVO> videoList = videoMapper.queryByRelation(videoReqQuery);
videoList.forEach(videoRespVO -> {
GoodsPageVO goodsPageVO = new GoodsPageVO();
goodsPageVO.setGoodsName(videoRespVO.getTemplateName());
goodsPageVO.setScenicId(videoRespVO.getScenicId());
goodsPageVO.setScenicName(videoRespVO.getScenicName());
goodsPageVO.setGoodsType(0);
goodsPageVO.setFaceId(videoRespVO.getFaceId());
goodsPageVO.setGoodsId(videoRespVO.getId());
goodsPageVO.setTemplateName(videoRespVO.getTemplateName());
goodsPageVO.setTemplateCoverUrl(videoRespVO.getTemplateCoverUrl());
goodsList.add(goodsPageVO);
});
SourceReqQuery sourceReqQuery = new SourceReqQuery();
sourceReqQuery.setScenicId(query.getScenicId());
sourceReqQuery.setIsBuy(query.getIsBuy());
sourceReqQuery.setFaceId(query.getFaceId());
sourceReqQuery.setMemberId(Long.valueOf(BaseContextHandler.getUserId()));
//查询源素材
List<SourceRespVO> sourceList = sourceMapper.queryByRelation(sourceReqQuery);
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(query.getScenicId());
List<GoodsPageVO> sourceGoods = sourceList.stream().collect(Collectors.groupingBy(SourceRespVO::getFaceId)).entrySet().stream().flatMap((faceEntry) -> {
Long faceId = faceEntry.getKey();
List<SourceRespVO> goods = faceEntry.getValue();
return goods.stream().collect(Collectors.groupingBy(SourceRespVO::getType)).keySet().stream().filter(type -> {
if (Integer.valueOf(1).equals(type)) {
if (Integer.valueOf(1).equals(scenicConfig.getDisableSourceVideo())) {
return false;
}
} else if (Integer.valueOf(2).equals(type)) {
if (Integer.valueOf(1).equals(scenicConfig.getDisableSourceImage())) {
return false;
}
}
return true;
}).map(type -> {
GoodsPageVO goodsPageVO = new GoodsPageVO();
goodsPageVO.setTemplateCoverUrl(goods.get(0).getUrl());
goodsPageVO.setFaceId(faceId);
if (type == 1) {
goodsPageVO.setGoodsName("录像集");
goodsPageVO.setGoodsType(1);
} else {
goodsPageVO.setGoodsName("照片集");
goodsPageVO.setGoodsType(2);
}
goodsPageVO.setScenicId(query.getScenicId());
return goodsPageVO;
});
}).collect(Collectors.toList());
if (!sourceGoods.isEmpty()) {
if (goodsList.size() > 2) {
goodsList.addAll(2, sourceGoods);
} else {
goodsList.addAll(sourceGoods);
}
}
return ApiResponse.success(goodsList);
}
@Override
public List<GoodsDetailVO> sourceGoodsList(GoodsReqQuery query) {
FaceEntity face = faceRepository.getFace(query.getFaceId());
if (face == null) {
return Collections.emptyList();
}
Integer sourceType = query.getSourceType();
SourceReqQuery sourceReqQuery = new SourceReqQuery();
sourceReqQuery.setScenicId(query.getScenicId());
sourceReqQuery.setIsBuy(query.getIsBuy());
sourceReqQuery.setMemberId(face.getMemberId());
sourceReqQuery.setType(sourceType);
sourceReqQuery.setFaceId(query.getFaceId());
List<SourceRespVO> list = sourceMapper.listUser(sourceReqQuery);
List<GoodsDetailVO> goodsDetailVOList = new ArrayList<>();
String goodsNamePrefix = "";
if (sourceType == 1) {
goodsNamePrefix = "录像";
} else if (sourceType == 2) {
goodsNamePrefix = "图片";
} else {
goodsNamePrefix = "其他类型";
}
//图片编号
int i=1;
for (SourceRespVO sourceRespVO : list) {
GoodsDetailVO goodsDetailVO = new GoodsDetailVO();
goodsDetailVO.setGoodsId(sourceRespVO.getId());
String shootingTime = DateUtils.format(sourceRespVO.getCreateTime(), "yyyy.MM.dd HH:mm:ss");
if (i < 10) {
goodsDetailVO.setGoodsName(goodsNamePrefix + "0" + i + " " + shootingTime);
} else {
goodsDetailVO.setGoodsName(goodsNamePrefix + i + " " + shootingTime);
}
goodsDetailVO.setScenicId(sourceRespVO.getScenicId());
goodsDetailVO.setScenicName(sourceRespVO.getScenicName());
goodsDetailVO.setLongitude(sourceRespVO.getLongitude());
goodsDetailVO.setLatitude(sourceRespVO.getLatitude());
goodsDetailVO.setGoodsType(sourceType);
goodsDetailVO.setSourceType(sourceType);
goodsDetailVO.setGoodsId(sourceRespVO.getId());
goodsDetailVO.setVideoUrl(sourceRespVO.getVideoUrl());
goodsDetailVO.setUrl(sourceRespVO.getUrl());
goodsDetailVO.setCreateTime(sourceRespVO.getCreateTime());
goodsDetailVO.setIsFree(sourceRespVO.getIsFree());
goodsDetailVOList.add(goodsDetailVO);
i++;
}
return goodsDetailVOList;
}
@Override
public ApiResponse<VideoGoodsDetailVO> videoGoodsDetail(Long userId, Long videoId) {
VideoGoodsDetailVO goodsDetailVO = new VideoGoodsDetailVO();
VideoRespVO videoRespVO = videoMapper.getById(videoId);
if (videoRespVO == null) {
return ApiResponse.fail("该vlog不存在或已失效");
}
goodsDetailVO.setGoodsName(videoRespVO.getTemplateName());
goodsDetailVO.setScenicId(videoRespVO.getScenicId());
goodsDetailVO.setScenicName(videoRespVO.getScenicName());
goodsDetailVO.setGoodsType(0);
goodsDetailVO.setGoodsId(videoRespVO.getId());
goodsDetailVO.setVideoUrl(videoRespVO.getVideoUrl());
goodsDetailVO.setTemplateCoverUrl(videoRespVO.getTemplateCoverUrl());
goodsDetailVO.setCreateTime(videoRespVO.getCreateTime());
goodsDetailVO.setHeight(videoRespVO.getHeight());
goodsDetailVO.setWidth(videoRespVO.getWidth());
goodsDetailVO.setDuration(videoRespVO.getDuration());
if (userId == null) {
goodsDetailVO.setIsBuy(0);
goodsDetailVO.setShare(true);
goodsDetailVO.setPrice("未登录");
} else {
MemberVideoEntity entity = videoMapper.queryUserVideo(userId, videoId);
if (entity == null) {
goodsDetailVO.setIsBuy(0);
goodsDetailVO.setShare(true);
goodsDetailVO.setPrice("未登录");
} else {
goodsDetailVO.setShare(false);
goodsDetailVO.setFaceId(entity.getFaceId());
goodsDetailVO.setIsBuy(entity.getIsBuy());
if (Integer.valueOf(0).equals(entity.getIsBuy())) {
PriceObj priceObj = orderBiz.queryPrice(videoRespVO.getScenicId(), 0, videoId);
if (priceObj.isFree()) {
goodsDetailVO.setIsBuy(1);
} else {
goodsDetailVO.setIsBuy(0);
goodsDetailVO.setPrice(priceObj.getPrice().toString());
goodsDetailVO.setSlashPrice(priceObj.getSlashPrice().toString());
}
}
}
}
TaskEntity task = videoTaskRepository.getTaskById(videoRespVO.getTaskId());
JSONObject paramJson = JSON.parseObject(task.getTaskParams());
long deviceCount;
goodsDetailVO.setShotTime(taskTaskService.getTaskShotDate(task.getId()));
if (paramJson == null) {
deviceCount = 1;
} else {
List<String> templatePlaceholder = templateRepository.getTemplatePlaceholder(task.getTemplateId());
deviceCount = paramJson.keySet().stream()
.filter(StringUtils::isNumeric)
.filter(templatePlaceholder::contains)
.count();
}
goodsDetailVO.setLensNum((int) deviceCount);
return ApiResponse.success(goodsDetailVO);
}
/**
* 查询用户当前景区的视频合成任务状态
*
* @param faceId 景区id
* @return 0没有任务 1 合成中 2 合成成功
*/
@Override
public VideoTaskStatusVO getTaskStatusByFaceId(Long faceId) {
FaceEntity face = faceRepository.getFace(faceId);
Long userId = face.getMemberId();
VideoTaskStatusVO response = new VideoTaskStatusVO();
response.setFaceId(faceId);
if (face == null) {
response.setStatus(0);
return response;
}
response.setScenicId(face.getScenicId());
int faceCutStatus = taskStatusBiz.getFaceCutStatus(faceId);
response.setCutStatus(faceCutStatus);
if (faceCutStatus == 0) {
// 切视频中,也显示正在处理
response.setStatus(2);
return response;
}
List<MemberVideoEntity> taskList = videoMapper.listRelationByFace(userId, faceId);
if (faceCutStatus != 1 && taskList.isEmpty()) {
// 视频切成了能够获取视频的状态,但是没有任务,还是显示正在处理
response.setStatus(0);
return response;
}
if (taskList.isEmpty()) {
response.setStatus(0);
return response;
}
List<TemplateRespVO> templateList = templateRepository.getTemplateListByScenicId(response.getScenicId());
List<Long> templateIds = templateList.stream().map(TemplateRespVO::getId).collect(Collectors.toList());
response.setMaxCount(templateList.size());
List<MemberVideoEntity> notFinishedTasks = taskList.stream()
.filter(task -> templateIds.contains(task.getTemplateId()))
.filter(task -> {
TaskEntity taskById = videoTaskRepository.getTaskById(task.getTaskId());
if (taskById == null) {
return true;
}
return taskById.getStatus() == 0 || taskById.getStatus() == 2;
})
.collect(Collectors.toList());
long finishedTask = taskList.stream()
.filter(task -> {
TaskEntity taskById = videoTaskRepository.getTaskById(task.getTaskId());
if (taskById == null) {
return false;
}
return taskById.getStatus() == 1;
}).count();
response.setCount(finishedTask);
if (!notFinishedTasks.isEmpty()) {
response.setTemplateId(notFinishedTasks.get(0).getTemplateId());
response.setTaskId(notFinishedTasks.get(0).getTaskId());
response.setStatus(2);
return response;
}
MemberVideoEntity lastVideo = taskList.get(taskList.size() - 1);
response.setTaskId(lastVideo.getTaskId());
response.setTemplateId(lastVideo.getTemplateId());
response.setVideoId(lastVideo.getVideoId());
response.setStatus(1);
return response;
}
@Override
public VideoTaskStatusVO getAllTaskStatus(Long userId) {
FaceRespVO lastFaceByUserId = faceMapper.findLastFaceByUserId(String.valueOf(userId));
return getTaskStatusByFaceId(lastFaceByUserId.getId());
}
@Override
public VideoTaskStatusVO getTaskStatusByTemplateId(Long faceId, Long templateId) {
List<MemberVideoEntity> taskList = videoMapper.listRelationByFaceAndTemplate(faceId, templateId);
VideoTaskStatusVO response = new VideoTaskStatusVO();
response.setFaceId(faceId);
response.setTemplateId(templateId);
if (taskList.isEmpty()) {
response.setStatus(0);
return response;
}
response.setScenicId(taskList.get(0).getScenicId());
response.setMaxCount(templateRepository.getTemplateListByScenicId(response.getScenicId()).size());
List<MemberVideoEntity> notFinishedTasks = taskList.stream()
.filter(task -> {
TaskEntity taskById = videoTaskRepository.getTaskById(task.getTaskId());
if (taskById == null) {
return true;
}
return taskById.getStatus() == 0 || taskById.getStatus() == 2;
}).collect(Collectors.toList());
long finishedTask = taskList.stream()
.filter(task -> {
TaskEntity taskById = videoTaskRepository.getTaskById(task.getTaskId());
if (taskById == null) {
return false;
}
return taskById.getStatus() == 1;
}).count();
response.setCount(finishedTask);
int faceCutStatus = taskStatusBiz.getFaceCutStatus(faceId);
if (Integer.valueOf(0).equals(faceCutStatus)) {
response.setTemplateId(notFinishedTasks.get(0).getTemplateId());
response.setStatus(2);
return response;
}
if (!notFinishedTasks.isEmpty()) {
response.setTemplateId(notFinishedTasks.get(0).getTemplateId());
response.setTaskId(notFinishedTasks.get(0).getTaskId());
response.setStatus(2);
return response;
}
MemberVideoEntity lastVideo = taskList.get(taskList.size() - 1);
response.setTaskId(lastVideo.getTaskId());
response.setTemplateId(lastVideo.getTemplateId());
response.setVideoId(lastVideo.getVideoId());
if (null != lastVideo.getVideoId()) {
response.setStatus(1);
response.setVideoId(lastVideo.getVideoId());
} else {
TaskEntity taskById = videoTaskRepository.getTaskById(lastVideo.getTaskId());
if (taskById == null) {
response.setStatus(1);
} else {
if (taskById.getStatus() == 1) {
response.setStatus(1);
response.setVideoId(lastVideo.getVideoId());
} else if (taskById.getStatus() == 0 || taskById.getStatus() == 2) {
response.setStatus(2);
} else {
response.setStatus(1);
}
}
}
return response;
}
@Override
public VideoTaskStatusVO getTaskStatusByScenicId(Long userId, Long scenicId) {
FaceRespVO faceVO = faceMapper.getLatestByMemberId(userId, scenicId);
VideoTaskStatusVO response = new VideoTaskStatusVO();
response.setScenicId(scenicId);
if (faceVO == null) {
// 从来没露脸
response.setStatus(-2);
return response;
}
return getTaskStatusByFaceId(faceVO.getId());
}
@Override
public ApiResponse<GoodsDetailVO> sourceGoodsInfo(Long sourceId) {
SourceRespVO sourceRespVO = sourceMapper.getById(sourceId);
if (sourceRespVO == null) {
sourceRespVO = sourceMapper.getById(sourceId);
if (sourceRespVO == null) {
return ApiResponse.fail("该视频不存在");
}
}
GoodsDetailVO goodsDetailVO = new GoodsDetailVO();
goodsDetailVO.setGoodsName("原片");
goodsDetailVO.setScenicId(sourceRespVO.getScenicId());
goodsDetailVO.setScenicName(sourceRespVO.getScenicName());
goodsDetailVO.setGoodsType(sourceRespVO.getType());
goodsDetailVO.setGoodsId(sourceRespVO.getId());
goodsDetailVO.setVideoUrl(sourceRespVO.getVideoUrl());
goodsDetailVO.setTemplateCoverUrl(sourceRespVO.getUrl());
goodsDetailVO.setCreateTime(sourceRespVO.getCreateTime());
return ApiResponse.success(goodsDetailVO);
}
@Override
public List<GoodsUrlVO> sourceGoodsListPreview(GoodsReqQuery query) {
List<File> tmpFile = new ArrayList<>();
FaceEntity face = faceRepository.getFace(query.getFaceId());
if (face == null) {
return Collections.emptyList();
}
Integer sourceType = query.getSourceType();
SourceReqQuery sourceReqQuery = new SourceReqQuery();
sourceReqQuery.setScenicId(query.getScenicId());
sourceReqQuery.setIsBuy(query.getIsBuy());
sourceReqQuery.setMemberId(face.getMemberId());
sourceReqQuery.setType(sourceType);
sourceReqQuery.setFaceId(query.getFaceId());
List<SourceRespVO> list = sourceMapper.listUser(sourceReqQuery);
if (!Integer.valueOf(2).equals(query.getSourceType())) {
return list.stream().map(source -> {
GoodsUrlVO goodsUrlVO = new GoodsUrlVO();
goodsUrlVO.setGoodsType(source.getType());
goodsUrlVO.setGoodsId(source.getId());
goodsUrlVO.setUrl(source.getVideoUrl());
goodsUrlVO.setCreateTime(source.getCreateTime());
return goodsUrlVO;
}).collect(Collectors.toList());
}
List<GoodsUrlVO> defaultUrlList = list.stream().map(source -> {
GoodsUrlVO goodsUrlVO = new GoodsUrlVO();
goodsUrlVO.setGoodsType(source.getType());
goodsUrlVO.setGoodsId(source.getId());
goodsUrlVO.setUrl(source.getUrl());
goodsUrlVO.setCreateTime(source.getCreateTime());
return goodsUrlVO;
}).collect(Collectors.toList());
IsBuyRespVO isBuy = orderBiz.isBuy(face.getMemberId(), query.getScenicId(), query.getSourceType(), query.getFaceId());
if (!isBuy.isBuy()) {
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(face.getScenicId());
if (scenicConfig != null && ((scenicConfig.getAntiScreenRecordType() & 2) == 0)) {
// 未启用水印
return defaultUrlList;
}
IStorageAdapter adapter;
if (scenicConfig != null && scenicConfig.getStoreType() != null) {
adapter = StorageFactory.get(scenicConfig.getStoreType());
adapter.loadConfig(JSONObject.parseObject(scenicConfig.getStoreConfigJson(), Map.class));
} else {
adapter = StorageFactory.use("assets-ext");
}
IOperator operator = ImageWatermarkFactory.get(ImageWatermarkOperatorEnum.WATERMARK);
List<SourceWatermarkEntity> watermarkEntityList = sourceMapper.listSourceWatermark(defaultUrlList.stream().map(GoodsUrlVO::getGoodsId).collect(Collectors.toList()), null, ImageWatermarkOperatorEnum.WATERMARK.getType());
List<GoodsUrlVO> collect = defaultUrlList.stream().peek(item -> {
Optional<SourceWatermarkEntity> any = watermarkEntityList.stream()
.filter(watermark -> watermark.getSourceId().equals(item.getGoodsId()))
.findAny();
if (any.isPresent()) {
item.setUrl(any.get().getWatermarkUrl());
} else {
// 生成
File dstFile = new File(item.getGoodsId() + ".jpg");
File watermarkedFile = new File(item.getGoodsId() + "_" + ImageWatermarkOperatorEnum.WATERMARK.getType() + "." + ImageWatermarkOperatorEnum.WATERMARK.getPreferFileType());
try {
HttpUtil.downloadFile(item.getUrl().replace("oss.zhentuai.com", "frametour-assets.oss-cn-shanghai-internal.aliyuncs.com"), dstFile);
} catch (Exception e) {
log.error("downloadFile error", e);
return;
}
tmpFile.add(dstFile);
WatermarkInfo info = new WatermarkInfo();
info.setOriginalFile(dstFile);
info.setWatermarkedFile(watermarkedFile);
try {
operator.process(info);
} catch (ImageWatermarkException e) {
log.error("process error", e);
return;
}
String url = adapter.uploadFile(watermarkedFile, StorageConstant.PHOTO_WATERMARKED_PATH, watermarkedFile.getName());
adapter.setAcl(StorageAcl.PUBLIC_READ, StorageConstant.PHOTO_WATERMARKED_PATH, watermarkedFile.getName());
sourceMapper.addSourceWatermark(item.getGoodsId(), null, ImageWatermarkOperatorEnum.WATERMARK.getType(), url);
tmpFile.add(watermarkedFile);
item.setUrl(url);
}
}).collect(Collectors.toList());
for (File file : tmpFile) {
file.delete();
}
return collect;
}
return defaultUrlList;
}
@Override
public List<GoodsUrlVO> sourceGoodsListDownload(GoodsReqQuery query) {
List<File> tmpFile = new ArrayList<>();
FaceEntity face = faceRepository.getFace(query.getFaceId());
if (face == null) {
return Collections.emptyList();
}
IsBuyRespVO isBuy = orderBiz.isBuy(face.getMemberId(), query.getScenicId(), query.getSourceType(), query.getFaceId());
if (!isBuy.isBuy()) {
return Collections.emptyList();
}
Integer sourceType = query.getSourceType();
SourceReqQuery sourceReqQuery = new SourceReqQuery();
sourceReqQuery.setScenicId(query.getScenicId());
sourceReqQuery.setIsBuy(query.getIsBuy());
sourceReqQuery.setMemberId(face.getMemberId());
sourceReqQuery.setType(sourceType);
sourceReqQuery.setFaceId(query.getFaceId());
List<SourceRespVO> list = sourceMapper.listUser(sourceReqQuery);
if (query.getGoodsId() != null) {
list = list.stream().filter(source -> source.getId().equals(query.getGoodsId())).collect(Collectors.toList());
}
if (!Integer.valueOf(2).equals(query.getSourceType())) {
return list.stream().map(source -> {
GoodsUrlVO goodsUrlVO = new GoodsUrlVO();
goodsUrlVO.setGoodsType(source.getType());
goodsUrlVO.setGoodsId(source.getId());
goodsUrlVO.setUrl(source.getVideoUrl());
goodsUrlVO.setCreateTime(source.getCreateTime());
return goodsUrlVO;
}).collect(Collectors.toList());
}
List<GoodsUrlVO> defaultUrlList = list.stream().map(source -> {
GoodsUrlVO goodsUrlVO = new GoodsUrlVO();
goodsUrlVO.setGoodsType(source.getType());
goodsUrlVO.setGoodsId(source.getId());
goodsUrlVO.setUrl(source.getUrl());
goodsUrlVO.setCreateTime(source.getCreateTime());
return goodsUrlVO;
}).collect(Collectors.toList());
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(face.getScenicId());
MpConfigEntity scenicMpConfig = scenicRepository.getScenicMpConfig(face.getScenicId());
if (scenicMpConfig == null) {
log.warn("未配置小程序参数,无法生成二维码");
return defaultUrlList;
}
if (scenicConfig != null && scenicConfig.getWatermarkType() != null) {
ImageWatermarkOperatorEnum type = ImageWatermarkOperatorEnum.getByCode(scenicConfig.getWatermarkType());
if (type != null) {
IStorageAdapter adapter;
if (scenicConfig != null && scenicConfig.getStoreType() != null) {
adapter = StorageFactory.get(scenicConfig.getStoreType());
adapter.loadConfig(JSONObject.parseObject(scenicConfig.getStoreConfigJson(), Map.class));
} else {
adapter = StorageFactory.use("assets-ext");
}
IOperator operator = ImageWatermarkFactory.get(type);
List<SourceWatermarkEntity> watermarkEntityList = sourceMapper.listSourceWatermark(list.stream().map(SourceRespVO::getId).collect(Collectors.toList()), face.getId(), type.getType());
File qrcode = new File("qrcode_"+face.getId()+".jpg");
try {
String urlLink = WxMpUtil.generateUrlLink(scenicMpConfig.getAppId(), scenicMpConfig.getAppSecret(), "pages/videoSynthesis/index", "scenicId=" + face.getScenicId() + "&faceId=" + face.getId());
QrCodeUtil.generate(urlLink + "?cq=", 300, 300, qrcode);
} catch (Exception e) {
log.error("generateWXQRCode error", e);
return defaultUrlList;
}
tmpFile.add(qrcode);
List<GoodsUrlVO> collect = defaultUrlList.stream().peek(item -> {
Optional<SourceWatermarkEntity> any = watermarkEntityList.stream()
.filter(watermark -> watermark.getSourceId().equals(item.getGoodsId()))
.findAny();
if (any.isPresent()) {
item.setUrl(any.get().getWatermarkUrl());
} else {
// 生成
File dstFile = new File(item.getGoodsId() + ".jpg");
File watermarkedFile = new File(item.getGoodsId() + "_" + type.getType() + "." + type.getPreferFileType());
try {
HttpUtil.downloadFile(item.getUrl().replace("oss.zhentuai.com", "frametour-assets.oss-cn-shanghai-internal.aliyuncs.com"), dstFile);
} catch (Exception e) {
log.error("downloadFile error", e);
return;
}
tmpFile.add(dstFile);
WatermarkInfo info = new WatermarkInfo();
info.setOriginalFile(dstFile);
info.setQrcodeFile(qrcode);
info.setScenicLine(scenicConfig.getWatermarkScenicText());
info.setDatetime(item.getCreateTime());
info.setDtFormat(scenicConfig.getWatermarkDtFormat());
info.setWatermarkedFile(watermarkedFile);
try {
operator.process(info);
} catch (ImageWatermarkException e) {
log.error("process error", e);
return;
}
String url = adapter.uploadFile(watermarkedFile, StorageConstant.PHOTO_WATERMARKED_PATH, watermarkedFile.getName());
adapter.setAcl(StorageAcl.PUBLIC_READ, StorageConstant.PHOTO_WATERMARKED_PATH, watermarkedFile.getName());
sourceMapper.addSourceWatermark(item.getGoodsId(), face.getId(), type.getType(), url);
tmpFile.add(watermarkedFile);
item.setUrl(url);
}
}).collect(Collectors.toList());
for (File file : tmpFile) {
file.delete();
}
return collect;
}
}
return defaultUrlList;
}
}

View File

@@ -0,0 +1,422 @@
package com.ycwl.basic.service.mobile.impl;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.utils.StringUtils;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.core.util.PemUtil;
import com.wechat.pay.java.service.partnerpayments.jsapi.model.Transaction;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.TransactionAmount;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.AmountReq;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import com.ycwl.basic.biz.OrderBiz;
import com.ycwl.basic.config.WechatConfig;
import com.ycwl.basic.constant.HttpConstant;
import com.ycwl.basic.constant.NumberConstant;
import com.ycwl.basic.constant.WeiXinConstant;
import com.ycwl.basic.enums.BizCodeEnum;
import com.ycwl.basic.enums.OrderStateEnum;
import com.ycwl.basic.enums.StatisticEnum;
import com.ycwl.basic.exception.AppException;
import com.ycwl.basic.mapper.OrderMapper;
import com.ycwl.basic.mapper.PaymentMapper;
import com.ycwl.basic.mapper.StatisticsMapper;
import com.ycwl.basic.model.mobile.statistic.req.StatisticsRecordAddReq;
import com.ycwl.basic.model.pc.order.entity.OrderEntity;
import com.ycwl.basic.model.pc.order.req.OrderUpdateReq;
import com.ycwl.basic.model.pc.payment.entity.PaymentEntity;
import com.ycwl.basic.model.wx.WXPayOrderReqVO;
import com.ycwl.basic.model.wx.WxPayRespVO;
import com.ycwl.basic.model.wx.WxchatCallbackSuccessData;
import com.ycwl.basic.repository.OrderRepository;
import com.ycwl.basic.service.mobile.WxPayService;
import com.ycwl.basic.utils.SnowFlakeUtil;
import com.ycwl.basic.utils.WXPayUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.wechat.pay.java.core.http.Constant.*;
import static com.wechat.pay.java.service.refund.model.Status.SUCCESS;
import static com.ycwl.basic.constant.WeiXinConstant.*;
/**
* @Author: songmingsong
* @CreateTime: 2024-12-05
* @Description: 微信支付实现
* @Version: 1.0
*/
@Slf4j
@Service
public class WxPayServiceImpl implements WxPayService {
@Autowired
private WechatConfig wechatConfig;
@Autowired
private PaymentMapper paymentMapper;
@Autowired
private StatisticsMapper statisticsMapper;
@Autowired
private OrderRepository orderRepository;
@Autowired
private OrderBiz orderBiz;
@Autowired
private OrderMapper orderMapper;
@Override
public WxPayRespVO createOrder(WXPayOrderReqVO req) {
PaymentEntity entity = new PaymentEntity();
entity.setOrderId(req.getOrderSn());
entity.setMemberId(req.getMemberId());
entity.setPayPrice(new BigDecimal(BigInteger.valueOf(req.getTotalPrice()), 2));
Long entityId = paymentMapper.addGetId(entity);
try {
// 使用自动更新平台证书的RSA配置
Config config = getInstance(wechatConfig);
// 构建service
JsapiService service = new JsapiService.Builder().config(config).build();
// request.setXxx(val)设置所需参数,具体参数可见Request定义
PrepayRequest request = new PrepayRequest();
Amount amount = new Amount();
amount.setTotal(req.getTotalPrice());
request.setAmount(amount);
request.setAppid(wechatConfig.getMiniProgramAppId());
request.setMchid(wechatConfig.getMchId());
request.setDescription(req.getGoodsName());
request.setNotifyUrl(wechatConfig.getPayNotifyUrl());
request.setOutTradeNo(req.getOrderSn().toString());
request.setDescription(req.getDescription());
Payer payer = new Payer();
payer.setOpenid(req.getOpenId());
request.setPayer(payer);
// SettleInfo settleInfo = new SettleInfo();
// settleInfo.setProfitSharing(true);
// request.setSettleInfo(settleInfo);
// 调用下单方法,得到应答
PrepayResponse response = service.prepay(request);
WxPayRespVO vo = new WxPayRespVO();
Long timeStamp = System.currentTimeMillis() / NumberConstant.THOUSAND;
vo.setTimeStamp(timeStamp);
String substring = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
vo.setNonceStr(substring);
String signatureStr = Stream.of(wechatConfig.getMiniProgramAppId(), String.valueOf(timeStamp), substring, "prepay_id=" + response.getPrepayId())
.collect(Collectors.joining("\n", "", "\n"));
String sign = WXPayUtil.getSign(signatureStr, wechatConfig.getKeyPath());
vo.setPaySign(sign);
vo.setSignType("RSA-SHA256");
vo.setPrepayId("prepay_id=" + response.getPrepayId());
return vo;
} catch (ServiceException e) {
JSONObject parse = JSONObject.parseObject(e.getResponseBody());
throw new AppException(BizCodeEnum.ADVANCE_PAYMENT_FAILED, parse.getString(HttpConstant.Message));
} catch (Exception e) {
throw new AppException(BizCodeEnum.ADVANCE_PAYMENT_FAILED, e.toString());
}
}
@Override
public void payNotify(HttpServletRequest request) {
try {
// 读取请求体的信息
ServletInputStream inputStream = request.getInputStream();
StringBuffer stringBuffer = new StringBuffer();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s;
// 读取回调请求体
while ((s = bufferedReader.readLine()) != null) {
stringBuffer.append(s);
}
String s1 = stringBuffer.toString();
log.warn("微信支付回调:{}", s1);
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
String nonce = request.getHeader(WECHAT_PAY_NONCE);
String signType = request.getHeader(WeiXinConstant.WECHATPAY_SIGNATURE_TYPE);
String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
NotificationConfig config = new RSAAutoCertificateConfig.Builder()
.merchantId(wechatConfig.getMchId())
.privateKey(wechatConfig.getKeyPath())
.merchantSerialNumber(wechatConfig.getMchSerialNo())
.apiV3Key(wechatConfig.getApiV3())
.build();
// 初始化 NotificationParser
NotificationParser parser = new NotificationParser(config);
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(serialNo)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
// 若未设置signType,默认值为 WECHATPAY2-SHA256-RSA2048
.signType(signType)
.body(s1)
.build();
Transaction parse = parser.parse(requestParam, Transaction.class);
log.info("[微信支付]parse = {}", parse);
// 更新订单信息
new Thread(() -> {
long orderId = Long.parseLong(parse.getOutTradeNo());
switch (parse.getTradeState()) {
case SUCCESS:
orderBiz.paidOrder(orderId);
break;
case NOTPAY:
case CLOSED:
case REVOKED:
orderBiz.cancelOrder(orderId);
break;
case REFUND:
orderBiz.refundOrder(orderId);
break;
}
}).start();
} catch (Exception e) {
throw new AppException(BizCodeEnum.ADVANCE_PAYMENT_CALLBACK_FAILED, e.toString());
}
}
@Override
public WxchatCallbackSuccessData queryPay(Long orderId) {
WxchatCallbackSuccessData wxchatCallbackSuccessData = new WxchatCallbackSuccessData();
QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
queryRequest.setMchid(wechatConfig.getMchId());
queryRequest.setOutTradeNo(orderId.toString());
Config config = getInstance(wechatConfig);
// 构建service
JsapiService service = new JsapiService.Builder().config(config).build();
try {
com.wechat.pay.java.service.payments.model.Transaction transaction = service.queryOrderByOutTradeNo(queryRequest);
String successTime = transaction.getSuccessTime();
wxchatCallbackSuccessData.setOrderId(orderId.toString());
wxchatCallbackSuccessData.setSuccessTime(successTime);
wxchatCallbackSuccessData.setTradetype(WeiXinConstant.getDescriptionType(transaction.getTradeType()));
wxchatCallbackSuccessData.setTradestate(WeiXinConstant.getDescriptionState(transaction.getTradeState()));
TransactionAmount amount = transaction.getAmount();
Integer total = amount.getTotal();
wxchatCallbackSuccessData.setTotalMoney(new BigDecimal(total).movePointLeft(2));
} catch (ServiceException e) {
// API返回失败, 例如ORDER_NOT_EXISTS
log.error("[微信退款] code={}, message={}", e.getErrorCode(), e.getErrorMessage());
log.error("[微信退款] Response_body={}", e.getResponseBody());
}
return wxchatCallbackSuccessData;
}
@Override
public Boolean refundOrder(String orderId) {
OrderEntity order = orderRepository.getOrder(Long.parseLong(orderId));
BigDecimal payPrice = order.getPayPrice();
long priceInCents = payPrice.multiply(new BigDecimal(NumberConstant.HUNDRED)).longValue(); // 转换为分(int)
Config config =
new RSAAutoCertificateConfig.Builder()
.merchantId(wechatConfig.getMchId())
// 使用 com.wechat.pay.java.core.util 中的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名
.privateKey(wechatConfig.getKeyPath())
.merchantSerialNumber(wechatConfig.getMchSerialNo())
.apiV3Key(wechatConfig.getApiV3())
.build();
RefundService service = new RefundService.Builder().config(config).build();
CreateRequest request = new CreateRequest();
request.setNotifyUrl(wechatConfig.getRefundNotifyUrl());
AmountReq amountReq = new AmountReq();
amountReq.setTotal(priceInCents);
amountReq.setRefund(priceInCents);
amountReq.setCurrency(WECHATPAY_CURRENCY_CNY);
request.setAmount(amountReq);
request.setOutTradeNo(orderId);
request.setOutRefundNo(SnowFlakeUtil.getId());
Refund refundResult = service.create(request);
if (refundResult.getStatus() == SUCCESS) {
OrderUpdateReq orderUpdateReq = new OrderUpdateReq();
orderUpdateReq.setId(Long.parseLong(orderId));
orderUpdateReq.setRefundStatus(OrderStateEnum.REFUNDED.getType());
orderUpdateReq.setRefundAt(new Date());
orderMapper.update(orderUpdateReq);
return true;
} else {
return false;
}
}
@Override
public boolean refundNotify(String refundResult) throws IOException, GeneralSecurityException {
// 转为map格式
Map<String, String> jsonMap = JSONObject.parseObject(refundResult, Map.class);
/*
* 退款成功后返回一个加密字段resource,以下为解密
* 解密需要从resource参数中,获取到ciphertext,nonce,associated_data这三个参数进行解密
*/
String resource = JSONObject.toJSONString(jsonMap.get(REFUNDS_RESOURCE));
JSONObject object = JSONObject.parseObject(resource);
String ciphertext = String.valueOf(object.get(REFUNDS_CIPHERTEXT));
String nonce = String.valueOf(object.get(REFUNDS_NONCE));
String associated_data = String.valueOf(object.get(REFUNDS_ASSOCIATED_DATA));
String resultStr = decryptToString(associated_data.getBytes(String.valueOf(StandardCharsets.UTF_8)),
nonce.getBytes(String.valueOf(StandardCharsets.UTF_8)),
ciphertext);
Map<String, String> reqInfo = JSONObject.parseObject(resultStr, Map.class);
String refund_status = reqInfo.get(REFUNDS_REFUND_STATUS);// 退款状态
String out_trade_no = reqInfo.get(WECHATPAY_OUT_TRADE_NO); // 订单号
if (!StringUtils.isEmpty(refund_status) && WECHATPAY_SUCCESS.equals(refund_status)) {
long orderId = Long.parseLong(out_trade_no);
orderBiz.refundOrder(orderId);
OrderEntity order = orderRepository.getOrder(orderId);
StatisticsRecordAddReq statisticsRecordAddReq = new StatisticsRecordAddReq();
statisticsRecordAddReq.setMemberId(order.getMemberId());
statisticsRecordAddReq.setType(StatisticEnum.REFUND.code);
statisticsRecordAddReq.setScenicId(order.getScenicId());
statisticsRecordAddReq.setMorphId(orderId);
statisticsMapper.addStatisticsRecord(statisticsRecordAddReq);
log.info("[微信退款回调]退款成功");
return true;
} else {
log.error("[微信退款回调]退款失败");
return false;
}
}
@Override
public void closeOrder(String orderId) {
CloseOrderRequest closeOrderRequest = new CloseOrderRequest();
closeOrderRequest.setOutTradeNo(orderId);
closeOrderRequest.setMchid(wechatConfig.getMchId());
Config config = getInstance(wechatConfig);
// 构建service
JsapiService service = new JsapiService.Builder().config(config).build();
service.closeOrder(closeOrderRequest);
}
/**
* 退款回调 解密数据
*
* @param associatedData
* @param nonce
* @param ciphertext
* @return
* @throws GeneralSecurityException
* @throws IOException
*/
public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) throws GeneralSecurityException, IOException {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(wechatConfig.getApiV3().getBytes(), "AES");
GCMParameterSpec spec = new GCMParameterSpec(128, nonce);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData);
return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
}
}
/**
* 生成退款请求token
*
* @param method
* @param orderId
* @param body
* @return
* @throws InvalidKeyException
* @throws NoSuchAlgorithmException
* @throws UnsupportedEncodingException
* @throws SignatureException
*/
public String getToken(String method, String orderId, String body) throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException, SignatureException {
String nonceStr = WXPayUtil.generateNonceStr();
long timestamp = System.currentTimeMillis() / NumberConstant.THOUSAND; // 生成时间戳
String parameter = method + "\n"
+ REFUNDS_URi + orderId + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ body + "\n";
// 对参数进行加密
byte[] bytes = parameter.getBytes(String.valueOf(StandardCharsets.UTF_8));
Signature sign = Signature.getInstance("SHA256withRSA");
PrivateKey privateKey = PemUtil.loadPrivateKeyFromString(wechatConfig.getKeyPath()); // privateKeyPath是商户证书密钥的位置apiclient_key.pem
sign.initSign(privateKey); // 商户密钥文件路径
sign.update(bytes);
String signature = Base64.getEncoder().encodeToString(sign.sign());
// 获取token
String token = "mchid=\"" + wechatConfig.getMchId() + "\"," // 商户号
+ "nonce_str=\"" + nonceStr + "\","
+ "timestamp=\"" + timestamp + "\","
+ "serial_no=\"" + wechatConfig.getMchSerialNo() + "\"," // merchantSerialNumber是微信支付中申请的证书序列号
+ "signature=\"" + signature + "\"";
return REFUNDS_SCHEMA + token;
}
/**
* 一个商户号只能初始化一个配置,否则会因为重复的下载任务报错
*/
private static class Holder {
private static volatile Config instance;
static void init(WechatConfig wechatConfig) {
instance = new RSAAutoCertificateConfig.Builder()
.merchantId(wechatConfig.getMchId())
.privateKey(wechatConfig.getKeyPath())
.merchantSerialNumber(wechatConfig.getMchSerialNo())
.apiV3Key(wechatConfig.getApiV3())
.build();
}
}
public static Config getInstance(WechatConfig wechatConfig) {
if (Holder.instance == null) {
synchronized (Holder.class) {
if (Holder.instance == null) {
Holder.init(wechatConfig);
}
}
}
return Holder.instance;
}
}