diff --git a/ruoyi-admin/src/main/java/org/dromara/app/AppCommentController.java b/ruoyi-admin/src/main/java/org/dromara/app/AppCommentController.java index d8e32d6dd..4910a4f68 100644 --- a/ruoyi-admin/src/main/java/org/dromara/app/AppCommentController.java +++ b/ruoyi-admin/src/main/java/org/dromara/app/AppCommentController.java @@ -14,6 +14,7 @@ import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.extern.slf4j.Slf4j; import org.dromara.common.core.domain.R; +import org.dromara.common.redis.redis.RedisCache; import org.dromara.common.satoken.utils.LoginHelper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -32,6 +33,9 @@ public class AppCommentController { private RedisOperator redis; @Autowired private MsgService msgService; + @Autowired + private RedisCache redisCache; + @ApiOperation("查询视频评论列表") @PostMapping("/vlogComments") public R> queryVlogComments( @@ -66,7 +70,7 @@ public class AppCommentController { commentService.createComment(bo); // 2) 短视频评论总数 +1(Redis 优先) - redis.increment(BaseInfoProperties.REDIS_VLOG_COMMENT_COUNTS + ":" + bo.getVlogId(), 1); + redisCache.zSetIncrement(BaseInfoProperties.REDIS_VLOG_COMMENT_COUNTS , bo.getVlogId(), 1); // 3) 发送站内消息:根评论 -> 通知视频作者;回复评论 -> 通知被回复用户 if ("0".equals(bo.getFatherCommentId())) { diff --git a/ruoyi-admin/src/main/java/org/dromara/app/AppVlogController.java b/ruoyi-admin/src/main/java/org/dromara/app/AppVlogController.java index 716f8f66a..a3498c6ee 100644 --- a/ruoyi-admin/src/main/java/org/dromara/app/AppVlogController.java +++ b/ruoyi-admin/src/main/java/org/dromara/app/AppVlogController.java @@ -6,6 +6,7 @@ import com.wzj.soopin.content.domain.bo.*; import com.wzj.soopin.content.domain.po.MyLikedVlog; import com.wzj.soopin.content.domain.po.Vlog; import com.wzj.soopin.content.domain.vo.IndexVlogVO; +import com.wzj.soopin.content.service.IVlogPullService; import com.wzj.soopin.content.service.VlogService; import com.wzj.soopin.content.service.VlogUploadService; import com.wzj.soopin.content.utils.PagedGridResult; @@ -13,6 +14,7 @@ import com.wzj.soopin.content.utils.QcCloud; import com.wzj.soopin.content.utils.RedisOperator; import io.swagger.annotations.Api; import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.dromara.common.core.domain.R; @@ -22,6 +24,7 @@ import org.dromara.common.mq.domain.MQMessage; import org.dromara.common.mq.enums.MQMessageType; import org.dromara.common.mq.enums.MessageActionEnum; import org.dromara.common.mq.utils.MqUtil; +import org.dromara.common.redis.redis.RedisCache; import org.dromara.common.satoken.utils.LoginHelper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -38,34 +41,25 @@ import static com.wzj.soopin.content.domain.base.BaseInfoProperties.*; @Api(tags = "VlogController 短视频相关业务功能的接口") @RequestMapping("/app/vlog") @RestController +@AllArgsConstructor public class AppVlogController { - @Autowired - private VlogService vlogService; - @Autowired - private QcCloud qcCloud; - @Autowired - private VlogUploadService vlogUploadService; - @Autowired + private final VlogService vlogService; + private final QcCloud qcCloud; + private final VlogUploadService vlogUploadService; + public final RedisCache cache; + + private final IVlogPullService pullService; - public RedisOperator redis; @Tag(name = "首页视频列表") @PostMapping("/indexList") public R> indexList(@RequestBody IndexListBO bo, @RequestBody Page page) { - try{ - LoginUser loginUser = LoginHelper.getLoginUser(); - if (loginUser == null) { - throw new ServiceException("用户未登录"); - } - bo.setUserId(String.valueOf(loginUser.getUserId())); - }catch (Exception e){ - log.error("用户没登陆", e); - } - Page pages = vlogService.getIndexVlogList(bo, page); + Page pages = pullService.page(page); return R.ok(pages); } + @GetMapping("/detail/{vlogId}") - public R detail( @PathVariable String vlogId) { + public R detail(@PathVariable String vlogId) { return R.ok(vlogService.getVlogDetailById(vlogId)); } @@ -99,7 +93,7 @@ public class AppVlogController { throw new ServiceException("用户未登录"); } bo.setMyId(String.valueOf(loginUser.getUserId())); - Page pages = vlogService.getMyFollowVlogList( page); + Page pages = vlogService.getMyFollowVlogList(page); return R.ok(pages); } @@ -111,9 +105,10 @@ public class AppVlogController { throw new ServiceException("用户未登录"); } bo.setMyId(String.valueOf(loginUser.getUserId())); - Page pages = vlogService.getMyFriendVlogList( page); + Page pages = vlogService.getMyFriendVlogList(page); return R.ok(pages); } + @PostMapping("publish") public R publish(@RequestBody VlogBO vlogBO) throws Exception { @@ -126,12 +121,12 @@ public class AppVlogController { String url = vlogBO.getUrl(); - log.info("未审核视频地址:"+url); + log.info("未审核视频地址:" + url); String fileName = url.substring(url.lastIndexOf("/") + 1); - log.info("视频文件名称:"+fileName); - log.info("开始上传腾讯云点播:"+fileName); + log.info("视频文件名称:" + fileName); + log.info("开始上传腾讯云点播:" + fileName); String fileId = qcCloud.uploadViaTempFile(fileName); - log.info("视频发布ID:"+fileId); + log.info("视频发布ID:" + fileId); vlogBO.setFileId(fileId); // 删除minio文件 // MinIOUtils.removeFile(minIOConfig.getBucketName(),fileName); @@ -142,6 +137,7 @@ public class AppVlogController { return R.ok(); } + @Tag(name = "我的公开视频列表") @PostMapping("/myPublicList") public R> myPublicList(@RequestBody MyListBO bo, @RequestBody Page page) { @@ -149,155 +145,51 @@ public class AppVlogController { return R.ok(pages); } - - - private Integer nacosConuts=0; - - - @PostMapping("like") + @PostMapping("/like") public R like(@RequestBody VlogBO vlogBO) { LoginUser loginUser = LoginHelper.getLoginUser(); if (loginUser == null) { throw new ServiceException("用户未登录"); } - String userId = String.valueOf(loginUser.getUserId()); String vlogId = vlogBO.getId(); - - //获取vlog - Vlog vlog = vlogService.getVlog(vlogId); - if(vlog==null){ - throw new ServiceException("视频不存在"); - } - // 我点赞的视频,关联关系保存到数据库 - vlogService.userLikeVlog(userId, vlogId); - - // 点赞后,视频和视频发布者的获赞都会 +1 - redis.increment(REDIS_VLOGER_BE_LIKED_COUNTS + ":" + vlog.getMemberId(), 1); - redis.increment(REDIS_VLOG_BE_LIKED_COUNTS + ":" + vlogId, 1); - - // 我点赞的视频,需要在redis中保存关联关系 - redis.set(REDIS_USER_LIKE_VLOG + ":" + userId + ":" + vlogId, "1"); - - log.info("nacosConuts="+nacosConuts); - - String countsStr = redis.get(REDIS_VLOG_BE_LIKED_COUNTS + ":" + vlogId); - Integer counts=0; - if (StringUtils.isNotBlank(countsStr)){ - - counts=Integer.valueOf(countsStr); - if (counts>=nacosConuts){ - - vlogService.flushCounts(vlogId, counts); - } - } - if (userId != null && vlog.getMemberId() != null && !userId.equals(vlog.getMemberId())) { - // 新版:使用模板类型编号和参数 - Map params = new HashMap<>(); - params.put("userId", userId); - params.put("nickname", loginUser.getNickname()); - params.put("action", MessageActionEnum.INTERACTION_LIKE.name()); - params.put("toUserId",vlog.getMemberId()); - MQMessage message = MQMessage.builder() - .messageType(MQMessageType.IM.name()) - .data(params) - .source("member") - .build(); - // 关注消息 - MqUtil.sendIMMessage(message); - - } - + vlogService.userLikeVlog(userId,vlogId ); return R.ok(); } - @PostMapping("unlike") - public R unlike(@RequestBody Map params) { + + @PostMapping("/unlike") + public R unlike(@RequestBody VlogBO vlogBO) { LoginUser loginUser = LoginHelper.getLoginUser(); if (loginUser == null) { throw new ServiceException("用户未登录"); } - String userId = String.valueOf(loginUser.getUserId()); - String vlogId = params.get("vlogId"); - - //获取vlog - Vlog vlog = vlogService.getVlog(vlogId); - if(vlog==null){ - throw new ServiceException("视频不存在"); - } - - // 我取消点赞的视频,关联关系删除 + String vlogId = vlogBO.getId(); vlogService.userUnLikeVlog(userId, vlogId); - - redis.decrement(REDIS_VLOGER_BE_LIKED_COUNTS + ":" + vlog.getMemberId(), 1); - redis.decrement(REDIS_VLOG_BE_LIKED_COUNTS + ":" + vlogId, 1); - redis.del(REDIS_USER_LIKE_VLOG + ":" + userId + ":" + vlogId); - return R.ok(); } + @GetMapping("/read") + public R read(@RequestBody VlogBO vlogBO) { + LoginUser loginUser = LoginHelper.getLoginUser(); + if (loginUser == null) { + throw new ServiceException("用户未登录"); + } + vlogService.readVlog(loginUser.getUserId(), vlogBO.getId()); + return R.ok(); + } + @Tag(name = "手动触发缓存点赞最多视频") @PostMapping("/cacheTopLikedVlogs") public R cacheTopLikedVlogs(@RequestParam(defaultValue = "100") int limit) { try { - vlogService.cacheTopLikedVlogs(limit); +// vlogService.cacheTopLikedVlogs(limit); return R.ok(); } catch (Exception e) { log.error("手动触发缓存点赞最多视频失败", e); return R.fail("缓存失败: " + e.getMessage()); } } - - @Tag(name = "获取缓存中的点赞最多视频") - @GetMapping("/getTopLikedVlogs") - public R getTopLikedVlogs(@RequestParam(defaultValue = "") String date, - @RequestParam(defaultValue = "10") int pageSize, - @RequestParam(defaultValue = "0") int pageNum) { - try { - String redisKey; - if (StringUtils.isBlank(date)) { - // 如果没有指定日期,使用今天的日期 - redisKey = "top_liked_vlogs:" + java.time.LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd")); - } else { - redisKey = "top_liked_vlogs:" + date; - } - - String cachedData = redis.get(redisKey); - List> resultList = new ArrayList<>(); - - if (StringUtils.isNotBlank(cachedData)) { - // 解析JSON数据 - ObjectMapper objectMapper = new ObjectMapper(); - List> vlogList = objectMapper.readValue(cachedData, new com.fasterxml.jackson.core.type.TypeReference>>() {}); - - // 计算分页 - int startIndex = pageNum * pageSize; - int endIndex = Math.min(startIndex + pageSize, vlogList.size()); - - if (startIndex < vlogList.size()) { - // 从Redis缓存中获取指定页的数据 - resultList = vlogList.subList(startIndex, endIndex); - log.info("从Redis缓存中获取了{}条视频数据", resultList.size()); - } - } - - // 如果Redis中的数据不足,从数据库随机查询补充 - if (resultList.size() < pageSize) { - int needMore = pageSize - resultList.size(); - log.info("Redis缓存数据不足,需要从数据库随机查询{}条视频", needMore); - - // 从数据库随机查询视频 - List> randomVlogs = vlogService.getRandomVlogs(needMore); - resultList.addAll(randomVlogs); - log.info("从数据库随机查询了{}条视频", randomVlogs.size()); - } - - return R.ok(resultList); - } catch (Exception e) { - log.error("获取缓存中的点赞最多视频失败", e); - return R.fail("获取缓存失败: " + e.getMessage()); - } - } } diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml index 330d0286a..c7979e16e 100644 --- a/ruoyi-admin/src/main/resources/application-dev.yml +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -254,3 +254,12 @@ tencent: password: A1969bf8 authentication-database: admin # replica-set-name: mongoreplset + +easypay: + api-path-prefix: https://d-phoenix-gap.easypay.com.cn:24443/yqt + req-id: D01X66666667068 + mcht-code: 631000000003325 + wechat-mchid: 804474446 + easypay-public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArgVerkGaSEQvZIOpLjeUoVpL0lSYLc04+txtPFtfm5r5XFbaNaf5Ahu0lziGEwWzrGONThSsnb3U9pqoY6BpqviN4h+Guw5oEdHr1T/eDkQD5urgQUaZA6lDoU9XC662r+0kpbKidvXIsK2CrShN+BF8HEJmRZuhglxh25OHWIWqQiUDjLZC+QJRZqUu9Uzy9RBBu7qa0f0xbqYl3hnYi+vH++SsyOavO2gUVQyKU5Kkt5ZJVpZFQvD3BXePgwJSpsvrjhj0hiYp2v6PScN9XHP1vXB4wtIYSFYwmVus1KkV/LfDzUm6zHjliHYTVl6lPMhveIVJlRIqInRZRHxg5QIDAQAB + merRsaPrivateKey: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCc1mku1mtfTc1vZM9z3TkRMZ9SaKP+6MdoQhjDHB9vJUOuG2Jnegej4gtzcm8MVYomV1azJMtCbPq4PN9aHegH13JthvrUPR3nAKp8AUp9Fh47ded//snNyAf7C7o8xKN5e7n89ROwacCAL2QDno76ngXzQQVj1TxY9pBdekZ03ezuDk1sv/u4FBlW8kRyaVdqyl4FdvP+EEljDe8gGxRikLyb3cK1B6G7w2BXgM/svft5SOoiqStx2XvgHRrFRB5w96TdoKNH9yxHU2clMJiz+5cej1VL6OHZspuY3cnPyJtyS+weYjDT+COLLBUe9UwbOV15DXDNpBLd34W4GiqtAgMBAAECggEAYaVwmVOwSAblp7wJGScb16OggStbJ2MAe93jEt7Yh1eZGrY7/xbP2O3smTUfBHvhZlusRB7dWf8F3l0v5iiGhRNTT/PhCPEARAl7G3emS9jQe869kkgslq06ose7bQg0i3dH5cEkQAqnameXClXWyRxHshrCY1SonO7uFPUDFtD2Z9GHu6dOWkOHfX7UETHV5/NTSqvner5M2YNsV/5To87zrmaeeNyvu91JgJSYofTD8IcMyhxcswxB9F3ECP7nulLDdCV+9mvE9zO0i4mPBxYb++87J0pu35TN9OY+gpiCK3Ed0gDqvvkTiCQEFu6y7OiqsQwy03CE5hAwWZR2YQKBgQDdb6e7+FWJSr9fIknfvZPPtKmbEkMHP1RYnVo3pmuVm14Z3UmostiSBRPMfVjeOX0N82SONM9P+1v9SDgSK3pL8rlk9Hx6D5KI7uj2IMtTVt00pO31KtxDLmXfgapDbWHK1RGEn1PDrnKYy5yOKJ8n0Wgp4lVEPIBN6uIud0A9iQKBgQC1UXdhEXFETyqEYWb9OnWbBjiVIw3D2/qW5c+WQGsHYdMEImC/0oTWIH9fKd4Azf26JjZ9nQvs5VcijJP6BXbGPqir5NgGVkwCj33PoNg4dDgIVE4BYaSp//7B6jCi5QiRTzCSOmUSkZ5L1Kz4SNEDe9r0MpLhxrsAzmR9aEJ/BQKBgHI71Ks63F2cSwd39+ZNtYA0cj7Gd/+4IvooCs+kseGXKj9rkkFOKj2CEwmuLHdP7vyQcHKQOdbIFFegtxRgi5G8oPm8yq5pdC3iGhpHJr1SlYFACGYu+zxJJlLcYIqyVf2+V3A0hZDwYLwEZjpMKHbxJ6xbz6MJFyObJZ3U9TYJAoGANXoGjJF5Z501u/+CQZN5VjSagZnqGGcL3G+BLx5msrGua9y7zjeHyCOjjWyqtnAKsllM3vVvq/nkHiN6DVaJNmUKmFARSqUvG944TAFzZAsa75H1w8CJsT34ZDbvC0wjn7/MYoRohPZ/ynu6XCwVwUJJTJaR7ZcQVmeJCdezLQUCgYEAszix6V9oL80Clb1nMwIly6I78+pcFKyk82yXxvySb6XPJvgoasbz9xYIeKlPyy6r8Aj/ujz5GVtDtkE6n6bsjekCdnUKUY3uTvFAX+YGQkuZnaPoisELJFI2Hal22tNnJyCOYh//AGAiHYpHNUKD4hsKBb45MhK1xwvTHpuLkdc= + backUrl: http://192.168.1.71:8080/easypay/trade/callback diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index bec720e59..f010a8b2c 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -375,6 +375,9 @@ wechat: transfer-notify-url: https://a94aeb5582c2.ngrok-free.app/no-auth/wechat/notify # 转账回调地址 app-id: wxebcdaea31881caab # 应用ID secret: your_wechat_secret # 应用密钥 + mini-program: + app-id: wx87a5db19138da60d + secret: your_wechat_secret http: client: diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java index c2723c880..e35d3288c 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java @@ -36,4 +36,21 @@ public interface GlobalConstants { * 会员 redis key */ String MEMBER_KEY = GLOBAL_REDIS_KEY + "member:"; + + /** + * 视频 redis key + */ + String VLOG_KEY = GLOBAL_REDIS_KEY + "vlog:"; + + /** + * 会员视频 redis key + */ + String GLOBAL_OFFSET = GLOBAL_REDIS_KEY + "offset:"; + + + /** + * 热点视频 redis key + */ + String HOT_VLOG_TAG = "HOT"; + } diff --git a/ruoyi-common/ruoyi-common-mq/pom.xml b/ruoyi-common/ruoyi-common-mq/pom.xml index 97f1eb7d5..0ae0f2504 100644 --- a/ruoyi-common/ruoyi-common-mq/pom.xml +++ b/ruoyi-common/ruoyi-common-mq/pom.xml @@ -48,6 +48,10 @@ 2.15.2 - + + org.apache.rocketmq + rocketmq-client + 5.3.0 + diff --git a/ruoyi-common/ruoyi-common-mq/src/main/java/org/dromara/common/mq/domain/VlogPushEvent.java b/ruoyi-common/ruoyi-common-mq/src/main/java/org/dromara/common/mq/domain/VlogPushEvent.java new file mode 100644 index 000000000..192133f92 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mq/src/main/java/org/dromara/common/mq/domain/VlogPushEvent.java @@ -0,0 +1,37 @@ +package org.dromara.common.mq.domain; + +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +/** + * 消息事件 + * + * @author ruoyi + */ +@Getter +public class VlogPushEvent extends ApplicationEvent { + + private final String tag; + private final String userIdStr; + + /** + * 使用 Long 类型用户 ID 创建消息事件 + */ + public VlogPushEvent(Object source, String tag) { + super(source); + this.tag = tag; + this.userIdStr = tag; + } + /** + * 使用 Long 类型用户 ID 创建消息事件 + */ + public VlogPushEvent(String tag) { + super(tag); + this.tag = tag; + this.userIdStr = null; + } + + + + +} diff --git a/ruoyi-common/ruoyi-common-mq/src/main/java/org/dromara/common/mq/utils/MqUtil.java b/ruoyi-common/ruoyi-common-mq/src/main/java/org/dromara/common/mq/utils/MqUtil.java index 0e9c994ae..23031d00b 100644 --- a/ruoyi-common/ruoyi-common-mq/src/main/java/org/dromara/common/mq/utils/MqUtil.java +++ b/ruoyi-common/ruoyi-common-mq/src/main/java/org/dromara/common/mq/utils/MqUtil.java @@ -1,8 +1,13 @@ package org.dromara.common.mq.utils; import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer; +import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; +import org.apache.rocketmq.client.consumer.LitePullConsumer; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; +import org.apache.rocketmq.common.consumer.ConsumeFromWhere; +import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.mq.config.RocketMQConfig; @@ -13,18 +18,40 @@ import org.springframework.context.ApplicationContextAware; import org.springframework.messaging.support.MessageBuilder; import org.springframework.stereotype.Component; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + @Slf4j @Component public class MqUtil implements ApplicationContextAware { + private static final String NAME_SERVER_ADDR = "localhost:9876"; + private static final String CONSUMER_GROUP = "TEST_PULL_GROUP"; + private static final String TOPIC = "TEST_TOPIC"; private static RocketMQTemplate template; - + private static Map consumerMap = new ConcurrentHashMap<>(); @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { template = applicationContext.getBean(RocketMQTemplate.class); } + public void init() throws Exception { + // 1. 创建消费者实例,指定消费者组 + DefaultLitePullConsumer consumer = new DefaultLitePullConsumer("YOUR_PULL_CONSUMER_GROUP"); + + consumer.setNamesrvAddr("localhost:9876"); // 替换为您的NameServer地址 + consumer.subscribe("YOUR_TOPIC", "*"); + consumer.start(); + // 3. 设置从何处开始消费(首次启动时) + //consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); + // 4. 订阅主题和Tag + consumer.subscribe("YOUR_TOPIC", "*"); // 使用*消费所有Tag + // 5. 启动消费者 + consumer.start(); + System.out.println("LitePullConsumer 启动成功"); + } + // - public static void sendMessage(String topic, MQMessage message) { + public static void sendMessage(String destination, MQMessage message) { if (template == null) { log.error("RocketMQTemplate未初始化,无法发送消息"); return; @@ -32,10 +59,10 @@ public class MqUtil implements ApplicationContextAware { try { String jsonMessage = JsonUtils.toJsonString(message); - template.convertAndSend(topic, jsonMessage); - log.info("发送消息到RocketMQ成功,topic: {}, message: {}", topic, jsonMessage); + template.convertAndSend(destination, jsonMessage); + log.info("发送消息到RocketMQ成功,topic: {}, message: {}", destination, jsonMessage); } catch (Exception e) { - log.error("发送消息到RocketMQ失败,topic: {}, message: {}", topic, message, e); + log.error("发送消息到RocketMQ失败,topic: {}, message: {}", destination, message, e); } } public static void sendIMMessage( MQMessage message) { @@ -97,5 +124,4 @@ public class MqUtil implements ApplicationContextAware { } } - } diff --git a/ruoyi-common/ruoyi-common-mq/src/main/java/org/dromara/common/mq/utils/UserRocketMQConsumerManager.java b/ruoyi-common/ruoyi-common-mq/src/main/java/org/dromara/common/mq/utils/UserRocketMQConsumerManager.java new file mode 100644 index 000000000..0357fae7a --- /dev/null +++ b/ruoyi-common/ruoyi-common-mq/src/main/java/org/dromara/common/mq/utils/UserRocketMQConsumerManager.java @@ -0,0 +1,212 @@ +package org.dromara.common.mq.utils; + +import lombok.AllArgsConstructor; +import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; +import org.apache.rocketmq.client.consumer.PullResult; +import org.apache.rocketmq.client.consumer.PullStatus; +import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely; +import org.apache.rocketmq.client.exception.MQBrokerException; +import org.apache.rocketmq.client.exception.MQClientException; +import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.common.message.MessageQueue; +import org.apache.rocketmq.remoting.exception.RemotingException; +import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; +import org.dromara.common.core.constant.GlobalConstants; +import org.dromara.common.redis.redis.RedisCache; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * 带超时自动销毁功能的用户级RocketMQ消费者管理器 + * 支持按用户隔离消费者,并自动清理超时未活动的实例 + */ +@Component +public class UserRocketMQConsumerManager { + // 全局NameServer地址 + private String namesrvAddr="192.168.1.65:9876"; + + // 超时时间(毫秒),默认30分钟 + private long timeoutMillis = 30 * 60 * 1000; + + // 定时检查间隔(毫秒),默认5分钟 + private long checkIntervalMillis = 5 * 60 * 1000; + + // 存储用户与消费者的映射(线程安全) + private final Map userConsumerMap = new ConcurrentHashMap<>(); + + // 定时任务执行器,用于检测超时消费者 + private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + + private final String consumerGroup="user_consumer_group"; + private final String topic="MEMBER_VLOG_MSG"; + + @Autowired + private RedisCache redisCache; + + + + /** + * 为指定用户创建消费者实例 + * @param userId 用户唯一标识 + * @param consumerGroup 该用户的消费组 + * @param topic 消费的主题 + * @return 是否创建成功 + */ + public boolean createConsumer(String userId, String consumerGroup, String topic) throws MQClientException { + validateUserId(userId); + + // 若用户已存在消费者,更新活动时间并返回 + if (userConsumerMap.containsKey(userId)) { + UserConsumerContext context = userConsumerMap.get(userId); + context.lastActiveTime = System.currentTimeMillis(); + System.out.println("用户[" + userId + "]的消费者已存在,更新活动时间"); + return false; + } + System.setProperty("rocketmq.namesrv.addr", namesrvAddr); + // 创建消费者 + DefaultMQPullConsumer consumer = new DefaultMQPullConsumer(consumerGroup); + consumer.setMessageModel(MessageModel.CLUSTERING); + consumer.setAllocateMessageQueueStrategy(new AllocateMessageQueueAveragely()); + consumer.start(); + + + // 初始化该用户的偏移量存储 + redisCache.zSetAdd(GlobalConstants.GLOBAL_OFFSET+userId,userId,0); + + // 存储用户消费者上下文 + UserConsumerContext context = new UserConsumerContext( + consumer, + consumerGroup, + topic, + System.currentTimeMillis() + ); + userConsumerMap.put(userId, context); + System.out.println("用户[" + userId + "]的消费者创建成功,消费组: " + consumerGroup); + return true; + } + + /** + * 为指定用户拉取消息(会更新活动时间) + * @param userId 用户唯一标识 + * @param batchSize 每次拉取数量 + * @return 拉取到的消息列表 + */ + public List pullUserMessages(String userId , int batchSize) + throws MQClientException, InterruptedException, MQBrokerException, RemotingException { + validateUserId(userId); + UserConsumerContext context = getUserConsumerContext(userId); + + // 更新最后活动时间 + context.lastActiveTime = System.currentTimeMillis(); + + // 获取用户的消息队列 + Set mqs = context.consumer.fetchSubscribeMessageQueues(context.topic); + List allMessages = new ArrayList<>(); + + int current=0; + // 拉取所有队列的消息 + for (MessageQueue mq : mqs) { + PullResult result = pullSingleQueueMessage(userId, context.consumer, mq , batchSize-current); + if (result.getPullStatus() == PullStatus.FOUND) { + allMessages.addAll(result.getMsgFoundList()); + // 更新用户的偏移量 + updateUserOffset(userId, mq, result.getNextBeginOffset()); + redisCache.zSetAdd(GlobalConstants.GLOBAL_OFFSET+userId,mq.getQueueId()+"",result.getNextBeginOffset()); + current+=result.getMsgFoundList().size(); + if(current==batchSize){ + break; + } + } + + } + + return allMessages; + } + + /** + * 获取指定用户的消费者上下文 + */ + private UserConsumerContext getUserConsumerContext(String userId) throws MQClientException { + UserConsumerContext context = userConsumerMap.get(userId); + if (context == null) { + createConsumer(userId, consumerGroup,topic); + } + return userConsumerMap.get(userId); + } + + /** + * 拉取单个队列的消息 + */ + private PullResult pullSingleQueueMessage(String userId, DefaultMQPullConsumer consumer, + MessageQueue mq, int batchSize) + throws MQClientException, MQBrokerException, RemotingException, InterruptedException { + // 获取该用户在该队列的偏移量 + Double offset = getUserQueueOffset(userId, mq); + if(offset==null){ + offset=0.0; + } + return consumer.pull(mq, userId, offset.longValue(), batchSize); + } + + /** + * 获取用户在指定队列的消费偏移量 + */ + private Double getUserQueueOffset(String userId, MessageQueue mq) { + return redisCache.zSetScore(GlobalConstants.GLOBAL_OFFSET+userId,mq.getQueueId()+"") ; + } + + /** + * 更新用户在指定队列的消费偏移量 + */ + private void updateUserOffset(String userId, MessageQueue mq, long offset) { + } + + /** + * 获取当前所有用户的消费者列表 + */ + public Set getRegisteredUserIds() { + return userConsumerMap.keySet(); + } + + /** + * 校验用户ID + */ + private void validateUserId(String userId) { + if (userId == null || userId.trim().isEmpty()) { + throw new IllegalArgumentException("用户ID不能为空"); + } + } + + + /** + * 用户消费者上下文(存储消费者实例及活动时间) + */ + private static class UserConsumerContext { + DefaultMQPullConsumer consumer; + String consumerGroup; + String topic; + long createTime; // 创建时间 + long lastActiveTime; // 最后活动时间(拉取消息时间) + + public UserConsumerContext(DefaultMQPullConsumer consumer, String consumerGroup, + String topic, long createTime) { + this.consumer = consumer; + this.consumerGroup = consumerGroup; + this.topic = topic; + this.createTime = createTime; + this.lastActiveTime = createTime; // 初始活动时间=创建时间 + } + } + +} + diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/redis/RedisCache.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/redis/RedisCache.java index 165171a53..0ccabc737 100644 --- a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/redis/RedisCache.java +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/redis/RedisCache.java @@ -7,6 +7,7 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component; +import java.sql.ShardingKey; import java.util.*; import java.util.concurrent.TimeUnit; @@ -121,7 +122,7 @@ public class RedisCache * 获得缓存的list对象 * * @param key 缓存的键值 - * @return 缓存键值对应的数据 + * * @return 缓存键值对应的数据 */ public List getCacheList(final String key) { @@ -230,4 +231,98 @@ public class RedisCache } + //zeset类型的增删改查 + /** + * 新增zset + * @param key + * @param value + * @param score + */ + public void zSetAdd(String key, String value, double score) { + redisTemplate.opsForZSet().add(key, value, score); + } + + /** + * 查询zset + * @param key + * @param start + * @param end + * @return + */ + public Set zSetRange(String key, long start, long end) { + return redisTemplate.opsForZSet().range(key, start, end); + } + /** + * 删除zset + * @param key + * @param value + */ + public void zSetRemove(String key, String value) { + redisTemplate.opsForZSet().remove(key, value); + } + /** + * 查询zset分数 + * @param key + * @param value + * @return + */ + public Double zSetScore(String key, String value) { + return redisTemplate.opsForZSet().score(key, value); + } + /** + * 查询zset长度 + * @param key + * @return + */ + public Long zSetSize(String key) { + return redisTemplate.opsForZSet().size(key); + } + /** + * 查询zset排名 + * @param key + * @param value + * @return + */ + public Long zSetRank(String key, String value) { + return redisTemplate.opsForZSet().rank(key, value); + } + /** + * 排行榜 + * @param key + * @param start + * @param end + * @return + */ + public Set zSetReverseRange(String key, long start, long end) { + return redisTemplate.opsForZSet().reverseRange(key, start, end); + } + /** + * 增加增量 + * @param key + * @param value + * @param delta + */ + public void zSetIncrement(String key, String value, double delta) { + redisTemplate.opsForZSet().incrementScore(key, value, delta); + } + /** + * 减少数量 + * @param key + * @param value + * @param delta + */ + public void zSetDecrement(String key, String value, double delta) { + redisTemplate.opsForZSet().incrementScore(key, value, -delta); + } + + + /** + * 是否有这个成员 + * @param key + * @param value + * @return + */ + public boolean zSetHasMember(String key, String value) { + return redisTemplate.opsForZSet().rank(key, value) != null; + } } diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java index 355cd2931..228502cd0 100644 --- a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java +++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java @@ -7,10 +7,7 @@ import org.redisson.api.*; import org.redisson.api.options.KeysScanOptions; import java.time.Duration; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -581,4 +578,6 @@ public class RedisUtils { RKeys rKeys = CLIENT.getKeys(); return rKeys.countExists(key) > 0; } + + } diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java index a5729387d..b7c7609a1 100644 --- a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java @@ -65,11 +65,16 @@ public class LoginHelper { */ @SuppressWarnings("unchecked cast") public static T getLoginUser() { - SaSession session = StpUtil.getTokenSession(); - if (ObjectUtil.isNull(session)) { + try { + SaSession session = StpUtil.getTokenSession(); + if (ObjectUtil.isNull(session)) { + return null; + } + return (T) session.get(LOGIN_USER_KEY); + }catch (Exception e){ return null; } - return (T) session.get(LOGIN_USER_KEY); + } /** diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/ArticleCategoryController.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/ArticleCategoryController.java index 9d707a5a5..be6a47c41 100644 --- a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/ArticleCategoryController.java +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/ArticleCategoryController.java @@ -27,7 +27,7 @@ import java.util.List; */ @Tag(name = "文章分类管理") @RestController -@RequestMapping("/content/article/category") +@RequestMapping("/cms/article/category") @RequiredArgsConstructor public class ArticleCategoryController { diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/ArticleController.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/ArticleController.java index 253d9d3b6..f3826d7c3 100644 --- a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/ArticleController.java +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/ArticleController.java @@ -27,7 +27,7 @@ import java.util.List; */ @Tag(name = "内容管理") @RestController -@RequestMapping("/content/article") +@RequestMapping("/cms/article") @RequiredArgsConstructor public class ArticleController { diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/VlogController.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/VlogController.java index aba847e2c..b14364eaf 100644 --- a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/VlogController.java +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/VlogController.java @@ -3,8 +3,12 @@ package com.wzj.soopin.content.controller; import com.wzj.soopin.content.domain.base.BaseInfoProperties; -import com.wzj.soopin.content.domain.bo.VlogBO; +import com.wzj.soopin.content.domain.bo.*; +import com.wzj.soopin.content.domain.po.Article; +import com.wzj.soopin.content.domain.vo.ArticleVO; +import com.wzj.soopin.content.domain.vo.IndexVlogVO; import com.wzj.soopin.content.enums.YesOrNo; +import com.wzj.soopin.content.service.IVlogPushService; import org.dromara.common.core.domain.R; import com.wzj.soopin.content.service.VlogService; import com.wzj.soopin.content.service.VlogUploadService; @@ -20,9 +24,6 @@ import com.wzj.soopin.content.utils.RedisOperator; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import io.swagger.v3.oas.annotations.tags.Tag; -import com.wzj.soopin.content.domain.bo.IndexListBO; -import com.wzj.soopin.content.domain.bo.MyListBO; -import com.wzj.soopin.content.domain.bo.SimpleListBO; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.web.bind.annotation.*; @@ -33,7 +34,7 @@ import java.util.ArrayList; @Slf4j @Api(tags = "VlogController 短视频相关业务功能的接口") -@RequestMapping("/vlog") +@RequestMapping("/cms/vlog") @RestController public class VlogController extends BaseInfoProperties { @@ -45,8 +46,15 @@ public class VlogController extends BaseInfoProperties { private VlogUploadService vlogUploadService; @Autowired public RedisOperator redis; - - @PostMapping("vodCallBack") + @Autowired + private IVlogPushService vlogPushService; + @Tag(name = "查询列表") + @PostMapping("/page") + public R> page(@RequestBody VlogBO bo, @RequestBody Page page) { + Page pages = vlogService.getIndexVlogList(bo ,page); + return R.ok(pages); + } + @PostMapping("/vodCallBack") public R vodCallBack(@RequestBody Map callbackData) { try { // 解析回调事件类型 @@ -160,7 +168,7 @@ public class VlogController extends BaseInfoProperties { - @PostMapping("changeVlogStatus") + @PostMapping("/changeVlogStatus") public R changeVlogStatus(@RequestParam String userId, @RequestParam String vlogId, @RequestParam Integer status) { @@ -168,7 +176,7 @@ public class VlogController extends BaseInfoProperties { return R.ok(); } - @PostMapping("changeToPrivate") + @PostMapping("/changeToPrivate") public R changeToPrivate(@RequestParam String userId, @RequestParam String vlogId) { vlogService.changeToPrivateOrPublic(userId, @@ -177,7 +185,7 @@ public class VlogController extends BaseInfoProperties { return R.ok(); } - @PostMapping("changeToPublic") + @PostMapping("/changeToPublic") public R changeToPublic(@RequestParam String userId, @RequestParam String vlogId) { vlogService.changeToPrivateOrPublic(userId, @@ -186,15 +194,13 @@ public class VlogController extends BaseInfoProperties { return R.ok(); } - - - - - @PostMapping("totalLikedCounts") + @PostMapping("/totalLikedCounts") public R totalLikedCounts(@RequestParam String vlogId) { return R.ok(vlogService.getVlogBeLikedCounts(vlogId)); } - - + @GetMapping("/push") + public R pushMember(@RequestParam int count,@RequestParam String tag) { + return R.ok(vlogPushService.pushVlogToMq(count,tag)); + } } diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/admin/CommentController.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/admin/CommentController.java index d21dd8fbe..b4ac60e4c 100644 --- a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/admin/CommentController.java +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/admin/CommentController.java @@ -33,7 +33,7 @@ import com.wzj.soopin.content.enums.MessageEnum; @Slf4j @Api(tags = "管理端-评论管理接口") -@RequestMapping("/comment") +@RequestMapping("/cms/comment") @RestController public class CommentController extends BaseController { diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/admin/VlogUploadController.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/admin/VlogUploadController.java index 62f264770..8ca0dd728 100644 --- a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/admin/VlogUploadController.java +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/controller/admin/VlogUploadController.java @@ -24,7 +24,7 @@ import java.util.*; @Slf4j @Api(tags = "管理端-视频") -@RequestMapping("/video") +@RequestMapping("/cms/video") @RestController public class VlogUploadController extends BaseInfoProperties { @@ -43,217 +43,6 @@ public class VlogUploadController extends BaseInfoProperties { @Autowired private IFansService fansService; - @ApiOperation("获取腾讯云点播视频列表") - @PostMapping("/list") - public R>> getVodList(@RequestBody VlogBO vlogBO) { - try { - // 验证分页参数 - if (vlogBO.getCurrent() < 1) { - vlogBO.setCurrent(1L); - } - if (vlogBO.getSize() < 1 || vlogBO.getSize() > 50) { - vlogBO.setSize(10L); - } - - // 如果有模糊查询条件、时间区间条件,或者有排序条件,则只查询数据库 - if (StringUtils.isNotBlank(vlogBO.getMobile()) - || StringUtils.isNotBlank(vlogBO.getNickname()) - || StringUtils.isNotBlank(vlogBO.getTitleQuery()) - || vlogBO.getStartTime() != null - || vlogBO.getEndTime() != null - || vlogBO.getStatus() != null - || StringUtils.isNotBlank(vlogBO.getColumn())) { - - // 创建分页对象 - Page> page = new Page<>(vlogBO.getCurrent(), vlogBO.getSize()); - - // 调用服务层方法进行查询 - IPage> result = vlogService.getVlogListByMobile(page, vlogBO); - return R.ok(result); - } - - // 否则查询腾讯云点播 - // 验证密钥配置 - String secretId = tencentCloudUtil.getSecretId(); - String secretKey = tencentCloudUtil.getSecretKey(); - if (secretId == null || secretId.isEmpty() || secretKey == null || secretKey.isEmpty()) { - log.error("腾讯云密钥未配置"); - return R.fail("腾讯云密钥未配置"); - } - - // 计算实际的offset - long offset = (long) ((vlogBO.getCurrent() - 1) * vlogBO.getSize()); - if (offset < 0) { - offset = 0; - } - - // 打印分页参数 - log.info("分页参数 - 当前页码: {}, 每页记录数: {}, 偏移量: {}", vlogBO.getCurrent(), vlogBO.getSize(), offset); - - // 创建请求对象 - SearchMediaRequest req = new SearchMediaRequest(); - - // 设置分页参数 - req.setOffset(offset); - req.setLimit(vlogBO.getSize()); - - // 设置排序 - SortBy sort = new SortBy(); - sort.setField("CreateTime"); - sort.setOrder("Desc"); - req.setSort(sort); - - // 调用腾讯云API获取视频列表 - log.info("开始调用腾讯云 SearchMedia API"); - SearchMediaResponse resp = vlogUploadService.searchMedia(req); - log.info("腾讯云 SearchMedia API 调用成功,总记录数: {}", resp.getTotalCount()); - - // 处理响应结果 - Map result = new HashMap<>(); - result.put("total", resp.getTotalCount()); - result.put("current", vlogBO.getCurrent()); - result.put("size", vlogBO.getSize()); - result.put("pages", (resp.getTotalCount() + vlogBO.getSize() - 1) / vlogBO.getSize()); // 总页数 - - // 批量获取所有视频的统计信息,避免重复查询 - Map fileIdToVlogIdMap = new HashMap<>(); - List allVlogIds = new ArrayList<>(); - - if (resp.getMediaInfoSet() != null) { - for (MediaInfo mediaInfo : resp.getMediaInfoSet()) { - String fileId = mediaInfo.getFileId(); - Map statistics = vlogService.getVlogStatistics(fileId); - String vlogId = (String) statistics.get("vlogId"); - if (StringUtils.isNotBlank(vlogId)) { - fileIdToVlogIdMap.put(fileId, vlogId); - allVlogIds.add(vlogId); - } - } - } - - // 批量获取所有点赞数据 - Map vlogLikeCounts = new HashMap<>(); - Map vlogCommentCounts = new HashMap<>(); - long totalLikeCounts = 0; - long totalCommentCounts = 0; - - if (!allVlogIds.isEmpty()) { - // 批量获取所有点赞键 - String likePattern = REDIS_USER_LIKE_VLOG + ":*"; - Collection allLikeKeys = RedisUtils.keys(likePattern); - - if (allLikeKeys != null && !allLikeKeys.isEmpty()) { - // 逐个获取点赞值(由于RedisUtils没有批量方法) - Map likeKeyValueMap = new HashMap<>(); - for (String likeKey : allLikeKeys) { - String likeValue = RedisUtils.getCacheObject(likeKey); - if ("1".equals(likeValue)) { - likeKeyValueMap.put(likeKey, likeValue); - } - } - - // 按vlogId分组统计点赞数 - for (String vlogId : allVlogIds) { - int likeCount = 0; - for (String likeKey : likeKeyValueMap.keySet()) { - if (likeKey.endsWith(":" + vlogId)) { - likeCount++; - } - } - vlogLikeCounts.put(vlogId, likeCount); - totalLikeCounts += likeCount; - } - } - - // 批量获取所有评论数 - for (String vlogId : allVlogIds) { - String commentKey = REDIS_VLOG_COMMENT_COUNTS + ":" + vlogId; - String commentCountsStr = RedisUtils.getCacheObject(commentKey); - int commentCount = 0; - if (StringUtils.isNotBlank(commentCountsStr)) { - try { - commentCount = Integer.valueOf(commentCountsStr); - } catch (NumberFormatException e) { - log.warn("Redis中视频{}的评论数格式错误: {}", vlogId, commentCountsStr); - } - } - vlogCommentCounts.put(vlogId, commentCount); - totalCommentCounts += commentCount; - } - } - - // 将总的点赞数和评论数添加到结果中 - result.put("totalLikeCounts", totalLikeCounts); - result.put("totalCommentCounts", totalCommentCounts); - - List> mediaList = new ArrayList<>(); - if (resp.getMediaInfoSet() != null) { - for (MediaInfo mediaInfo : resp.getMediaInfoSet()) { - Map mediaMap = new HashMap<>(); - // 基础信息 - MediaBasicInfo basicInfo = mediaInfo.getBasicInfo(); - mediaMap.put("fileId", mediaInfo.getFileId()); - mediaMap.put("name", basicInfo.getName()); - mediaMap.put("description", basicInfo.getDescription()); - mediaMap.put("createTime", basicInfo.getCreateTime()); - mediaMap.put("updateTime", basicInfo.getUpdateTime()); - mediaMap.put("expireTime", basicInfo.getExpireTime()); - mediaMap.put("classId", basicInfo.getClassId()); - mediaMap.put("className", basicInfo.getClassName()); - mediaMap.put("classPath", basicInfo.getClassPath()); - mediaMap.put("coverUrl", basicInfo.getCoverUrl()); - mediaMap.put("type", basicInfo.getType()); - mediaMap.put("mediaUrl", basicInfo.getMediaUrl()); - mediaMap.put("status", basicInfo.getStatus()); - mediaMap.put("storageRegion", basicInfo.getStorageRegion()); - mediaMap.put("category", basicInfo.getCategory()); - mediaMap.put("storageClass", basicInfo.getStorageClass()); - mediaMap.put("tagSet", basicInfo.getTagSet()); - - // 获取视频统计信息 - Map statistics = vlogService.getVlogStatistics(mediaInfo.getFileId()); - mediaMap.putAll(statistics); - - // 从statistics中获取vlogId,用于Redis查询 - String vlogId = (String) statistics.get("vlogId"); - log.info("处理视频 fileId: {}, vlogId: {}", mediaInfo.getFileId(), vlogId); - - // 使用批量获取的数据,避免重复查询Redis - int videoLikeCounts = 0; - int videoCommentCounts = 0; - if (StringUtils.isNotBlank(vlogId)) { - videoLikeCounts = vlogLikeCounts.getOrDefault(vlogId, 0); - videoCommentCounts = vlogCommentCounts.getOrDefault(vlogId, 0); - } - mediaMap.put("likeCounts", videoLikeCounts); - mediaMap.put("commentCounts", videoCommentCounts); - - // 获取视频上传者信息 - Map uploaderInfo = vlogService.getVlogUploaderInfo(mediaInfo.getFileId()); - if (uploaderInfo != null) { - mediaMap.put("nickname", uploaderInfo.get("name")); - mediaMap.put("mobile", uploaderInfo.get("phone")); - } - - mediaList.add(mediaMap); - } - } - result.put("records", mediaList); - - // 构建Page对象 - Page> page = new Page<>(); - page.setCurrent(vlogBO.getCurrent()); - page.setSize(vlogBO.getSize()); - page.setTotal(resp.getTotalCount()); - page.setRecords(mediaList); - - return R.ok(page); - } catch (Exception e) { - log.error("获取视频列表失败", e); - return R.fail("获取视频列表失败: " + e.getMessage()); - } - } - /** * 视频审核接口 */ diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/domain/base/BaseInfoProperties.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/domain/base/BaseInfoProperties.java index 322c0dc02..88c4ff49c 100644 --- a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/domain/base/BaseInfoProperties.java +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/domain/base/BaseInfoProperties.java @@ -4,13 +4,14 @@ package com.wzj.soopin.content.domain.base; import com.wzj.soopin.content.utils.PagedGridResult; import com.wzj.soopin.content.utils.RedisOperator; +import org.dromara.common.redis.redis.RedisCache; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; public class BaseInfoProperties { - public RedisOperator redis; + public RedisCache redis; public static final Integer COMMON_START_PAGE = 1; public static final Integer COMMON_START_PAGE_ZERO = 0; diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/domain/bo/VlogBO.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/domain/bo/VlogBO.java index 17f0109d7..96c214e07 100644 --- a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/domain/bo/VlogBO.java +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/domain/bo/VlogBO.java @@ -12,6 +12,7 @@ import org.apache.commons.lang3.StringUtils; import org.dromara.common.core.domain.BaseBO; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.List; @Data @AllArgsConstructor @@ -64,6 +65,12 @@ public class VlogBO extends BaseBO { @ApiModelProperty("创建时间范围-结束时间") private LocalDateTime endTime; + private List blockVd; + + private List blockUser; + + private List ids; + @Override public LambdaQueryWrapper toWrapper() { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/event/VlogPushEventListener.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/event/VlogPushEventListener.java new file mode 100644 index 000000000..cc3bb32fa --- /dev/null +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/event/VlogPushEventListener.java @@ -0,0 +1,20 @@ +package com.wzj.soopin.content.event; + +import com.wzj.soopin.content.service.IVlogPushService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.mq.domain.VlogPushEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class VlogPushEventListener { + private final IVlogPushService vlogPushService; + @EventListener + public void handleNeedPushVlogEvent(VlogPushEvent event) { + log.info("收到需要推送的视频事件,用户ID:{}", event.getUserIdStr()); + vlogPushService.pushVlogToMq(100, event.getTag()); + } +} diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/mapper/VlogMapper.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/mapper/VlogMapper.java index a7c305563..d77e5fc9a 100644 --- a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/mapper/VlogMapper.java +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/mapper/VlogMapper.java @@ -168,4 +168,8 @@ public interface VlogMapper extends BaseMapper { "ORDER BY RAND() " + "LIMIT #{limit}") List> selectRandomVlogs(@Param("limit") int limit); + + + IPage getVlogForUser(Page page, @Param("memberId") Long memberId); + int readVlog(@Param("memberId") Long memberId, @Param("vlogId") String vlogId); } diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/mapper/VlogMapperCustom.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/mapper/VlogMapperCustom.java index 9674ae69b..7bb930f0d 100644 --- a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/mapper/VlogMapperCustom.java +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/mapper/VlogMapperCustom.java @@ -2,6 +2,7 @@ package com.wzj.soopin.content.mapper; import com.baomidou.mybatisplus.annotation.InterceptorIgnore; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.wzj.soopin.content.domain.bo.VlogBO; import com.wzj.soopin.content.domain.po.Vlog; import com.wzj.soopin.content.domain.vo.IndexVlogVO; import com.wzj.soopin.content.domain.vo.VlogerVO; @@ -16,9 +17,9 @@ import java.util.Map; @InterceptorIgnore(tenantLine = "true") public interface VlogMapperCustom extends BaseMapperPlus { - List getIndexVlogList(@Param("paramMap") Map map); + List getIndexVlogList(@Param("bo") VlogBO bo); - Page getIndexVlogList(@Param("paramMap") Map map, Page page); + Page getIndexVlogList(@Param("bo") VlogBO bo, Page page); List getVlogDetailById(@Param("paramMap") Map map); diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/IVlogPullService.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/IVlogPullService.java new file mode 100644 index 000000000..9dfe12b19 --- /dev/null +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/IVlogPullService.java @@ -0,0 +1,21 @@ +package com.wzj.soopin.content.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.tencentcloudapi.vod.v20180717.models.SearchMediaRequest; +import com.tencentcloudapi.vod.v20180717.models.SearchMediaResponse; +import com.wzj.soopin.content.domain.bo.VlogBO; +import com.wzj.soopin.content.domain.vo.IndexVlogVO; + +public interface IVlogPullService { + + /** + * 从视频库中拉取视频 + */ + Page page(Page page); + + + void pullVlog(VlogBO vlogBO); + + + +} diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/IVlogPushService.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/IVlogPushService.java new file mode 100644 index 000000000..194c91bf2 --- /dev/null +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/IVlogPushService.java @@ -0,0 +1,12 @@ +package com.wzj.soopin.content.service; + +/** + * 视频推送服务 + * @author wqx + * @date 2023-12-20 + */ +public interface IVlogPushService { + boolean cacheTopLikedVlogs(int limit) ; + + boolean pushVlogToMq(int count,String tag); +} diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/VlogService.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/VlogService.java index 6ff642ccc..8d3e8602f 100644 --- a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/VlogService.java +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/VlogService.java @@ -2,6 +2,7 @@ package com.wzj.soopin.content.service; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; import com.wzj.soopin.content.domain.bo.*; import com.wzj.soopin.content.domain.po.Vlog; import com.wzj.soopin.content.domain.vo.IndexVlogVO; @@ -10,7 +11,7 @@ import com.wzj.soopin.content.utils.PagedGridResult; import java.util.List; import java.util.Map; -public interface VlogService { +public interface VlogService extends IService { /** * 修改视频首帧图 */ @@ -115,10 +116,6 @@ public interface VlogService { */ List> getLikedUsers(String vlogId); - /** - * 获取视频点赞数 - */ - int getLikeCounts(String vlogId); /** * 获取视频上传者信息 @@ -135,11 +132,6 @@ public interface VlogService { */ IPage> getVlogListByMobile(Page> page, VlogBO vlogBO); - /** - * 查询点赞最多的视频列表并存储到Redis - * @param limit 查询数量限制 - */ - void cacheTopLikedVlogs(int limit); /** * 随机查询视频列表 @@ -148,8 +140,15 @@ public interface VlogService { */ List> getRandomVlogs(int limit); + IPage getVlogForUser(Page page, Long memberId); + int readVlog(Long memberId, String vlogId); + + List getIndexVlogList(VlogBO bo); + Page getIndexVlogList(VlogBO bo,Page page); + /** * 首页搜索/搜索的vlog列表 */ Page getIndexSearchVlogList(SearchBO bo, Page page); + } diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/VlogUploadService.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/VlogUploadService.java index 4878fea2d..06a0951aa 100644 --- a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/VlogUploadService.java +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/VlogUploadService.java @@ -26,13 +26,6 @@ public interface VlogUploadService { // */ // Map getTaskStatus(String taskId) throws Exception; - /** - * 搜索媒体文件 - * - * @param req 搜索请求 - * @return 搜索响应 - */ - SearchMediaResponse searchMedia(SearchMediaRequest req); // 新增:视频审核 void auditVlog(String vlogId, Integer status, String reason); diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/impl/MsgServiceImpl.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/impl/MsgServiceImpl.java index dbac54880..7713e20b2 100644 --- a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/impl/MsgServiceImpl.java +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/impl/MsgServiceImpl.java @@ -80,7 +80,7 @@ public class MsgServiceImpl extends BaseInfoProperties implements MsgService { map = new HashMap<>(); } - String relationship = redis.get(REDIS_FANS_AND_VLOGGER_RELATIONSHIP + ":" + msg.getToUserId() + ":" + msg.getFromUserId()); + String relationship = redis.getCacheObject(REDIS_FANS_AND_VLOGGER_RELATIONSHIP + ":" + msg.getToUserId() + ":" + msg.getFromUserId()); if (StringUtils.isNotBlank(relationship) && relationship.equalsIgnoreCase("1")) { map.put("isFriend", true); } else { diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/impl/VlogPullServiceImpl.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/impl/VlogPullServiceImpl.java new file mode 100644 index 000000000..6b5ad4f17 --- /dev/null +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/impl/VlogPullServiceImpl.java @@ -0,0 +1,393 @@ +package com.wzj.soopin.content.service.impl; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.tencentcloudapi.common.exception.TencentCloudSDKException; +import com.tencentcloudapi.vod.v20180717.VodClient; +import com.tencentcloudapi.vod.v20180717.models.*; +import com.wzj.soopin.content.domain.bo.VlogBO; +import com.wzj.soopin.content.service.IVlogPullService; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.wzj.soopin.content.domain.vo.IndexVlogVO; +import com.wzj.soopin.content.service.VlogService; +import com.wzj.soopin.content.utils.TencentCloudUtil; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.rocketmq.common.message.MessageExt; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.domain.model.LoginUser; +import org.dromara.common.json.utils.JsonUtils; +import org.dromara.common.mq.domain.MQMessage; +import org.dromara.common.mq.domain.VlogPushEvent; +import org.dromara.common.mq.utils.UserRocketMQConsumerManager; +import org.dromara.common.redis.utils.RedisUtils; +import org.dromara.common.satoken.utils.LoginHelper; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; + +import static com.wzj.soopin.content.domain.base.BaseInfoProperties.REDIS_USER_LIKE_VLOG; +import static com.wzj.soopin.content.domain.base.BaseInfoProperties.REDIS_VLOG_COMMENT_COUNTS; +import static org.dromara.common.core.constant.GlobalConstants.HOT_VLOG_TAG; + +@Service +@AllArgsConstructor +@Slf4j +public class VlogPullServiceImpl implements IVlogPullService { + + private final UserRocketMQConsumerManager mqConsumerManager; + private final VlogService vlogService; + private final ApplicationEventPublisher eventPublisher; + private final TencentCloudUtil tencentCloudUtil; + + @Override + public Page page(Page page) { + + List messageExts = new ArrayList<>(); + + try { + LoginUser loginUser = LoginHelper.getLoginUser(); + if (loginUser == null) { + messageExts = mqConsumerManager.pullUserMessages(HOT_VLOG_TAG, (int) page.getSize()); + } else { + messageExts = mqConsumerManager.pullUserMessages(loginUser.getUserId() + "", (int) page.getSize()); + + } + List ids = messageExts.stream().map(messageExt -> { + MQMessage mqMessage = JsonUtils.parseObject(messageExt.getBody(), MQMessage.class); + return mqMessage.getData().toString(); + }).collect(Collectors.toList()); + VlogBO vlogBO = new VlogBO(); + if (ids.size() > 0) { + + vlogBO.setIds(ids); + List vlogVOList = vlogService.getIndexVlogList(vlogBO); + return page.setRecords(vlogVOList); + } else { + if (loginUser != null) { + eventPublisher.publishEvent(new VlogPushEvent(loginUser.getUserId() + "")); + + } else { + eventPublisher.publishEvent(new VlogPushEvent(HOT_VLOG_TAG)); + } + //发出事件 + //先临时取10条数据 + Page indexVlogVOPage = vlogService.getIndexVlogList(vlogBO, page); + //直接获取数据库数据,并要求push类推送数据 + return indexVlogVOPage; + } + + } catch (Exception e) { + log.error("拉取视频失败", e); + } + return page; + } + + + @Override + public void pullVlog(VlogBO vlogBO) { + try { + // 验证分页参数 + if (vlogBO.getCurrent() < 1) { + vlogBO.setCurrent(1L); + } + if (vlogBO.getSize() < 1 || vlogBO.getSize() > 50) { + vlogBO.setSize(10L); + } + + // 如果有模糊查询条件、时间区间条件,或者有排序条件,则只查询数据库 + if (StringUtils.isNotBlank(vlogBO.getMobile()) + || StringUtils.isNotBlank(vlogBO.getNickname()) + || StringUtils.isNotBlank(vlogBO.getTitleQuery()) + || vlogBO.getStartTime() != null + || vlogBO.getEndTime() != null + || vlogBO.getStatus() != null + || StringUtils.isNotBlank(vlogBO.getColumn())) { + + // 创建分页对象 + Page> page = new Page<>(vlogBO.getCurrent(), vlogBO.getSize()); + + // 调用服务层方法进行查询 + IPage> result = vlogService.getVlogListByMobile(page, vlogBO); + + return; + } + + // 否则查询腾讯云点播 + // 验证密钥配置 + String secretId = tencentCloudUtil.getSecretId(); + String secretKey = tencentCloudUtil.getSecretKey(); + if (secretId == null || secretId.isEmpty() || secretKey == null || secretKey.isEmpty()) { + log.error("腾讯云密钥未配置"); + return; + } + + // 计算实际的offset + long offset = (long) ((vlogBO.getCurrent() - 1) * vlogBO.getSize()); + if (offset < 0) { + offset = 0; + } + + // 打印分页参数 + log.info("分页参数 - 当前页码: {}, 每页记录数: {}, 偏移量: {}", vlogBO.getCurrent(), vlogBO.getSize(), offset); + + // 创建请求对象 + SearchMediaRequest req = new SearchMediaRequest(); + + // 设置分页参数 + req.setOffset(offset); + req.setLimit(vlogBO.getSize()); + + // 设置排序 + SortBy sort = new SortBy(); + sort.setField("CreateTime"); + sort.setOrder("Desc"); + req.setSort(sort); + + // 调用腾讯云API获取视频列表 + log.info("开始调用腾讯云 SearchMedia API"); + SearchMediaResponse resp = searchMedia(req); + log.info("腾讯云 SearchMedia API 调用成功,总记录数: {}", resp.getTotalCount()); + + // 处理响应结果 + Map result = new HashMap<>(); + result.put("total", resp.getTotalCount()); + result.put("current", vlogBO.getCurrent()); + result.put("size", vlogBO.getSize()); + result.put("pages", (resp.getTotalCount() + vlogBO.getSize() - 1) / vlogBO.getSize()); // 总页数 + + // 批量获取所有视频的统计信息,避免重复查询 + Map fileIdToVlogIdMap = new HashMap<>(); + List allVlogIds = new ArrayList<>(); + + if (resp.getMediaInfoSet() != null) { + for (MediaInfo mediaInfo : resp.getMediaInfoSet()) { + String fileId = mediaInfo.getFileId(); + Map statistics = vlogService.getVlogStatistics(fileId); + String vlogId = (String) statistics.get("vlogId"); + if (StringUtils.isNotBlank(vlogId)) { + fileIdToVlogIdMap.put(fileId, vlogId); + allVlogIds.add(vlogId); + } + } + } + + // 批量获取所有点赞数据 + Map vlogLikeCounts = new HashMap<>(); + Map vlogCommentCounts = new HashMap<>(); + long totalLikeCounts = 0; + long totalCommentCounts = 0; + + if (!allVlogIds.isEmpty()) { + // 批量获取所有点赞键 + String likePattern = REDIS_USER_LIKE_VLOG + ":*"; + Collection allLikeKeys = RedisUtils.keys(likePattern); + + if (allLikeKeys != null && !allLikeKeys.isEmpty()) { + // 逐个获取点赞值(由于RedisUtils没有批量方法) + Map likeKeyValueMap = new HashMap<>(); + for (String likeKey : allLikeKeys) { + String likeValue = RedisUtils.getCacheObject(likeKey); + if ("1".equals(likeValue)) { + likeKeyValueMap.put(likeKey, likeValue); + } + } + + // 按vlogId分组统计点赞数 + for (String vlogId : allVlogIds) { + int likeCount = 0; + for (String likeKey : likeKeyValueMap.keySet()) { + if (likeKey.endsWith(":" + vlogId)) { + likeCount++; + } + } + vlogLikeCounts.put(vlogId, likeCount); + totalLikeCounts += likeCount; + } + } + + // 批量获取所有评论数 + for (String vlogId : allVlogIds) { + String commentKey = REDIS_VLOG_COMMENT_COUNTS + ":" + vlogId; + String commentCountsStr = RedisUtils.getCacheObject(commentKey); + int commentCount = 0; + if (StringUtils.isNotBlank(commentCountsStr)) { + try { + commentCount = Integer.valueOf(commentCountsStr); + } catch (NumberFormatException e) { + log.warn("Redis中视频{}的评论数格式错误: {}", vlogId, commentCountsStr); + } + } + vlogCommentCounts.put(vlogId, commentCount); + totalCommentCounts += commentCount; + } + } + + // 将总的点赞数和评论数添加到结果中 + result.put("totalLikeCounts", totalLikeCounts); + result.put("totalCommentCounts", totalCommentCounts); + + List> mediaList = new ArrayList<>(); + if (resp.getMediaInfoSet() != null) { + for (MediaInfo mediaInfo : resp.getMediaInfoSet()) { + Map mediaMap = new HashMap<>(); + // 基础信息 + MediaBasicInfo basicInfo = mediaInfo.getBasicInfo(); + mediaMap.put("fileId", mediaInfo.getFileId()); + mediaMap.put("name", basicInfo.getName()); + mediaMap.put("description", basicInfo.getDescription()); + mediaMap.put("createTime", basicInfo.getCreateTime()); + mediaMap.put("updateTime", basicInfo.getUpdateTime()); + mediaMap.put("expireTime", basicInfo.getExpireTime()); + mediaMap.put("classId", basicInfo.getClassId()); + mediaMap.put("className", basicInfo.getClassName()); + mediaMap.put("classPath", basicInfo.getClassPath()); + mediaMap.put("coverUrl", basicInfo.getCoverUrl()); + mediaMap.put("type", basicInfo.getType()); + mediaMap.put("mediaUrl", basicInfo.getMediaUrl()); + mediaMap.put("status", basicInfo.getStatus()); + mediaMap.put("storageRegion", basicInfo.getStorageRegion()); + mediaMap.put("category", basicInfo.getCategory()); + mediaMap.put("storageClass", basicInfo.getStorageClass()); + mediaMap.put("tagSet", basicInfo.getTagSet()); + + // 获取视频统计信息 + Map statistics = vlogService.getVlogStatistics(mediaInfo.getFileId()); + mediaMap.putAll(statistics); + + // 从statistics中获取vlogId,用于Redis查询 + String vlogId = (String) statistics.get("vlogId"); + log.info("处理视频 fileId: {}, vlogId: {}", mediaInfo.getFileId(), vlogId); + + // 使用批量获取的数据,避免重复查询Redis + int videoLikeCounts = 0; + int videoCommentCounts = 0; + if (StringUtils.isNotBlank(vlogId)) { + videoLikeCounts = vlogLikeCounts.getOrDefault(vlogId, 0); + videoCommentCounts = vlogCommentCounts.getOrDefault(vlogId, 0); + } + mediaMap.put("likeCounts", videoLikeCounts); + mediaMap.put("commentCounts", videoCommentCounts); + + // 获取视频上传者信息 + Map uploaderInfo = vlogService.getVlogUploaderInfo(mediaInfo.getFileId()); + if (uploaderInfo != null) { + mediaMap.put("nickname", uploaderInfo.get("name")); + mediaMap.put("mobile", uploaderInfo.get("phone")); + } + + mediaList.add(mediaMap); + } + } + result.put("records", mediaList); + }catch (Exception e){ + log.error("拉取视频失败: {}", e.getMessage(), e); + } + } + + public SearchMediaResponse searchMedia(SearchMediaRequest req) { + try { + // 获取VOD客户端实例 + VodClient client = tencentCloudUtil.getVodClient(); + + // 设置默认值 + if (req.getOffset() == null) { + req.setOffset(0L); + } + if (req.getLimit() == null) { + req.setLimit(10L); + } + + // 添加日志,打印请求参数 +// log.info("SearchMediaRequest 请求参数: {}", req.toJsonString()); + + // 添加调试信息 + log.info("请求时间戳: {}", System.currentTimeMillis()); + log.info("请求参数详情 - Offset: {}, Limit: {}, Sort: {}, Filters: {}", + req.getOffset(), + req.getLimit(), +// req.getSort() != null ? req.getSort().toJsonString() : "null", + req.getFilters() != null ? String.join(",", req.getFilters()) : "null"); + + // 验证请求参数 + validateSearchRequest(req); + + SearchMediaResponse response = client.SearchMedia(req); + log.info("SearchMedia API 调用成功,返回结果数: {}", response.getTotalCount()); + return response; + } catch (TencentCloudSDKException e) { + log.error("搜索媒体文件失败: {}", e.getMessage(), e); + // 添加更详细的错误信息 + if (e.getMessage().contains("signature")) { + log.error("签名验证失败,请检查:\n1. 密钥是否正确\n2. 服务器时间是否准确\n3. 时区设置是否正确"); + } else if (e.getMessage().contains("InvalidParameterValue")) { + log.error("参数值错误,请检查:\n1. 分页参数 Offset 和 Limit 是否有效\n2. 搜索条件是否合法\n3. 排序参数是否正确"); + } else if (e.getMessage().contains("UnauthorizedOperation")) { + log.error("未授权操作,请检查:\n1. 密钥是否有权限\n2. 应用 ID 是否正确\n3. 是否开通了点播服务"); + } + throw new RuntimeException("搜索媒体文件失败: " + e.getMessage()); + } + } + /** + * 验证搜索请求参数 + */ + private void validateSearchRequest(SearchMediaRequest req) { + // 验证分页参数 + if (req.getOffset() < 0) { + throw new IllegalArgumentException("Offset 不能小于 0"); + } + if (req.getLimit() < 1 || req.getLimit() > 50) { + throw new IllegalArgumentException("Limit 必须在 1-50 之间"); + } + if (req.getOffset() + req.getLimit() > 5000) { + throw new IllegalArgumentException("Offset + Limit 不能超过 5000"); + } + + // 验证搜索条件 + if (req.getFileIds() != null && req.getFileIds().length > 10) { + throw new IllegalArgumentException("FileIds 数组长度不能超过 10"); + } + if (req.getNames() != null && req.getNames().length > 10) { + throw new IllegalArgumentException("Names 数组长度不能超过 10"); + } + if (req.getNamePrefixes() != null && req.getNamePrefixes().length > 10) { + throw new IllegalArgumentException("NamePrefixes 数组长度不能超过 10"); + } + if (req.getDescriptions() != null && req.getDescriptions().length > 10) { + throw new IllegalArgumentException("Descriptions 数组长度不能超过 10"); + } + if (req.getClassIds() != null && req.getClassIds().length > 10) { + throw new IllegalArgumentException("ClassIds 数组长度不能超过 10"); + } + if (req.getTags() != null && req.getTags().length > 16) { + throw new IllegalArgumentException("Tags 数组长度不能超过 16"); + } + if (req.getSourceTypes() != null && req.getSourceTypes().length > 10) { + throw new IllegalArgumentException("SourceTypes 数组长度不能超过 10"); + } + if (req.getStreamIds() != null && req.getStreamIds().length > 10) { + throw new IllegalArgumentException("StreamIds 数组长度不能超过 10"); + } + if (req.getStorageRegions() != null && req.getStorageRegions().length > 20) { + throw new IllegalArgumentException("StorageRegions 数组长度不能超过 20"); + } +// if (req.getMediaTypes() != null && req.getMediaTypes().length > 10) { +// throw new IllegalArgumentException("MediaTypes 数组长度不能超过 10"); +// } +// if (req.getStatus() != null && req.getStatus().length > 10) { +// throw new IllegalArgumentException("Status 数组长度不能超过 10"); +// } +// if (req.getReviewResults() != null && req.getReviewResults().length > 10) { +// throw new IllegalArgumentException("ReviewResults 数组长度不能超过 10"); +// } +// if (req.getTrtcSdkAppIds() != null && req.getTrtcSdkAppIds().length > 10) { +// throw new IllegalArgumentException("TrtcSdkAppIds 数组长度不能超过 10"); +// } +// if (req.getTrtcRoomIds() != null && req.getTrtcRoomIds().length > 10) { +// throw new IllegalArgumentException("TrtcRoomIds 数组长度不能超过 10"); +// } + } +} diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/impl/VlogPushServiceImpl.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/impl/VlogPushServiceImpl.java new file mode 100644 index 000000000..f8043000e --- /dev/null +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/impl/VlogPushServiceImpl.java @@ -0,0 +1,112 @@ +package com.wzj.soopin.content.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.wzj.soopin.content.domain.po.Vlog; +import com.wzj.soopin.content.service.IVlogPushService; +import com.wzj.soopin.content.service.VlogService; +import com.wzj.soopin.member.domain.po.Member; +import com.wzj.soopin.member.service.IMemberService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.mq.domain.MQMessage; +import org.dromara.common.mq.utils.MqUtil; +import org.dromara.common.redis.redis.RedisCache; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.wzj.soopin.content.domain.base.BaseInfoProperties.REDIS_VLOG_BE_LIKED_COUNTS; +import static org.dromara.common.core.constant.GlobalConstants.HOT_VLOG_TAG; + +@Service +@AllArgsConstructor +@Slf4j +public class VlogPushServiceImpl implements IVlogPushService { + + private final RedisCache redisCache; + private final VlogService vlogService; + + private final IMemberService memberService; + + @Override + public boolean cacheTopLikedVlogs(int limit ) { + try { + + } catch (Exception e) { + log.error("缓存点赞最多视频到Redis失败", e); + } + return true; + } + + @Override + public boolean pushVlogToMq(int count,String tag ) { + + if(tag.equals(HOT_VLOG_TAG)){ + // 1. 获取 ZSet 中所有成员(按分数升序排列) + // 这里我们只关心成员(member),不关心分数(score),所以使用 range(key, start, end) + log.info("开始查询点赞最多的{}条视频", count); + Set rankSet = redisCache.zSetRange(REDIS_VLOG_BE_LIKED_COUNTS, 0, count); + if (rankSet == null ||rankSet.isEmpty()) { + return false; + } + if(rankSet!=null&&rankSet.size() vlogPage = new Page<>(1, count-rankSet.size()); + vlogPage = vlogService.page(vlogPage, new LambdaQueryWrapper() + .orderByDesc(Vlog::getLikeCounts)); + Set vlogIds = vlogPage.getRecords().stream().map(vlog -> vlog.getId()).collect(Collectors.toSet()); + rankSet.addAll(vlogIds); + } + // 3. 将 Set 转换为 List 以便打乱顺序 + List vlogList = new ArrayList<>(rankSet); + + // 4. 使用 Collections.shuffle 打乱 List 的顺序 + Collections.shuffle(vlogList); + vlogList.stream().forEach(vlogId -> { + + //将数据发送的mq的热点数据队列 + MQMessage message = MQMessage.builder() + .topic("MEMBER_VLOG_MSG") + .tag(HOT_VLOG_TAG) + .messageType("json") + .data(vlogId) + .source("vlog_service") + .sendTime(LocalDateTime.now()) + .build(); + MqUtil.sendMessage("MEMBER_VLOG_MSG",HOT_VLOG_TAG, message); + }); + + }else{ + Member member = memberService.getById(tag); + String cityCode = member.getCity(); + IPage vlogPage=vlogService.getVlogForUser(new Page<>(1, 100), Long.parseLong(tag)); + if(CollectionUtils.isEmpty(vlogPage.getRecords())){ + return false; + } + vlogPage.getRecords().stream().forEach(vlogId -> { + //将数据发送的mq的热点数据队列 + MQMessage message = MQMessage.builder() + .topic("MEMBER_VLOG_MSG") + .tag(tag) + .messageType("json") + .data(vlogId) + .source("vlog_service") + .sendTime(LocalDateTime.now()) + .build(); + MqUtil.sendMessage("MEMBER_VLOG_MSG"+":"+tag, message); + }); + } + + + return true; + } + +} diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/impl/VlogServiceImpl.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/impl/VlogServiceImpl.java index 9b69ee8a0..6a54c9ef7 100644 --- a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/impl/VlogServiceImpl.java +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/impl/VlogServiceImpl.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -13,6 +14,7 @@ import com.wzj.soopin.content.domain.bo.*; import com.wzj.soopin.content.domain.po.MyLikedVlog; import com.wzj.soopin.content.domain.po.Vlog; import com.wzj.soopin.content.domain.po.Users; +import com.wzj.soopin.content.domain.vo.VlogerVO; import com.wzj.soopin.member.domain.po.Member; import com.wzj.soopin.content.domain.vo.IndexVlogVO; import com.wzj.soopin.content.enums.YesOrNo; @@ -32,12 +34,22 @@ import com.wzj.soopin.content.utils.TencentCloudUtil; import com.wzj.soopin.member.service.IFansService; import com.wzj.soopin.content.convert.VlogConvert; import com.wzj.soopin.member.service.IMemberService; +import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; +import org.dromara.common.core.constant.GlobalConstants; import org.dromara.common.core.domain.model.LoginUser; import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.mq.domain.MQMessage; +import org.dromara.common.mq.enums.MQMessageType; +import org.dromara.common.mq.enums.MessageActionEnum; +import org.dromara.common.mq.utils.MqUtil; +import org.dromara.common.mq.utils.UserRocketMQConsumerManager; +import org.dromara.common.redis.redis.RedisCache; import org.dromara.common.satoken.utils.LoginHelper; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.dromara.common.core.utils.MapstructUtils; @@ -49,36 +61,27 @@ import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; import java.util.ArrayList; + import lombok.extern.slf4j.Slf4j; +import static com.wzj.soopin.content.domain.base.BaseInfoProperties.*; + @Slf4j @Service -public class VlogServiceImpl extends BaseInfoProperties implements VlogService { +@AllArgsConstructor +public class VlogServiceImpl extends ServiceImpl implements VlogService { + + private final VlogMapper vlogMapper; + private final VlogMapperCustom vlogMapperCustom; + private final MyLikedVlogMapper myLikedVlogMapper; + private final IFansService fansService; + private final MsgService msgService; + private final Sid sid; + private final CommentMapper commentMapper; + private final MemberMapper memberMapper; + private final RedisCache cache; + private final RedisCache redisCache; - @Autowired - private VlogMapper vlogMapper; - @Autowired - private VlogMapperCustom vlogMapperCustom; - @Autowired - private MyLikedVlogMapper myLikedVlogMapper; - @Autowired - private IFansService fansService; - @Autowired - private MsgService msgService; - @Autowired - private Sid sid; - @Autowired - private CommentMapper commentMapper; - @Autowired - private RedisOperator redis; - @Autowired - private TencentCloudUtil tencentCloudUtil; - @Autowired - private UsersMapper usersMapper; - @Autowired - private MemberMapper memberMapper; - @Autowired - private IMemberService memberService; @Transactional(rollbackFor = Exception.class) @Override public void updateVlogStatus(String fileId, Integer status, String reason) { @@ -138,157 +141,40 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService { vlogMapper.insert(vlog); } - @Override - public Page getIndexVlogList(IndexListBO bo, Page page) { - String userId = bo.getMemberId(); - String search = bo.getSearch(); - String cityCode = bo.getCityCode(); - String status = bo.getStatus(); - int current = (int) page.getCurrent(); - int size = (int) page.getSize(); - Page pageParam = new Page<>(current, size); - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - if (StringUtils.isNotBlank(search)) { - queryWrapper.like(Vlog::getTitle, search); - } - if (StringUtils.isNotBlank(cityCode)) { - queryWrapper.eq(Vlog::getCityCode, cityCode); - } - if (StringUtils.isNotBlank(status)) { - queryWrapper.eq(Vlog::getStatus, status); - } - // 去掉黑名单的视频 - if (StringUtils.isNotBlank(userId)) { - String redisKey = REDIS_VIDEO_BLOCK + ":" + userId; - if (redis.keyIsExist(redisKey)) { - List blockVdList = new ArrayList<>(); - ObjectMapper objectMapper = new ObjectMapper(); - List reports = redis.lrange(redisKey, 0, -1); - for (String report : reports) { - try { - Map reportMap = objectMapper.readValue(report, new TypeReference>() {}); - String vlogId = (String) reportMap.get("vlogId"); - blockVdList.add(vlogId); - } catch (JsonProcessingException e) { - e.printStackTrace(); - } - } - if (!blockVdList.isEmpty()) { - queryWrapper.notIn(Vlog::getId, blockVdList); - } - } - } - // 去掉黑名单的用户 - if (StringUtils.isNotBlank(userId)) { - String redisKey = REDIS_USER_BLOCK + ":" + userId; - if (redis.keyIsExist(redisKey)) { - List blockUserList = new ArrayList<>(); - ObjectMapper objectMapper = new ObjectMapper(); - List reports = redis.lrange(redisKey, 0, -1); - for (String report : reports) { - try { - Map reportMap = objectMapper.readValue(report, new TypeReference>() {}); - String memberId = (String) reportMap.get("MemberId"); - blockUserList.add(memberId); - } catch (JsonProcessingException e) { - log.error(e.getMessage()); - } - } - if (!blockUserList.isEmpty()) { - queryWrapper.notIn(Vlog::getMemberId, blockUserList); - } - } - } - // 去掉已读的视频 - if (StringUtils.isNotBlank(userId)) { - String redisKey = REDIS_USER_READ_VLOG + ":" + userId; - if (redis.keyIsExist(redisKey)) { - List readVlogList = new ArrayList<>(); - ObjectMapper objectMapper = new ObjectMapper(); - List reports = redis.lrange(redisKey, 0, -1); - for (String report : reports) { - try { - Map reportMap = objectMapper.readValue(report, new TypeReference>() {}); - String memberId = (String) reportMap.get("MemberId"); - readVlogList.add(memberId); - } catch (JsonProcessingException e) { - log.error(e.getMessage()); - } - } - if (!readVlogList.isEmpty()) { - queryWrapper.notIn(Vlog::getMemberId, readVlogList); - } - } - } - Page vlogPage = vlogMapper.selectPage(pageParam, queryWrapper); - List vlogList = vlogPage.getRecords(); - List voList = vlogList.stream().map(v -> { - IndexVlogVO vo = MapstructUtils.convert(v, IndexVlogVO.class); - if (StringUtils.isNotBlank(userId)) { - vo.setDoIFollowVloger(fansService.queryDoIFollowVloger(userId, v.getMemberId())); - vo.setDoILikeThisVlog(doILikeVlog(userId, v.getId())); - } - vo.setLikeCounts(getVlogBeLikedCounts(v.getId())); - vo.setCommentsCounts(getVlogComment(v.getId())); - MemberVO m =memberService.getMemberInfo(vo.getMemberId()); - if (m != null) { - vo.setAvatar(m.getAvatar()); - vo.setNickname(m.getNickname()); - } - return vo; - }).collect(Collectors.toList()); - - - - // 封装分页结果 - Page gridResult = new Page(); - gridResult.setRecords(voList); // 当前页数据列表 - gridResult.setCurrent(current); // 当前页码 - gridResult.setTotal(vlogPage.getTotal()); // 总记录数 - gridResult.setSize(vlogPage.getPages()); // 总页数 - - - return gridResult; - - } @Override public Integer getVlogBeLikedCounts(String vlogId) { - String countsStr = redis.get(REDIS_VLOG_BE_LIKED_COUNTS + ":" + vlogId); - if (StringUtils.isBlank(countsStr)) { - countsStr = "0"; - } - return Integer.valueOf(countsStr); + Double count = cache.zSetScore(REDIS_VLOG_BE_LIKED_COUNTS, vlogId); + + return count==null?0:count.intValue(); } private Integer getVlogComment(String vlogId) { - String countsStr = redis.get(REDIS_VLOG_COMMENT_COUNTS + ":" + vlogId); - if (StringUtils.isBlank(countsStr)) { - countsStr = "0"; - } - return Integer.valueOf(countsStr); + Double count = cache.zSetScore(REDIS_VLOG_COMMENT_COUNTS, vlogId); + return count==null?0:count.intValue(); } - private boolean doILikeVlog(String myId, String vlogId) { - String doILike = redis.get(REDIS_USER_LIKE_VLOG + ":" + myId + ":" + vlogId); + private boolean doILikeVlog(String myId, String vlogId) { + String doILike = cache.getCacheObject(REDIS_USER_LIKE_VLOG + ":" + myId + ":" + vlogId); return StringUtils.isNotBlank(doILike) && doILike.equalsIgnoreCase("1"); } @Override - public IndexVlogVO getVlogDetailById( String vlogId) { + @Cacheable(cacheNames = GlobalConstants.VLOG_KEY, key = "#vlogId") + public IndexVlogVO getVlogDetailById(String vlogId) { Vlog vlog = vlogMapper.selectById(vlogId); if (vlog == null) { return null; } IndexVlogVO vo = MapstructUtils.convert(vlog, IndexVlogVO.class); //获取用户信息 - try{ + try { LoginUser loginUser = LoginHelper.getLoginUser(); if (loginUser != null) { - vo.setDoIFollowVloger(fansService.queryDoIFollowVloger(loginUser.getUserId()+"", vlog.getMemberId())); - vo.setDoILikeThisVlog(doILikeVlog(loginUser.getUserId()+"", vlogId)); + vo.setDoIFollowVloger(fansService.queryDoIFollowVloger(loginUser.getUserId() + "", vlog.getMemberId())); + vo.setDoILikeThisVlog(doILikeVlog(loginUser.getUserId() + "", vlogId)); } - }catch (Exception e){ + } catch (Exception e) { log.error(e.getMessage()); } @@ -307,6 +193,8 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService { @Transactional(rollbackFor = Exception.class) @Override + + @CacheEvict(value = GlobalConstants.VLOG_KEY, key = "#vlogId") public void changeToPrivateOrPublic(String userId, String vlogId, Integer yesOrNo) { LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(Vlog::getId, vlogId) @@ -317,6 +205,7 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService { @Transactional(rollbackFor = Exception.class) @Override + @CacheEvict(value = GlobalConstants.VLOG_KEY, key = "#vlogId") public void changeVlogStatus(String userId, String vlogId, Integer status) { LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(Vlog::getId, vlogId) @@ -332,17 +221,17 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService { Page pageParam = new Page<>(current, size); LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - if(bo.getUserId()==null){ - LoginUser user= LoginHelper.getLoginUser(); - if(user==null){ + if (bo.getUserId() == null) { + LoginUser user = LoginHelper.getLoginUser(); + if (user == null) { throw new ServiceException("用户未登录"); } queryWrapper.eq(Vlog::getMemberId, user.getUserId()); - }else{ + } else { queryWrapper.eq(Vlog::getMemberId, bo.getUserId()); } - queryWrapper.eq(bo.getPrivateFlag()!=null,Vlog::getIsPrivate, bo.getPrivateFlag()); + queryWrapper.eq(bo.getPrivateFlag() != null, Vlog::getIsPrivate, bo.getPrivateFlag()); Page vlogPage = vlogMapper.selectPage(pageParam, queryWrapper); List vlogList = vlogPage.getRecords(); @@ -380,7 +269,15 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService { @Transactional(rollbackFor = Exception.class) @Override + @CacheEvict(value = GlobalConstants.VLOG_KEY, key = "#vlogId") public void userLikeVlog(String userId, String vlogId) { + //获取vlog + Vlog vlog = this.getVlog(vlogId); + if (vlog == null) { + throw new ServiceException("视频不存在"); + } + // 我点赞的视频,关联关系保存到数据库 + String rid = sid.nextShort(); MyLikedVlog likedVlog = new MyLikedVlog(); likedVlog.setId(rid); @@ -388,36 +285,32 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService { likedVlog.setUserId(userId); myLikedVlogMapper.insert(likedVlog); - // 我喜欢的视频总数累加 - redis.increment(REDIS_VLOGER_BE_LIKED_COUNTS + ":" + userId, 1); - // 视频被喜欢的总数累加 - redis.increment(REDIS_VLOG_BE_LIKED_COUNTS + ":" + vlogId, 1); - // 保存用户和视频的喜欢关系 - redis.set(REDIS_USER_LIKE_VLOG + ":" + userId + ":" + vlogId, "1"); + // 点赞后,视频和视频发布者的获赞都会 +1 + cache.zSetIncrement(REDIS_VLOGER_BE_LIKED_COUNTS, vlog.getMemberId(), 1); + cache.zSetIncrement(REDIS_VLOG_BE_LIKED_COUNTS, vlogId, 1); - // 发送点赞通知 - Vlog vlog = vlogMapper.selectById(vlogId); - if (vlog != null) { - String vlogerId = vlog.getMemberId(); - if (!userId.equals(vlogerId)) { -// Long templateId = 1938491299175723009L; -// SysMessageTemplateVo template = templateService.selectTemplateById(templateId); -// if (template != null) { -// // 查询点赞用户昵称 -// Member liker = memberMapper.selectById(userId); -// String likerNickname = liker != null && liker.getNickname() != null ? liker.getNickname() : ""; -// -// String content = template.getTemplateContent() -// .replace("${videoTitle}", vlog.getTitle() == null ? "" : vlog.getTitle()) -// .replace("${liker}", userId) -// .replace("${likerNickname}", likerNickname); -// SysMessageBo messageBo = new SysMessageBo(); -// messageBo.setTitle(template.getTitle()); -// messageBo.setContent(content); -// messageBo.setSenderId(Long.valueOf(userId)); -// sysMessageService.sendMessageToUser(messageBo, Long.valueOf(vlogerId)); -// } - } + // 我点赞的视频,需要在redis中保存关联关系 + cache.setCacheObject(REDIS_USER_LIKE_VLOG + ":" + userId + ":" + vlogId, "1"); + Double count = cache.zSetScore(REDIS_VLOG_BE_LIKED_COUNTS, vlogId); + if (count > 0) { + //更新数据库的点赞数 + this.flushCounts(vlogId, count.intValue()); + } + //发送消息 + if (userId != null && vlog.getMemberId() != null && !userId.equals(vlog.getMemberId())) { + // 新版:使用模板类型编号和参数 + Map params = new HashMap<>(); + params.put("userId", userId); + params.put("nickname", LoginHelper.getLoginUser().getNickname()); + params.put("action", MessageActionEnum.INTERACTION_LIKE.name()); + params.put("toUserId", vlog.getMemberId()); + MQMessage message = MQMessage.builder() + .messageType(MQMessageType.IM.name()) + .data(params) + .source("member") + .build(); + // 关注消息 + MqUtil.sendIMMessage(message); } } @@ -437,61 +330,71 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService { @Transactional(rollbackFor = Exception.class) @Override + @CacheEvict(value = GlobalConstants.VLOG_KEY, key = "#vlogId") public void userUnLikeVlog(String userId, String vlogId) { - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.eq(MyLikedVlog::getUserId, userId) - .eq(MyLikedVlog::getVlogId, vlogId); - myLikedVlogMapper.delete(queryWrapper); - // 我喜欢的视频总数累减 - redis.decrement(REDIS_VLOGER_BE_LIKED_COUNTS + ":" + userId, 1); - // 视频被喜欢的总数累减 - redis.decrement(REDIS_VLOG_BE_LIKED_COUNTS + ":" + vlogId, 1); - // 删除用户和视频的喜欢关系 - redis.del(REDIS_USER_LIKE_VLOG + ":" + userId + ":" + vlogId); + //获取vlog + Vlog vlog = this.getVlog(vlogId); + if (vlog == null) { + throw new ServiceException("视频不存在"); + } + + // 我取消点赞的视频,关联关系删除 + this.userUnLikeVlog(userId, vlogId); + + cache.zSetDecrement(REDIS_VLOGER_BE_LIKED_COUNTS, vlog.getMemberId(), 1); + cache.zSetDecrement(REDIS_VLOG_BE_LIKED_COUNTS, vlogId, 1); + cache.deleteObject(REDIS_USER_LIKE_VLOG + ":" + userId + ":" + vlogId); + + + Double count = cache.zSetScore(REDIS_VLOG_BE_LIKED_COUNTS, vlogId); + if (count > 0) { + //更新数据库的点赞数 + this.flushCounts(vlogId, count.intValue()); + } } @Override - public Page getMyLikedVlogList( Page page) { + public Page getMyLikedVlogList(Page page) { - LoginUser user= LoginHelper.getLoginUser(); - if(user==null){ + LoginUser user = LoginHelper.getLoginUser(); + if (user == null) { throw new ServiceException("用户未登录"); } Map map = new HashMap<>(); map.put("myId", user.getUserId()); - Page likedPage = vlogMapperCustom.getMyLikedVlogList(map,page); + Page likedPage = vlogMapperCustom.getMyLikedVlogList(map, page); - likedPage.getRecords().stream().forEach( - liked -> { - liked.setDoIFollowVloger(true); - liked.setDoILikeThisVlog(true); + likedPage.getRecords().stream().forEach( + liked -> { + liked.setDoIFollowVloger(true); + liked.setDoILikeThisVlog(true); }); - return likedPage; + return likedPage; } @Override public Page getMyFollowVlogList(Page page) { - LoginUser user= LoginHelper.getLoginUser(); - if(user==null){ + LoginUser user = LoginHelper.getLoginUser(); + if (user == null) { throw new ServiceException("用户未登录"); } Map map = new HashMap<>(); map.put("myId", user.getUserId()); - Page voPage = vlogMapperCustom.getMyFollowVlogList(map,page); + Page voPage = vlogMapperCustom.getMyFollowVlogList(map, page); return voPage; } @Override - public Page getMyFriendVlogList( Page page) { - LoginUser user= LoginHelper.getLoginUser(); - if(user==null){ + public Page getMyFriendVlogList(Page page) { + LoginUser user = LoginHelper.getLoginUser(); + if (user == null) { throw new ServiceException("用户未登录"); } Map map = new HashMap<>(); map.put("myId", user.getUserId()); - Page voPage = vlogMapperCustom.getMyFriendVlogList(map,page); + Page voPage = vlogMapperCustom.getMyFriendVlogList(map, page); return voPage; } @@ -512,15 +415,15 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService { if (vlog != null) { result.put("vlogId", vlog.getId()); result.put("title", vlog.getTitle()); - result.put("width",vlog.getWidth()); - result.put("height",vlog.getHeight()); - result.put("reason",vlog.getReason()); + result.put("width", vlog.getWidth()); + result.put("height", vlog.getHeight()); + result.put("reason", vlog.getReason()); // 注意:点赞数和评论数现在在控制器中直接从Redis获取 // 这里不再统计,避免重复计算 // 获取粉丝数量:优先 Redis,无则 MySQL - String fansCountsStr = redis.get(REDIS_MY_FANS_COUNTS + ":" + vlog.getMemberId()); + String fansCountsStr = cache.getCacheObject(REDIS_MY_FANS_COUNTS + ":" + vlog.getMemberId()); Integer fansCounts = 0; if (StringUtils.isNotBlank(fansCountsStr)) { fansCounts = Integer.valueOf(fansCountsStr); @@ -560,22 +463,14 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService { result.put("comments", comments); // 从Redis获取点赞数 - String likeCountsStr = redis.get(REDIS_VLOG_BE_LIKED_COUNTS + ":" + vlog.getId()); - Integer likeCounts = 0; - if (StringUtils.isNotBlank(likeCountsStr)) { - try { - likeCounts = Integer.valueOf(likeCountsStr); - } catch (NumberFormatException e) { - log.warn("Redis中视频{}的点赞数格式错误: {}", vlog.getId(), likeCountsStr); - likeCounts = 0; - } - } + int likeCounts = getVlogBeLikedCounts(vlog.getId()); + result.put("likeCounts", likeCounts); result.put("vlogId", vlog.getId()); List> likedUsers = myLikedVlogMapper.selectLikedUsersByVlogId(vlog.getId()); result.put("likedUsers", likedUsers); - result.put("vlog",vo); + result.put("vlog", vo); // 添加粉丝列表 // Page fansPage = fansService.queryMyFans(vlog.getVlogerId(), 0, 10); @@ -608,20 +503,6 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService { return myLikedVlogMapper.selectLikedUsersByVlogId(vlogId); } - @Override - public int getLikeCounts(String vlogId) { - // 从Redis获取点赞数 - String likeCountsStr = redis.get(REDIS_VLOG_BE_LIKED_COUNTS + ":" + vlogId); - if (StringUtils.isNotBlank(likeCountsStr)) { - try { - return Integer.valueOf(likeCountsStr); - } catch (NumberFormatException e) { - log.warn("Redis中视频{}的点赞数格式错误: {}", vlogId, likeCountsStr); - return 0; - } - } - return 0; - } @Override public Map getVlogUploaderInfo(String fileId) { @@ -699,77 +580,6 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService { return vlogMapper.selectVlogListWithAggregatedData(page, vlogBO); } - @Override - public void cacheTopLikedVlogs(int limit) { - try { - log.info("开始查询点赞最多的{}条视频", limit); - - // 查询所有公开的视频列表 - List> allVlogs = vlogMapper.selectAllPublicVlogs(); - - if (allVlogs != null && !allVlogs.isEmpty()) { - // 从Redis获取每个视频的点赞数量 - List> vlogsWithLikeCounts = new ArrayList<>(); - - for (Map vlog : allVlogs) { - String vlogId = vlog.get("id").toString(); - String redisLikeKey = REDIS_VLOG_BE_LIKED_COUNTS + ":" + vlogId; - String likeCountStr = redis.get(redisLikeKey); - - // 获取Redis中的点赞数,如果没有则使用数据库中的默认值 - int likeCount = 0; - if (likeCountStr != null && !likeCountStr.isEmpty()) { - try { - likeCount = Integer.parseInt(likeCountStr); - } catch (NumberFormatException e) { - log.warn("Redis中视频{}的点赞数格式错误: {}", vlogId, likeCountStr); - likeCount = 0; - } - } - - // 添加Redis中的点赞数到视频信息中 - vlog.put("redis_like_count", likeCount); - vlogsWithLikeCounts.add(vlog); - } - - // 先打乱顺序 - Collections.shuffle(vlogsWithLikeCounts); - - // 按Redis中的点赞数排序,获取前limit个 - vlogsWithLikeCounts.sort((v1, v2) -> { - int count1 = (Integer) v1.get("redis_like_count"); - int count2 = (Integer) v2.get("redis_like_count"); - return Integer.compare(count2, count1); // 降序排列 - }); - - // 取前limit个 - List> topLikedVlogs = vlogsWithLikeCounts.stream() - .limit(limit) - .collect(Collectors.toList()); - - if (!topLikedVlogs.isEmpty()) { - // 将结果存储到Redis中,使用JSON格式 - ObjectMapper objectMapper = new ObjectMapper(); - // 注册JavaTimeModule以支持LocalDateTime序列化 - objectMapper.registerModule(new com.fasterxml.jackson.datatype.jsr310.JavaTimeModule()); - objectMapper.disable(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - String jsonData = objectMapper.writeValueAsString(topLikedVlogs); - - // 存储到Redis,设置24小时过期时间 - String redisKey = "top_liked_vlogs:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); - redis.set(redisKey, jsonData, 24 * 60 * 60); // 24小时过期 - - log.info("成功缓存{}条点赞最多的视频到Redis,key: {}", topLikedVlogs.size(), redisKey); - } else { - log.warn("未查询到点赞最多的视频数据"); - } - } else { - log.warn("未查询到公开的视频数据"); - } - } catch (Exception e) { - log.error("缓存点赞最多视频到Redis失败", e); - } - } @Override public List> getRandomVlogs(int limit) { @@ -784,6 +594,49 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService { } } + @Override + public IPage getVlogForUser(Page page, Long memberId) { + return baseMapper.getVlogForUser(page, memberId); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int readVlog(Long memberId, String vlogId) { + //加入已读缓存列表,数量为1 + if (!redisCache.zSetHasMember("vlog:read:" + memberId, vlogId)) { + redisCache.zSetAdd("vlog:read:" + memberId, vlogId, 1); + } else { + redisCache.zSetIncrement("vlog:read:" + memberId, vlogId, 1); + } + return baseMapper.readVlog(memberId, vlogId); + } + + @Override + public List getIndexVlogList(VlogBO bo) { + List indexVlogVOPage = vlogMapperCustom.getIndexVlogList(bo); + fillRedisColumn(indexVlogVOPage ); + return indexVlogVOPage; + } + + @Override + public Page getIndexVlogList(VlogBO bo, Page page) { + Page indexVlogVOPage = vlogMapperCustom.getIndexVlogList(bo,page); + fillRedisColumn(indexVlogVOPage.getRecords()); + return indexVlogVOPage; + } + + private void fillRedisColumn(List vlogList) { + LoginUser user = LoginHelper.getLoginUser(); + vlogList.parallelStream().forEach(vlog -> { + if (user != null) { + vlog.setDoIFollowVloger(fansService.queryDoIFollowVloger(user.getUserId() + "", vlog.getMemberId())); + vlog.setDoILikeThisVlog(doILikeVlog(user.getUserId() + "", vlog.getId())); + } + vlog.setLikeCounts(getVlogBeLikedCounts(vlog.getId())); + vlog.setCommentsCounts(getVlogComment(vlog.getId())); + }); + } + @Override public Page getIndexSearchVlogList(SearchBO bo, Page page) { String userId = bo.getUserId(); diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/impl/VlogUploadServiceImpl.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/impl/VlogUploadServiceImpl.java index c7fecf6e9..09d02ac48 100644 --- a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/impl/VlogUploadServiceImpl.java +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/impl/VlogUploadServiceImpl.java @@ -14,6 +14,7 @@ import com.wzj.soopin.content.utils.TencentCloudUtil; import lombok.extern.slf4j.Slf4j; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.redis.redis.RedisCache; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -36,7 +37,7 @@ public class VlogUploadServiceImpl implements VlogUploadService { @Autowired private com.wzj.soopin.content.service.CommentService commentService; @Autowired - private com.wzj.soopin.content.utils.RedisOperator redis; + private RedisCache redisCache; @Autowired private com.wzj.soopin.content.mapper.VlogMapperCustom vlogMapperCustom; @@ -48,110 +49,8 @@ public class VlogUploadServiceImpl implements VlogUploadService { // return client.PullUpload(req); // } - @Override - public SearchMediaResponse searchMedia(SearchMediaRequest req) { - try { - // 获取VOD客户端实例 - VodClient client = tencentCloudUtil.getVodClient(); - // 设置默认值 - if (req.getOffset() == null) { - req.setOffset(0L); - } - if (req.getLimit() == null) { - req.setLimit(10L); - } - // 添加日志,打印请求参数 -// log.info("SearchMediaRequest 请求参数: {}", req.toJsonString()); - - // 添加调试信息 - log.info("请求时间戳: {}", System.currentTimeMillis()); - log.info("请求参数详情 - Offset: {}, Limit: {}, Sort: {}, Filters: {}", - req.getOffset(), - req.getLimit(), -// req.getSort() != null ? req.getSort().toJsonString() : "null", - req.getFilters() != null ? String.join(",", req.getFilters()) : "null"); - - // 验证请求参数 - validateSearchRequest(req); - - SearchMediaResponse response = client.SearchMedia(req); - log.info("SearchMedia API 调用成功,返回结果数: {}", response.getTotalCount()); - return response; - } catch (TencentCloudSDKException e) { - log.error("搜索媒体文件失败: {}", e.getMessage(), e); - // 添加更详细的错误信息 - if (e.getMessage().contains("signature")) { - log.error("签名验证失败,请检查:\n1. 密钥是否正确\n2. 服务器时间是否准确\n3. 时区设置是否正确"); - } else if (e.getMessage().contains("InvalidParameterValue")) { - log.error("参数值错误,请检查:\n1. 分页参数 Offset 和 Limit 是否有效\n2. 搜索条件是否合法\n3. 排序参数是否正确"); - } else if (e.getMessage().contains("UnauthorizedOperation")) { - log.error("未授权操作,请检查:\n1. 密钥是否有权限\n2. 应用 ID 是否正确\n3. 是否开通了点播服务"); - } - throw new RuntimeException("搜索媒体文件失败: " + e.getMessage()); - } - } - - /** - * 验证搜索请求参数 - */ - private void validateSearchRequest(SearchMediaRequest req) { - // 验证分页参数 - if (req.getOffset() < 0) { - throw new IllegalArgumentException("Offset 不能小于 0"); - } - if (req.getLimit() < 1 || req.getLimit() > 50) { - throw new IllegalArgumentException("Limit 必须在 1-50 之间"); - } - if (req.getOffset() + req.getLimit() > 5000) { - throw new IllegalArgumentException("Offset + Limit 不能超过 5000"); - } - - // 验证搜索条件 - if (req.getFileIds() != null && req.getFileIds().length > 10) { - throw new IllegalArgumentException("FileIds 数组长度不能超过 10"); - } - if (req.getNames() != null && req.getNames().length > 10) { - throw new IllegalArgumentException("Names 数组长度不能超过 10"); - } - if (req.getNamePrefixes() != null && req.getNamePrefixes().length > 10) { - throw new IllegalArgumentException("NamePrefixes 数组长度不能超过 10"); - } - if (req.getDescriptions() != null && req.getDescriptions().length > 10) { - throw new IllegalArgumentException("Descriptions 数组长度不能超过 10"); - } - if (req.getClassIds() != null && req.getClassIds().length > 10) { - throw new IllegalArgumentException("ClassIds 数组长度不能超过 10"); - } - if (req.getTags() != null && req.getTags().length > 16) { - throw new IllegalArgumentException("Tags 数组长度不能超过 16"); - } - if (req.getSourceTypes() != null && req.getSourceTypes().length > 10) { - throw new IllegalArgumentException("SourceTypes 数组长度不能超过 10"); - } - if (req.getStreamIds() != null && req.getStreamIds().length > 10) { - throw new IllegalArgumentException("StreamIds 数组长度不能超过 10"); - } - if (req.getStorageRegions() != null && req.getStorageRegions().length > 20) { - throw new IllegalArgumentException("StorageRegions 数组长度不能超过 20"); - } -// if (req.getMediaTypes() != null && req.getMediaTypes().length > 10) { -// throw new IllegalArgumentException("MediaTypes 数组长度不能超过 10"); -// } -// if (req.getStatus() != null && req.getStatus().length > 10) { -// throw new IllegalArgumentException("Status 数组长度不能超过 10"); -// } -// if (req.getReviewResults() != null && req.getReviewResults().length > 10) { -// throw new IllegalArgumentException("ReviewResults 数组长度不能超过 10"); -// } -// if (req.getTrtcSdkAppIds() != null && req.getTrtcSdkAppIds().length > 10) { -// throw new IllegalArgumentException("TrtcSdkAppIds 数组长度不能超过 10"); -// } -// if (req.getTrtcRoomIds() != null && req.getTrtcRoomIds().length > 10) { -// throw new IllegalArgumentException("TrtcRoomIds 数组长度不能超过 10"); -// } - } // 视频审核 @Override @@ -301,26 +200,11 @@ public class VlogUploadServiceImpl implements VlogUploadService { } // 2. 点赞数量:优先 Redis,无则 MySQL - String likeCountsStr = redis.get(com.wzj.soopin.content.domain.base.BaseInfoProperties.REDIS_VLOG_BE_LIKED_COUNTS + ":" + vlogId); - int likeCounts; - if (likeCountsStr != null) { - likeCounts = Integer.parseInt(likeCountsStr); - } else if (vlog != null && vlog.getLikeCounts() != null) { - likeCounts = vlog.getLikeCounts(); - } else { - likeCounts = 0; - } + int likeCounts=redisCache.zSetScore(com.wzj.soopin.content.domain.base.BaseInfoProperties.REDIS_VLOG_BE_LIKED_COUNTS , vlogId).intValue(); // 3. 评论数量:优先 Redis,无则 MySQL - String commentCountsStr = redis.get(com.wzj.soopin.content.domain.base.BaseInfoProperties.REDIS_VLOG_COMMENT_COUNTS + ":" + vlogId); - int commentCounts; - if (commentCountsStr != null) { - commentCounts = Integer.parseInt(commentCountsStr); - } else if (vlog != null && vlog.getCommentsCounts() != null) { - commentCounts = vlog.getCommentsCounts(); - } else { - commentCounts = 0; - } + int commentCounts=redisCache.zSetScore(com.wzj.soopin.content.domain.base.BaseInfoProperties.REDIS_VLOG_COMMENT_COUNTS , vlogId).intValue(); + // 4. 评论内容:只查 MySQL List commentList = new ArrayList<>(); diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/task/VlogScheduledTask.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/task/VlogScheduledTask.java index e472764bb..fca28ed6e 100644 --- a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/task/VlogScheduledTask.java +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/task/VlogScheduledTask.java @@ -27,7 +27,7 @@ public class VlogScheduledTask { public void jobExecute() { log.info("开始执行定时任务:查询点赞最多的100条视频并存储到Redis"); try { - vlogService.cacheTopLikedVlogs(100); +// vlogService.cacheTopLikedVlogs(100); log.info("定时任务执行完成:成功缓存点赞最多的100条视频到Redis"); } catch (Exception e) { log.error("定时任务执行失败:缓存点赞最多视频到Redis时发生异常", e); diff --git a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/utils/RedisOperator.java b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/utils/RedisOperator.java index 20ed89ff0..8756f9d7f 100644 --- a/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/utils/RedisOperator.java +++ b/ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/utils/RedisOperator.java @@ -24,286 +24,286 @@ public class RedisOperator { private StringRedisTemplate redisTemplate; // Key(键),简单的key-value操作 - - /** - * 判断key是否存在 - * @param key - * @return - */ - public boolean keyIsExist(String key) { - return redisTemplate.hasKey(key); - } - - /** - * 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。 - * - * @param key - * @return - */ - public long ttl(String key) { - return redisTemplate.getExpire(key); - } - - /** - * 实现命令:expire 设置过期时间,单位秒 - * - * @param key - * @return - */ - public void expire(String key, long timeout) { - redisTemplate.expire(key, timeout, TimeUnit.SECONDS); - } - - /** - * 实现命令:increment key,增加key一次 - * - * @param key - * @return - */ - public long increment(String key, long delta) { - return redisTemplate.opsForValue().increment(key, delta); - } - - /** - * 累加,使用hash - */ - public long incrementHash(String name, String key, long delta) { - return redisTemplate.opsForHash().increment(name, key, delta); - } - - /** - * 累减,使用hash - */ - public long decrementHash(String name, String key, long delta) { - delta = delta * (-1); - return redisTemplate.opsForHash().increment(name, key, delta); - } - - /** - * hash 设置value - */ - public void setHashValue(String name, String key, String value) { - redisTemplate.opsForHash().put(name, key, value); - } - - /** - * hash 获得value - */ - public String getHashValue(String name, String key) { - return (String)redisTemplate.opsForHash().get(name, key); - } - - /** - * 实现命令:decrement key,减少key一次 - * - * @param key - * @return - */ - public long decrement(String key, long delta) { - return redisTemplate.opsForValue().decrement(key, delta); - } - - /** - * 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key - */ - public Set keys(String pattern) { - return redisTemplate.keys(pattern); - } - - /** - * 实现命令:DEL key,删除一个key - * - * @param key - */ - public void del(String key) { - redisTemplate.delete(key); - } - - // String(字符串) - - /** - * 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key) - * - * @param key - * @param value - */ - public void set(String key, String value) { - redisTemplate.opsForValue().set(key, value); - } - - /** - * 实现命令:SET key value EX seconds,设置key-value和超时时间(秒) - * - * @param key - * @param value - * @param timeout - * (以秒为单位) - */ - public void set(String key, String value, long timeout) { - redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS); - } - - /** - * 如果key不存在,则设置,如果存在,则报错 - * @param key - * @param value - */ - public void setnx60s(String key, String value) { - redisTemplate.opsForValue().setIfAbsent(key, value, 60, TimeUnit.SECONDS); - } - - /** - * 如果key不存在,则设置,如果存在,则报错 - * @param key - * @param value - */ - public void setnx(String key, String value) { - redisTemplate.opsForValue().setIfAbsent(key, value); - } - - /** - * 实现命令:GET key,返回 key所关联的字符串值。 - * - * @param key - * @return value - */ - public String get(String key) { - return (String)redisTemplate.opsForValue().get(key); - } - - /** - * 批量查询,对应mget - * @param keys - * @return - */ - public List mget(List keys) { - return redisTemplate.opsForValue().multiGet(keys); - } - - /** - * 批量查询,管道pipeline - * @param keys - * @return - */ - public List batchGet(List keys) { - -// nginx -> keepalive -// redis -> pipeline - - List result = redisTemplate.executePipelined(new RedisCallback() { - @Override - public String doInRedis(RedisConnection connection) throws DataAccessException { - StringRedisConnection src = (StringRedisConnection)connection; - - for (String k : keys) { - src.get(k); - } - return null; - } - }); - - return result; - } - - - // Hash(哈希表) - - /** - * 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value - * - * @param key - * @param field - * @param value - */ - public void hset(String key, String field, Object value) { - redisTemplate.opsForHash().put(key, field, value); - } - - /** - * 实现命令:HGET key field,返回哈希表 key中给定域 field的值 - * - * @param key - * @param field - * @return - */ - public String hget(String key, String field) { - return (String) redisTemplate.opsForHash().get(key, field); - } - - /** - * 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。 - * - * @param key - * @param fields - */ - public void hdel(String key, Object... fields) { - redisTemplate.opsForHash().delete(key, fields); - } - - /** - * 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。 - * - * @param key - * @return - */ - public Map hgetall(String key) { - return redisTemplate.opsForHash().entries(key); - } - - // List(列表) - - /** - * 实现命令:LPUSH key value,将一个值 value插入到列表 key的表头 - * - * @param key - * @param value - * @return 执行 LPUSH命令后,列表的长度。 - */ - public long lpush(String key, String value) { - return redisTemplate.opsForList().leftPush(key, value); - } - - /** - * 实现命令:LPOP key,移除并返回列表 key的头元素。 - * - * @param key - * @return 列表key的头元素。 - */ - public String lpop(String key) { - return (String)redisTemplate.opsForList().leftPop(key); - } - - /** - * 实现命令:RPUSH key value,将一个值 value插入到列表 key的表尾(最右边)。 - * - * @param key - * @param value - * @return 执行 LPUSH命令后,列表的长度。 - */ - public long rpush(String key, String value) { - return redisTemplate.opsForList().rightPush(key, value); - } - // List(列表) - /** - * 实现命令:LRANGE key start stop,返回列表key中指定区间内的元素 - * - * @param key Redis key - * @param start 开始索引 - * @param stop 结束索引 - * @return 返回指定区间的元素 - */ - public List lrange(String key, long start, long stop) { - return redisTemplate.opsForList().range(key, start, stop); - } - /** - * 实现命令:LREM key count value,移除列表中与 value 相等的元素 - * - * @param key Redis key - * @param count 删除的数量(正数表示从头部开始删除,负数从尾部,0表示删除全部匹配项) - * @param value 要删除的元素值 - * @return 被删除的元素个数 - */ - public Long lrem(String key, long count, Object value) { - return redisTemplate.opsForList().remove(key, count, value); - } +// +// /** +// * 判断key是否存在 +// * @param key +// * @return +// */ +// public boolean keyIsExist(String key) { +// return redisTemplate.hasKey(key); +// } +// +// /** +// * 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。 +// * +// * @param key +// * @return +// */ +// public long ttl(String key) { +// return redisTemplate.getExpire(key); +// } +// +// /** +// * 实现命令:expire 设置过期时间,单位秒 +// * +// * @param key +// * @return +// */ +// public void expire(String key, long timeout) { +// redisTemplate.expire(key, timeout, TimeUnit.SECONDS); +// } +// +// /** +// * 实现命令:increment key,增加key一次 +// * +// * @param key +// * @return +// */ +// public long increment(String key, long delta) { +// return redisTemplate.opsForValue().increment(key, delta); +// } +// +// /** +// * 累加,使用hash +// */ +// public long incrementHash(String name, String key, long delta) { +// return redisTemplate.opsForHash().increment(name, key, delta); +// } +// +// /** +// * 累减,使用hash +// */ +// public long decrementHash(String name, String key, long delta) { +// delta = delta * (-1); +// return redisTemplate.opsForHash().increment(name, key, delta); +// } +// +// /** +// * hash 设置value +// */ +// public void setHashValue(String name, String key, String value) { +// redisTemplate.opsForHash().put(name, key, value); +// } +// +// /** +// * hash 获得value +// */ +// public String getHashValue(String name, String key) { +// return (String)redisTemplate.opsForHash().get(name, key); +// } +// +// /** +// * 实现命令:decrement key,减少key一次 +// * +// * @param key +// * @return +// */ +// public long decrement(String key, long delta) { +// return redisTemplate.opsForValue().decrement(key, delta); +// } +// +// /** +// * 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key +// */ +// public Set keys(String pattern) { +// return redisTemplate.keys(pattern); +// } +// +// /** +// * 实现命令:DEL key,删除一个key +// * +// * @param key +// */ +// public void del(String key) { +// redisTemplate.delete(key); +// } +// +// // String(字符串) +// +// /** +// * 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key) +// * +// * @param key +// * @param value +// */ +// public void set(String key, String value) { +// redisTemplate.opsForValue().set(key, value); +// } +// +// /** +// * 实现命令:SET key value EX seconds,设置key-value和超时时间(秒) +// * +// * @param key +// * @param value +// * @param timeout +// * (以秒为单位) +// */ +// public void set(String key, String value, long timeout) { +// redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS); +// } +// +// /** +// * 如果key不存在,则设置,如果存在,则报错 +// * @param key +// * @param value +// */ +// public void setnx60s(String key, String value) { +// redisTemplate.opsForValue().setIfAbsent(key, value, 60, TimeUnit.SECONDS); +// } +// +// /** +// * 如果key不存在,则设置,如果存在,则报错 +// * @param key +// * @param value +// */ +// public void setnx(String key, String value) { +// redisTemplate.opsForValue().setIfAbsent(key, value); +// } +// +// /** +// * 实现命令:GET key,返回 key所关联的字符串值。 +// * +// * @param key +// * @return value +// */ +// public String get(String key) { +// return (String)redisTemplate.opsForValue().get(key); +// } +// +// /** +// * 批量查询,对应mget +// * @param keys +// * @return +// */ +// public List mget(List keys) { +// return redisTemplate.opsForValue().multiGet(keys); +// } +// +// /** +// * 批量查询,管道pipeline +// * @param keys +// * @return +// */ +// public List batchGet(List keys) { +// +//// nginx -> keepalive +//// redis -> pipeline +// +// List result = redisTemplate.executePipelined(new RedisCallback() { +// @Override +// public String doInRedis(RedisConnection connection) throws DataAccessException { +// StringRedisConnection src = (StringRedisConnection)connection; +// +// for (String k : keys) { +// src.get(k); +// } +// return null; +// } +// }); +// +// return result; +// } +// +// +// // Hash(哈希表) +// +// /** +// * 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value +// * +// * @param key +// * @param field +// * @param value +// */ +// public void hset(String key, String field, Object value) { +// redisTemplate.opsForHash().put(key, field, value); +// } +// +// /** +// * 实现命令:HGET key field,返回哈希表 key中给定域 field的值 +// * +// * @param key +// * @param field +// * @return +// */ +// public String hget(String key, String field) { +// return (String) redisTemplate.opsForHash().get(key, field); +// } +// +// /** +// * 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。 +// * +// * @param key +// * @param fields +// */ +// public void hdel(String key, Object... fields) { +// redisTemplate.opsForHash().delete(key, fields); +// } +// +// /** +// * 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。 +// * +// * @param key +// * @return +// */ +// public Map hgetall(String key) { +// return redisTemplate.opsForHash().entries(key); +// } +// +// // List(列表) +// +// /** +// * 实现命令:LPUSH key value,将一个值 value插入到列表 key的表头 +// * +// * @param key +// * @param value +// * @return 执行 LPUSH命令后,列表的长度。 +// */ +// public long lpush(String key, String value) { +// return redisTemplate.opsForList().leftPush(key, value); +// } +// +// /** +// * 实现命令:LPOP key,移除并返回列表 key的头元素。 +// * +// * @param key +// * @return 列表key的头元素。 +// */ +// public String lpop(String key) { +// return (String)redisTemplate.opsForList().leftPop(key); +// } +// +// /** +// * 实现命令:RPUSH key value,将一个值 value插入到列表 key的表尾(最右边)。 +// * +// * @param key +// * @param value +// * @return 执行 LPUSH命令后,列表的长度。 +// */ +// public long rpush(String key, String value) { +// return redisTemplate.opsForList().rightPush(key, value); +// } +// // List(列表) +// /** +// * 实现命令:LRANGE key start stop,返回列表key中指定区间内的元素 +// * +// * @param key Redis key +// * @param start 开始索引 +// * @param stop 结束索引 +// * @return 返回指定区间的元素 +// */ +// public List lrange(String key, long start, long stop) { +// return redisTemplate.opsForList().range(key, start, stop); +// } +// /** +// * 实现命令:LREM key count value,移除列表中与 value 相等的元素 +// * +// * @param key Redis key +// * @param count 删除的数量(正数表示从头部开始删除,负数从尾部,0表示删除全部匹配项) +// * @param value 要删除的元素值 +// * @return 被删除的元素个数 +// */ +// public Long lrem(String key, long count, Object value) { +// return redisTemplate.opsForList().remove(key, count, value); +// } } diff --git a/ruoyi-modules/ruoyi-content/src/main/resources/mapper/content/VlogMapper.xml b/ruoyi-modules/ruoyi-content/src/main/resources/mapper/content/VlogMapper.xml index b71387915..27789e3d9 100644 --- a/ruoyi-modules/ruoyi-content/src/main/resources/mapper/content/VlogMapper.xml +++ b/ruoyi-modules/ruoyi-content/src/main/resources/mapper/content/VlogMapper.xml @@ -282,5 +282,27 @@ + + + + insert into cms_vlog_member (vlog_id, member_id,status) values (#{vlogId}, #{memberId},1) + diff --git a/ruoyi-modules/ruoyi-content/src/main/resources/mapper/content/VlogMapperCustom.xml b/ruoyi-modules/ruoyi-content/src/main/resources/mapper/content/VlogMapperCustom.xml index d7b50fd50..39885482b 100644 --- a/ruoyi-modules/ruoyi-content/src/main/resources/mapper/content/VlogMapperCustom.xml +++ b/ruoyi-modules/ruoyi-content/src/main/resources/mapper/content/VlogMapperCustom.xml @@ -32,32 +32,38 @@ WHERE v.is_private = 0 - - AND v.status = #{paramMap.status} + + AND v.status = #{bo.status} AND v.status = 1 AND v.first_frame_img IS NOT NULL - - AND v.city_code = #{paramMap.cityCode} + + AND v.city_code = #{bo.cityCode} - - AND v.title like '%${paramMap.search}%' + + AND v.title like '%${bo.title}%' - + AND v.id NOT IN - + #{vlogId} - + AND v.member_id NOT IN - + #{memberId} + + AND v.id IN + + #{vlogId} + + ORDER BY v.create_time DESC diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/Feedback.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/Feedback.java index 0d448c322..361ed360f 100644 --- a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/Feedback.java +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/po/Feedback.java @@ -45,7 +45,7 @@ public class Feedback extends BaseAudit { @Schema(description ="反馈对象ID") - private Long aimId; + private String aimId; @Schema(description ="反馈对象类型 1 会员 2 群组 3 评论 4 视频 5 聊天" ) private Integer aimType; diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/vo/FeedbackVO.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/vo/FeedbackVO.java index 5b964d930..02761a162 100644 --- a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/vo/FeedbackVO.java +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/domain/vo/FeedbackVO.java @@ -46,7 +46,7 @@ public class FeedbackVO { @Schema(description ="反馈对象ID") @Excel(name = "反馈对象ID") - private Long aimId; + private String aimId; @Schema(description ="反馈对象类型 1 会员 2 群组 3 评论 4 视频 5 聊天" ) private BaseAudit aimObject; diff --git a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IMemberService.java b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IMemberService.java index df0aac0f6..222f8c1c7 100644 --- a/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IMemberService.java +++ b/ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IMemberService.java @@ -73,6 +73,9 @@ public interface IMemberService extends IService { String updateWechat(MemberBO bo); + + + /** * 不带租户的获取会员信息 * diff --git a/ruoyi-modules/ruoyi-order/src/main/java/com/wzj/soopin/order/service/impl/OrderItemServiceImpl.java b/ruoyi-modules/ruoyi-order/src/main/java/com/wzj/soopin/order/service/impl/OrderItemServiceImpl.java index 5b8f70694..fbb22f33f 100644 --- a/ruoyi-modules/ruoyi-order/src/main/java/com/wzj/soopin/order/service/impl/OrderItemServiceImpl.java +++ b/ruoyi-modules/ruoyi-order/src/main/java/com/wzj/soopin/order/service/impl/OrderItemServiceImpl.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.wzj.soopin.order.domain.entity.OrderItem; import com.wzj.soopin.order.mapper.OrderItemMapper; import com.wzj.soopin.order.service.OrderItemService; +import org.dromara.common.redis.redis.RedisCache; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.fasterxml.jackson.databind.ObjectMapper; @@ -15,6 +16,8 @@ import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map; import java.util.HashMap; +import java.util.concurrent.TimeUnit; + import lombok.extern.slf4j.Slf4j; /** @@ -28,7 +31,7 @@ import lombok.extern.slf4j.Slf4j; public class OrderItemServiceImpl extends ServiceImpl implements OrderItemService { @Autowired - private RedisOperator redis; + private RedisCache redisCache; @Override public List findByOrderId(Long orderId) { @@ -54,7 +57,7 @@ public class OrderItemServiceImpl extends ServiceImpl map = new HashMap<>(); + map.put("code", "000000"); + map.put("msg", "Success"); + return map; + } + + /** + * 测试发起支付 + * @param request + * @return + */ + @SaIgnore + @Log(title = "测试发起支付", businessType = BusinessType.OTHER) + @PostMapping("/test/trade") + public R testTrade(@RequestBody PaymentBO paymentBO) throws ServerException { + EasypayPrePayVO easypayPrePayVO = easypayService.payment(paymentBO); + return R.ok(easypayPrePayVO); + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/controller/WxPayController.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/controller/WxPayController.java index c4ed1e812..d831a14f1 100644 --- a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/controller/WxPayController.java +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/controller/WxPayController.java @@ -11,6 +11,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.domain.R; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; @@ -20,7 +21,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.math.BigInteger; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -179,6 +179,22 @@ public class WxPayController { } } + /** + * 通过小程序的授权码获取用户的openid + * + * @param code 授权码 + * @return 包含openid的响应对象 + */ + @Tag(name = "获取用户openid") + @GetMapping("/openid2") + @Parameters({ + @Parameter(name = "code", description = "授权码", required = true, in = ParameterIn.QUERY) + }) + public R getOpenIdByMiniProgramCode(@RequestParam String code) { + WxAuthResponse response = wxAuthService.getOpenIdByMiniProgramCode(code); + return R.ok(response); + } + } diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/PaymentBO.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/PaymentBO.java new file mode 100644 index 000000000..5f6d0a4ca --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/PaymentBO.java @@ -0,0 +1,99 @@ +package com.wzj.soopin.transaction.domain.bo; + +import com.wzj.soopin.transaction.enums.easypay.PayType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 支付请求参数 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class PaymentBO { + + private String memberId; + + private String goodsId; + + /** + * 订单标题,对应支付宝订单里的 “商品说明”,微信订单里的“商品” + */ + private String orderSub; + /** + * 订单描述 + */ + private String orderDes; + /** + * 订单金额,单位分,最小1 + */ + private long transAmount; + /** + * 支付方式 + */ + private PayType payType; + + /** + * 支付宝业务参数:买家的支付宝唯一用户号 + */ + private String buyerId; + + /** + * 微信业务参数:微信用户子标识 + */ + private String subOpenId; + + // ============银联业务参数 start ============== + /** + * 交易类型 + */ + private String transType; + /** + * 授权码 + */ + private String userAuthCode; + /** + * 用户标识,对应旧系统的payerId + */ + private String userId; + /** + * 地区信息,7位,1-3为国家代码,境内业务统一为“156”,4-7位为地区码,采用《银联卡跨行业务地区代码标准》。境外商户受理的二维码业务,地区代码用全0 填充。 点击 + * [地区码](https://www.yuque.com/pandans/ws1g9s/loa46fu6isbvoksz#n3L1v) + *

+ * 例:上海市——上海市——浦东新区 则传 1562904 + */ + private String areaInfo; + /** + * 支付有效时间,允许对一个订单进行支付的最长相对时间,单位为秒。 + */ + private String paymentValidTime; + /** + * 二维码,payType=UnionPayJsapi、UnionPayJsMini时必填。 + * 二维码表示的数据,内容为特定格式的URL或TLV。其中境内二维码采用URL格式,境外二维码采用TLV格式。见附录F21. + */ + private String qrCode; + /** + * 二维码类型,payType=UnionPayJsapi、UnionPayJsMini时必填。 + */ + private String qrCodeType; + /** + * 支付方式,payType=UnionPayJsMini时必填 + */ + private String payChannel; + /** + * 微信分配的公众账号ID,payType=UnionPayJsMini时必填,上送调起云闪付微信小程序的微信APPID + */ + private String appid; + /** + * 银联支付标识,payType=UnionPayJsMini时必填,固定上送MicroMessenger + */ + private String appUpIdentifier; + // ============银联业务参数 end ============== + + + + +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/EasyPayRequest.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/EasyPayRequest.java new file mode 100644 index 000000000..f2dd3cf95 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/EasyPayRequest.java @@ -0,0 +1,24 @@ +package com.wzj.soopin.transaction.domain.bo.easypay; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * 易企通接口请求实体 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class EasyPayRequest { + + private EasyPayRequestHeader reqHeader; + + private Object reqBody; + + private String reqSign; + +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/EasyPayRequestHeader.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/EasyPayRequestHeader.java new file mode 100644 index 000000000..a18476c33 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/EasyPayRequestHeader.java @@ -0,0 +1,40 @@ +package com.wzj.soopin.transaction.domain.bo.easypay; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * 易企通接口请求头 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class EasyPayRequestHeader { + + //请求时间,当前时间:yyyyMMddHHmmss + private String transTime; + + //业务交易码 + private String transCode; + + // 请求流水号 + private String transSequence; + + // 密钥Id,商户号、机构号等 + private String reqId; + + //请求方类型,1:商户;2:拓展机构。 默认是1. 1商户模式下,reqId即为商户号mchtCode,2机构模式下,reqId即为拓展机构编号 + private String reqType; + + //证书Id,多证书情况下需要。支持同一个reqId下有多证书 + private String certificateId; + + //易生证书Id,多证书情况下需要,为空时使用默认证书 + private String easyPayCertificateId; + + +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/EasyPayResponse.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/EasyPayResponse.java new file mode 100644 index 000000000..4a08a9245 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/EasyPayResponse.java @@ -0,0 +1,24 @@ +package com.wzj.soopin.transaction.domain.bo.easypay; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * 易企通接口响应实体 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class EasyPayResponse { + + private EasyPayResponseHeader rspHeader; + + private Object rspBody; + + private String rspSign; + +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/EasyPayResponseHeader.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/EasyPayResponseHeader.java new file mode 100644 index 000000000..e8680162f --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/EasyPayResponseHeader.java @@ -0,0 +1,33 @@ +package com.wzj.soopin.transaction.domain.bo.easypay; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * 易企通接口响应头 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class EasyPayResponseHeader { + + // 应答码 000000-成功,其他失败 + private String rspCode; + + // 应答消息,错误信息 + private String rspInfo; + + //业务交易码, 同请求 + private String transCode; + + // 请求流水号,同请求 + private String transSequence; + + //易生证书Id,多证书情况下需要,为空时使用默认证书 + private String easyPayCertificateId; + +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/PayInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/PayInfo.java new file mode 100644 index 000000000..92ea2a677 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/PayInfo.java @@ -0,0 +1,42 @@ +package com.wzj.soopin.transaction.domain.bo.easypay; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 支付信息 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class PayInfo { + + /** + * 业务场景,建议上线前与产品经理确认 + * 微信订单发货业务时必填,送值说明参见2.业务指引/2.15微信订单发货 + * 建议上线前与产品经理确认 + */ + private String bizScen; + /** + * 业务类型 + * 微信订单发货业务时必填,送值说明参见2.业务指引/2.15微信订单发货 + * 建议上线前与产品经理确认 + */ + private String bizType; + /** + * 支付方式 + * @See com.wzj.soopin.transaction.enums.easypay.PayType + */ + private String payType; + /** + * 产品代码,yqt + */ + private String productCode; + /** + * 业务日期,yyyyMMdd + */ + private String transDate; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/ReqInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/ReqInfo.java new file mode 100644 index 000000000..19c94f36f --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/ReqInfo.java @@ -0,0 +1,26 @@ +package com.wzj.soopin.transaction.domain.bo.easypay; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 请求方信息 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ReqInfo { + + /** + * 商户号,合作商户号,易生提供 + */ + private String mchtCode; + /** + * 终端号,商户入网成功后可获得,客户根据自己情况选择上送,如需通过商服平台查询交易,则建议上送易生提供 + */ + private String termCode; + +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/RespStateInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/RespStateInfo.java new file mode 100644 index 000000000..624939870 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/RespStateInfo.java @@ -0,0 +1,41 @@ +package com.wzj.soopin.transaction.domain.bo.easypay; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 返回码信息,规则详见F.1 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class RespStateInfo { + + /** + * 渠道返回码 + */ + private String appendRetCode; + /** + * 渠道返回码描述 + */ + private String appendRetMsg; + /** + * 易生状态码 + */ + private String respCode; + /** + * 易生状态码描述 + */ + private String respDesc; + /** + * 交易状态码 + */ + private String transState; + /** + * 交易状态码描述 + */ + private String transStatusDesc; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/AliBizParam.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/AliBizParam.java new file mode 100644 index 000000000..1ebc06f05 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/AliBizParam.java @@ -0,0 +1,251 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.jsapi.req; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +/** + * 支付宝业务参数 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class AliBizParam { + + /** + * ali:商品详情列表,订单包含的商品列表信息,json格式。 + */ + private List aliGoodsDetail; + /** + * 商户机具终端信息 + */ + private Map> aliTerminalParams; + /** + * ali:商户传入业务信息,用于安全/营销等参数直传场景, json + */ + private String businessParams; + /** + * 买家的支付宝唯一用户号,[点击查看获取userId](https://opendocs.alipay.com/open/02xtl7) + */ + private String buyerId; + /** + * 买家支付宝账号 + */ + private String buyerLogonId; + /** + * ali:禁用支付渠道,逗号分隔 + * https://docs.open.alipay.com/common/wifww7,多个渠道以逗号分割,如同时禁用信用支付类型和积分,则disable_pay_channels="credit_group,point"https://docs.open.alipay.com/common/wifww7 + */ + private String disablePayChannels; + /** + * 可打折金额 + */ + private String discountableAmount; + /** * 扩展参数,可传支付宝能力使用分期付款信息,格式为json字符串; * 1)信用卡分期格式: * { "fq_num" : "3" , "fq_seller_percent" : "0", "fq_channels " : "alipayfq_cc"} * 2)花呗分期格式: * { "hb_fq_num" : "3" , "hb_fq_seller_percent" : "0" } */ + private String extendParams; + /** + * 实名支付信息 + */ + private AliBizParamIdentity identity; + /** + * 商户操作员编号 + */ + private String operatorId; + /** + * 描述分账信息 + */ + private RoyaltyInfo royaltyInfo; + /** + * 卖家id + */ + private String sellerId; + /** + * 商户门店编号 + */ + private String storeId; + /** + * 间联商户信息体 + */ + private SubMerchant subMerchant; + /** + * 不可打折金额 + */ + private String unDiscountableAmount; + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class AliGoodsDetail { + + /** + * 支付宝定义的统一商品编号 + */ + private String alipayGoodsId; + /** + * 商品描述信息 + */ + private String body; + /** + * 商品类目树,从商品类目根节点到叶子节点的类目id组成,类目id值使用|分割 + */ + private String categoriesTree; + /** + * 商品类目 + */ + private String goodsCategory; + /** + * 商品的编号 + */ + private String goodsId; + /** + * 商品名称 + */ + private String goodsName; + /** + * 商品单价,单位:元 + */ + private Double price; + /** + * 商品数量 + */ + private Long quantity; + /** + * 商品的展示地址 + */ + private String showUrl; + } + + /** + * 实名支付信息 + */ + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class AliBizParamIdentity { + + /** + * 证件号 + */ + private String certNo; + /** + * 证件类型,身份证: IDENTITY_CARD、 护照: PASSPORT 、 军 官 证 : OFFICER_CARD 、 士 兵 证 : SOLDIER_CARD、户口本: + * HOKOU 等。 注: need_check_info=T 时 该参数才有 IDENTITY_CARD28 效 + */ + private String certType; + /** + * 数字信封,见2.4 + */ + private String dgtlEnvlp; + /** + * 是否强制校验付款人身份信息 ,T:强制校验, F:不强制 + */ + private String fixBuyer; + /** + * 允许的最小买家年龄,买家年龄必须大于等于所传数值,买家年龄必须大 于等于所传数值 注: 1. need_check_info=T 时该参数才有效 2. min_age + * 为整数,必须大于等于 0 + */ + private Long minAge; + /** + * 手机号 + */ + private String mobile; + /** + * 姓名 + */ + private String name; + /** + * 是否强制校验身份信息,T:强制校验, F:不强制 + */ + private String needCheckInfo; + } + + /** + * 描述分账信息 + */ + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class RoyaltyInfo { + + /** + * 分账明细的信息 + */ + private List royaltyDetailInfos; + /** + * 分账类型卖家的分账类型 + */ + private String royaltyType; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class RoyaltyDetailInfo { + + /** + * 分账的金额 + */ + private long amount; + /** + * 分账的比例 + */ + private String amountPercentage; + /** + * 分账批次号 + */ + private String batchNo; + /** + * 分账描述信息 + */ + private String desc; + /** + * 商户分账的外部关联号 + */ + private String outRelationId; + /** + * 分账序列号 + */ + private String serialNo; + /** + * 如果转入账号类型为 userId,本参 数为接受分账金额的支付宝账号对应的支付宝唯一用户号 + */ + private String transIn; + /** + * 接受分账金额的账户类型 + */ + private String transInType; + /** + * 如果转出账号类型为 userId,本参数为要分账的支付宝账号对应的 支付宝唯一用户号 + */ + private String transOut; + /** + * 要分账的账户类型 + */ + private String transOutType; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class SubMerchant { + + /** + * 间连受理商户的支付宝商户编号,间连受理商户的支付宝商户编号 + */ + private String merchantId; + /** + * 商户id类型,商户id类型 + */ + private String merchantType; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/JsApiReqBody.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/JsApiReqBody.java new file mode 100644 index 000000000..866be8e3f --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/JsApiReqBody.java @@ -0,0 +1,79 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.jsapi.req; + +import com.wzj.soopin.transaction.domain.bo.easypay.PayInfo; +import com.wzj.soopin.transaction.domain.bo.easypay.ReqInfo; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.lang.Nullable; + +import java.util.List; + + +/** + * JSAPI支付接口请求体 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class JsApiReqBody { + /** + * 请求方信息 + */ + private ReqInfo reqInfo; + /** + * 基础订单信息 + */ + private JsapiReqOrderInfo reqOrderInfo; + /** + * 支付信息 + */ + private PayInfo payInfo; + /** + * 支付宝业务参数 + */ + private @Nullable AliBizParam aliBizParam; + /** + * 微信业务参数 + */ + private @Nullable WxBizParam wxBizParam; + /** + * 银联业务参数 + */ + private @Nullable QrBizParam qrBizParam; + /** + * toC营销信息,一笔交易请求中支持最多上送5条toC营销活动信息 + */ + private @Nullable List marketingOrders; + /** + * toB营销信息,1、一笔交易请求中仅可上送一个toB营销活动。 + * 2、如上送toB营销活动,则不再命中后台配置的手续费营销活动。 + * 3、活动业务说明见【2.7】。 + */ + private @Nullable List promos; + /** + * 清算信息,三个标识互斥,三个标识互斥,最多仅能其中一个送是(非0)。 + * 合单支付时,主单清算标识(本字段)不起作用,以各子单的清算标识为准。 + */ + private SettleParamInfo settleParamInfo; + /** + * 分账信息 + */ + private @Nullable SeparateInfo separateInfo; + /** + * 合单信息 + */ + private @Nullable List mergeOrders; + /** + * 合规信息 + */ + private RiskData riskData; + /** + * 其他信息 + */ + private @Nullable OtherParamInfo otherParamInfo; + + +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/JsapiReqOrderInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/JsapiReqOrderInfo.java new file mode 100644 index 000000000..9b0d7db24 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/JsapiReqOrderInfo.java @@ -0,0 +1,45 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.jsapi.req; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 基础订单信息 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class JsapiReqOrderInfo { + + /** + * 异步通知地址 + */ + private String backUrl; + /** + * 订单描述 + */ + private String orderDes; + /** + * 订单标题,对应支付宝订单里的 “商品说明”,微信订单里的“商品” + */ + private String orderSub; + /** + * 请求方自定义信息,不作处理 + */ + private String orgInfo; + /** + * 商户订单号,商户生成请求易生的流水号,同一日下商户号下唯一 + */ + private String orgTrace; + /** + * 订单超时时间,单位秒,例如3分钟送180 + */ + private String timeout; + /** + * 订单金额,单位分,最小1 + */ + private long transAmount; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/JsapiSeparateInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/JsapiSeparateInfo.java new file mode 100644 index 000000000..ba68e490f --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/JsapiSeparateInfo.java @@ -0,0 +1,45 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.jsapi.req; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 分账信息,子单支持分账 + * + * 合单子单分账信息 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class JsapiSeparateInfo { + + /** + * 商户号 + */ + private String mchtCode; + /** + * 分账批次号,分账批次流水号 + */ + private String separaBatchTrace; + /** + * 分账单信息列表 + */ + private List separateOrderDetailList; + /** + * 商户终端号 + */ + private String termCode; + /** + * 同批次分账金额合计,单位分,取值范围:1-10000000000 + */ + private long transSumAmt; + /** + * 同批次分账笔数,取值范围:1-10000000000 + */ + private long transSumCount; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/MarketingOrder.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/MarketingOrder.java new file mode 100644 index 000000000..0162482f9 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/MarketingOrder.java @@ -0,0 +1,43 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.jsapi.req; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class MarketingOrder { + /** + * 活动ID + */ + private Double activityId; + /** + * 营销金额币种,默认CNY + */ + private String marketingCurrency; + /** + * 收银单营销请求扩展信息 + */ + private Map marketingExtInfo; + /** + * 营销金额,单位分 + */ + private double marketingTransAmount; + /** + * 商户号,与reqInfo中保持一致 + */ + private String mchtCode; + /** + * 营销报名ID + */ + private Double registrationId; + /** + * 商户终端号,非必填,如上送需与reqInfo中保持一致 + */ + private String termCode; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/MergeOrder.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/MergeOrder.java new file mode 100644 index 000000000..bfc9a6fe6 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/MergeOrder.java @@ -0,0 +1,83 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.jsapi.req; + +import com.wzj.soopin.transaction.enums.easypay.DelaySettleFlag; +import com.wzj.soopin.transaction.enums.easypay.PatnerSettleFlag; +import com.wzj.soopin.transaction.enums.easypay.SplitSettleFlag; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class MergeOrder { + + /** + * 代理结算模式 + */ + private String agentStlmMode; + /** + * 币种 + */ + private String currency; + /** + * 请求方自定义信息 + */ + private String customInfo; + /** + * 延时结算标识 + */ + private DelaySettleFlag delaySettleFlag; + /** + * 下游手续费, 单位分 + */ + private Double handlingFee; + /** + * 下游机构商户唯一标识 + */ + private String orgSmerCode; + /** + * 小商户终端号 + */ + private String orgSterminalCode; + /** + * 特殊计费 + */ + private String otherFee; + /** + * D0标识,0: 非D0交易,即按进件商户开通功能结算周期结算 + * 1: D0交易,商户需开通了D0,且此字段送1时,会d0到账 + * 默认值0 + */ + private PatnerSettleFlag patnerSettleFlag; + /** + * 分账信息,子单支持分账 + */ + private JsapiSeparateInfo separateInfo; + /** + * 小商户号 + */ + private String smallMchtCode; + /** + * 分账标识,与主单分账标识互斥,不能同时上送1 + */ + private SplitSettleFlag splitSettleFlag; + /** + * 子单商户号,子单商户号 + */ + private String subMchtCode; + /** + * 商户子单流水号 + */ + private String subOrgTrace; + /** + * 子单商户终端号 + */ + private String subTermCode; + /** + * 子单商户订单金额,单位分 + */ + private double transAmount; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/OtherParamInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/OtherParamInfo.java new file mode 100644 index 000000000..b68eb3b75 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/OtherParamInfo.java @@ -0,0 +1,47 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.jsapi.req; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 其他信息 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class OtherParamInfo { + + /** + * 请求方自定义信息 + */ + private String customInfo; + /** + * 下游手续费 + */ + private Double handlingFee; + /** + * 下游机构商户唯一标识 + */ + private String orgSmerCode; + /** + * 小商户终端号 + */ + private String orgSterminalCode; + /** + * 特殊计费 + */ + private List otherFee; + /** + * 所属项目 + */ + private String projectId; + /** + * 小商户号 + */ + private String smallMchtCode; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/Promo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/Promo.java new file mode 100644 index 000000000..25b826c9e --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/Promo.java @@ -0,0 +1,22 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.jsapi.req; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class Promo { + + /** + * 营销活动ID + */ + private String promoCode; + /** + * 营销金额,单位分,必须小于等于交易应收手续费金额。 + */ + private String promoFeeAmount; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/QrBizParam.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/QrBizParam.java new file mode 100644 index 000000000..3eabe2623 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/QrBizParam.java @@ -0,0 +1,282 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.jsapi.req; + +import com.wzj.soopin.transaction.enums.easypay.IDCheckIn; +import com.wzj.soopin.transaction.enums.easypay.LimitCreditPay; +import com.wzj.soopin.transaction.enums.easypay.PayChannel; +import com.wzj.soopin.transaction.enums.easypay.QrCodeType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 银联业务参数 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class QrBizParam { + + /** + * AcqAddnData + */ + private AcqAddnData acqAddnData; + /** + * 微信分配的公众账号ID,payType=UnionPayJsMini时必填,上送调起云闪付微信小程序的微信APPID + */ + private String appid; + /** + * 银联支付标识,payType=UnionPayJsMini时必填,固定上送MicroMessenger + */ + private String appUpIdentifier; + /** + * 地区信息,7位,1-3为国家代码,境内业务统一为“156”,4-7位为地区码,采用《银联卡跨行业务地区代码标准》。境外商户受理的二维码业务,地区代码用全0 填充。 点击 + * [地区码](https://www.yuque.com/pandans/ws1g9s/loa46fu6isbvoksz#n3L1v) + *

+ * 例:上海市——上海市——浦东新区 则传 1562904 + */ + private String areaInfo; + /** + * 优惠码 + */ + private String discountCode; + /** + * 前台失败通知地址,与frontUrl同时出现。 + * APP侧未成功完成支付时调 + * 用,银联将通过GET方式访问 + * 本地址,银联将在frontFailUrl + * 后附加付款中止原因码,原因 + * 码error_code取值如下: + * 01 - APP付款失败 + * 91 - 用户取消支付 + */ + private String frontFailUrl; + /** + * 前台通知地址 + */ + private String frontUrl; + /** + * 实名支付验证标识 + */ + private IDCheckIn idCheckIn; + /** + * 实名支付验证地址,当idCheckIn 取值为“1”时出现 + */ + private String idCheckUrl; + /** + * UnionIdentity + */ + private QrBizParamIdentity identity; + /** + * 禁用支付渠道 + */ + private LimitCreditPay limitCreditPay; + /** + * 商户交易索引,若商户必须使用自身交易索引,且无法使用orderNo作为交易主键发起撤销、退货交易时出现。 + */ + private String merTransIndex; + /** + * 是否支持发票 + */ + private String needReceipt; + /** + * 支付方式,payType=UnionPayJsMini时必填 + */ + private PayChannel payChannel; + /** + * PayeeInfo + */ + private PayeeInfo payeeInfo; + /** + * 支付有效时间,允许对一个订单进行支付的最长相对时间,单位为秒。 + */ + private long paymentValidTime; + /** + * 银联服务商信息,json格式,参数值示例如下: + *

+ * {"pnrOrderId":"1220250321034502483875104", "pidSct" :"lj8352@da099%ldang", "tradeScene" + * :"1"} + */ + private String pidInfo; + /** + * 二维码,payType=UnionPayJsapi、UnionPayJsMini时必填。 + * 二维码表示的数据,内容为特定格式的URL或TLV。其中境内二维码采用URL格式,境外二维码采用TLV格式。见附录F21. + */ + private String qrCode; + /** + * 二维码类型,payType=UnionPayJsapi、UnionPayJsMini时必填。 + */ + private QrCodeType qrCodeType; + /** + * 银联服务商机构标识码,这是银联的渠道商号,类似微信渠道商号24006513 + */ + private String qrPnrInsIdCd; + /** + * 请求方自定义域 + */ + private String reqReserved; + /** + * 交易类型,参考 + * [附录F1.2](https://apifox.com/apidoc/shared/9758ecc8-2c38-4ec6-914f-b09be6f563bc/doc-5524664) + */ + private String transType; + /** + * 授权码 + */ + private String userAuthCode; + /** + * 用户标识,对应旧系统的payerId + */ + private String userId; + + + /** + * AcqAddnData + */ + @lombok.Data + public static class AcqAddnData { + /** + * 自定义数据 + */ + private String customData; + /** + * 商品信息,商品明细内容 + */ + private List goodsInfos; + /** + * QrOrderInfo + */ + private OrderInfo orderInfo; + } + + /** + * QrGoodsInfo,白条支付下,如涉及贴息信息,可在本参数下上送信息,参数说明详见F22 + */ + @Data + public static class GoodsInfo { + /** + * 商品简述信息 + */ + private String body; + /** + * 商品类目树 + */ + private String categoriesTree; + /** + * 商品类目 + */ + private String goodsCategory; + /** + * 商品编号 + */ + private String goodsId; + /** + * 商品名称 + */ + private String goodsName; + /** + * 商品单价 + */ + private long price; + /** + * 商品数量 + */ + private long quantity; + /** + * 商品的展示地址 + */ + private String showUrl; + /** + * 支付宝定义的统一商品编号 + * 第三方商品编号 + */ + private String thirdGoodsId; + } + + /** + * QrOrderInfo + */ + @Data + public static class OrderInfo { + /** + * 附件信息 + *

+ * 如需京东白条支付,可在此参数下上送白条相应参数,详细参数见附录F22 + */ + private String addnInfo; + /** + * 可优惠金额 + */ + private String dctAmount; + /** + * 订单描述 + */ + private String description; + /** + * 订单标题 + */ + private String title; + } + + /** + * UnionIdentity + */ + @lombok.Data + public static class QrBizParamIdentity { + /** + * 证件号 + */ + private String certNo; + /** + * 证件类型 + */ + private String certType; + /** + * 数字信封 + */ + private String dgtlEnvlp; + /** + * 手机号 + */ + private String mobile; + /** + * 证件姓名 + */ + private String name; + } + + /** + * PayeeInfo + */ + @lombok.Data + public static class PayeeInfo { + /** + * 商户类别 + */ + private String mchtMccCode; + /** + * 商户英文名称 + */ + private String nameEng; + /** + * 收款方账号 + */ + private String payeeAcctNo; + /** + * 账号类型 + */ + private String payeeAcctType; + /** + * 二级商户代码 + */ + private String subMchtCode; + /** + * 二级商户名称 + */ + private String subMchtName; + } + +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/RiskData.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/RiskData.java new file mode 100644 index 000000000..057108229 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/RiskData.java @@ -0,0 +1,137 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.jsapi.req; + + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 合规信息 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class RiskData { + + /** + * 用户ip + */ + private String customerIp; + /** + * 终端信息 + */ + private TerminalInfo terminalInfo; + + /** + * 终端信息 + */ + @Data + public static class TerminalInfo { + /** + * 终端版本号 + */ + private String appVersion; + /** + * 终端位置地区编码 + */ + private String areaNo; + /** + * 终端位置国家编码 + */ + private String ctryNo; + /** + * 加密随机数 + */ + private String encryptRandNum; + /** + * ICCID,SIM卡卡号 + */ + private String iccId; + /** + * 基站编号 1 + */ + private String lbsNum1; + /** + * 基站编号 2 + */ + private String lbsNum2; + /** + * 基站编号 3 + */ + private String lbsNum3; + /** + * 基站信号 1 + */ + private String lbsSignal1; + /** + * 基站信号 2 + */ + private String lbsSignal2; + /** + * 基站信号 3 + */ + private String lbsSignal3; + /** + * 终端实时经纬度信息 + */ + private String location; + /** + * 位置区域码 1 + */ + private String locationCd1; + /** + * 位置区域码 2 + */ + private String locationCd2; + /** + * 位置区域码 3 + */ + private String locationCd3; + /** + * 基站信息,移动国家代码 + */ + private String mobileCountryCd; + /** + * 基站信息,移动网络号码 + */ + private String mobileNetNum; + /** + * 网络授权 + */ + private String networkLicense; + /** + * 密钥文本 + */ + private String secretText; + /** + * 终端序列号 + */ + private String serialNum; + /** + * 电信基站信号 + */ + private String telecomLbs; + /** + * 电信网络识别码 + */ + private String telecomNetId; + /** + * 电信系统识别码 + */ + private String telecomSysId; + /** + * 终端编号 + */ + private String terminalId; + /** + * 终端IP,终端信息非必填,但如果送了终端信息,则terminalIp必填 + */ + private String terminalIp; + /** + * 终端类型 + */ + private String terminalType; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/SeparateInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/SeparateInfo.java new file mode 100644 index 000000000..4f4972f14 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/SeparateInfo.java @@ -0,0 +1,87 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.jsapi.req; + +import com.wzj.soopin.transaction.enums.easypay.DelaySettleFlag; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 分账信息 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SeparateInfo { + + /** + * 分账商户号,即发起分账方,分账资金来源方 + */ + private String oriSeparateMchtCode; + /** + * 分账流水号,商户号下唯一 + */ + private String separaBatchTrace; + /** + * 分账订单明细 + */ + private List separateOrderDetailList; + /** + * 分账订单总金额,单位分 + */ + private long transSumAmt; + /** + * 分账订单总笔数 + */ + private long transSumCount; + + /** + * 分账订单明细 + */ + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class SeparateOrderDetailList { + + /** + * 分账订单标题 + */ + private String body; + /** + * 分账D0标识,D0需提前开通 + */ + private DelaySettleFlag isD0; + /** + * 分账收款方商户号,可以商户号或分账接收方编号 + */ + private String receiveMchtCode; + /** + * 分账手续费承担本金金额,金额和比例2选1必填 + */ + private Long sepaFeeAmount; + /** + * 分账手续费承担本金比例,金额和比例2选1必填,30代表30%,仅可上送整数 + */ + private Long sepaFeeRatio; + /** + * 分账子单流水号 + */ + private String separateTrade; + /** + * 分账比例,分账金额和分账比例2选一必填,30代表30%,仅可上送整数 + */ + private Long sepaRatio; + /** + * 分账金额,分账金额和分账比例2选一必填,单位分 + */ + private Long sepaTransAmount; + /** + * 分账订单描述 + */ + private String subject; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/SettleParamInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/SettleParamInfo.java new file mode 100644 index 000000000..f6527a9ee --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/SettleParamInfo.java @@ -0,0 +1,35 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.jsapi.req; + +import com.wzj.soopin.transaction.enums.easypay.DelaySettleFlag; +import com.wzj.soopin.transaction.enums.easypay.PatnerSettleFlag; +import com.wzj.soopin.transaction.enums.easypay.SplitSettleFlag; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 清算信息,三个标识互斥,三个标识互斥,最多仅能其中一个送是(非0)。 + * 合单支付时,主单清算标识(本字段)不起作用,以各子单的清算标识为准。 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SettleParamInfo { + /** + * 延时结算标识 + * {@link DelaySettleFlag} + */ + private String delaySettleFlag; + /** + * D0标识 + * {@link PatnerSettleFlag} + */ + private String patnerSettleFlag; + /** + * 分账标识 + * {@link SplitSettleFlag} + */ + private String splitSettleFlag; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/Specialfee.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/Specialfee.java new file mode 100644 index 000000000..2afbe6ebd --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/Specialfee.java @@ -0,0 +1,23 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.jsapi.req; + +import com.wzj.soopin.transaction.enums.easypay.SpecialTag; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class Specialfee { + + /** + * 费用金额,单位分 + */ + private long feeAmount; + /** + * 费用类型 + */ + private SpecialTag specialTag; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/WxBizParam.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/WxBizParam.java new file mode 100644 index 000000000..112675322 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/req/WxBizParam.java @@ -0,0 +1,201 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.jsapi.req; + +import com.wzj.soopin.transaction.enums.easypay.WxLimitPay; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 微信业务参数 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class WxBizParam { + + /** + * 微信分配的公众账号ID,微信分配的公众账号ID(企业号corpid即为此appid) + */ + private String appId; + /** + * 附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据 + */ + private String attach; + /** + * 单品优惠功能字段 + */ + private Detail detail; + /** + * 订单优惠标记,用于区分订单是否可以享受优惠,字段内容在微信后台配置券时进行设置 + */ + private String goodsTag; + /** + * 实名信息 + */ + private WxBizParamIdentity identity; + /** + * 是否支持发票 + */ + private Boolean needReceip; + /** + * 用户标识 + */ + private String openId; + /** + * 商品描述 + */ + private String orderBody; + /** + * 微信场景信息,该字段用于上报场景信息,目前支持上报实际门店信息。该字段为JSON对象数据,对象格式为{"store_info":{"id": "门店ID","name": + * "名称","area_code": "编码","address": "地址" }} + */ + private SceneInfo sceneInfo; + /** + * 微信分配的子商户公众账号ID + */ + private String subAppid; + /** + * 用户子标识,[点击查看获取openid](https://pay.weixin.qq.com/doc/v3/partner/4012081935) + */ + private String subOpenId; + /** + * 交易限制支付类型 + */ + private WxLimitPay wxLimitPay; + + + + /** + * 单品优惠功能字段 + */ + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class Detail { + /** + * 订单原价,1.商户侧一张小票订单可能被分多次支付,订单原价用于记录整张小票的交易金额。
2.当订单原价与支付金额不相等,则不享受优惠。
3.该字段主要用于防止同一张小票分多次支付,以享受多次优惠的情况,正常支付订单不必上传此参数。 + */ + private Long costPrice; + /** + * 单品列表,单品信息,使用Json数组格式提交 + */ + private List goodsDetail; + /** + * 商家小票ID,商家小票ID + */ + private String receiptid; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class GoodsDetail { + /** + * 商品编码,由半角的大小写字母、数字、中划线、下划线中的一种或几种组成 + */ + private String goodsid; + /** + * 商品名称,商品的实际名称 + */ + private String goodsName; + /** + * 商品单价,单位为:分。如果商户有优惠,需传输商户优惠后的单价(例如:用户对一笔100元的订单使用了商场发的优惠券100-50,则活动商品的单价应为原单价-50) + */ + private Long price; + /** + * 商品数量,用户购买的数量 + */ + private Long quantity; + /** + * 微信侧商品编码,微信支付定义的统一商品编号(没有可不传) + */ + private String wxpayGoodsid; + } + + /** + * 实名信息 + */ + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class WxBizParamIdentity { + /** + * 证件号 + */ + private String certNo; + /** + * 证件类型 + */ + private String certType; + /** + * 数字信封 + */ + private String dgtlEnvlp; + /** + * 手机号 + */ + private String mobile; + /** + * 姓名 + */ + private String name; + } + + /** + * 微信场景信息,该字段用于上报场景信息,目前支持上报实际门店信息。该字段为JSON对象数据,对象格式为{"store_info":{"id": "门店ID","name": + * "名称","area_code": "编码","address": "地址" }} + */ + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class SceneInfo { + /** + * 商户端设备号,商户端设备号 + */ + private String deviceid; + /** + * 用户的客户端IP,用户的客户端IP + */ + private String payerClientip; + /** + * 门店信息,门店信息 + */ + private StoreInfo storeInfo; + } + + /** + * 门店信息,门店信息 + */ + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class StoreInfo { + /** + * 门店详细地址,门店详细地址 + */ + private String address; + /** + * 门店所在地行政区划码,门店所在地行政区划码 + */ + private String areaCode; + /** + * 门店唯一标识,门店唯一标识 + */ + private String id; + /** + * 门店名称,门店名称 + */ + private String name; + } + +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/resp/AliRespParamInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/resp/AliRespParamInfo.java new file mode 100644 index 000000000..19e9fced4 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/resp/AliRespParamInfo.java @@ -0,0 +1,22 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.jsapi.resp; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * ali业务参数 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class AliRespParamInfo { + + /** + * 调起支付宝支付参数,调起支付宝支付参考 + * [附录F.15](https://apifox.com/apidoc/shared/9758ecc8-2c38-4ec6-914f-b09be6f563bc/doc-5662571) + */ + private String tradeNo; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/resp/JsApiRespBody.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/resp/JsApiRespBody.java new file mode 100644 index 000000000..e0b3d7f51 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/resp/JsApiRespBody.java @@ -0,0 +1,39 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.jsapi.resp; + +import com.wzj.soopin.transaction.domain.bo.easypay.RespStateInfo; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * JSAPI支付接口响应 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class JsApiRespBody { + + /** + * 返回码信息,规则详见F.1 + */ + private RespStateInfo respStateInfo; + /** + * 基础订单信息 + */ + private JsapiRespOrderInfo respOrderInfo; + /** + * ali业务参数 + */ + private AliRespParamInfo aliRespParamInfo; + /** + * wx业务参数 + */ + private WxJsApiRespParamInfoVO wxRespParamInfo; + /** + * 银联业务参数 + */ + private QrJsApiRespParamInfoVO qrRespParamInfo; + +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/resp/JsapiRespOrderInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/resp/JsapiRespOrderInfo.java new file mode 100644 index 000000000..a65397e0a --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/resp/JsapiRespOrderInfo.java @@ -0,0 +1,151 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.jsapi.resp; + +import com.wzj.soopin.transaction.enums.easypay.AddCalcType; +import com.wzj.soopin.transaction.enums.easypay.BillingCycle; +import com.wzj.soopin.transaction.enums.easypay.CardType; +import com.wzj.soopin.transaction.enums.easypay.FeeType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 基础订单信息 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class JsapiRespOrderInfo { + /** + * 商户费率信息 + */ + private Empty mchtRateList; + /** + * 商户订单号,商户生成请求易生的流水号 + */ + private String orgTrace; + /** + * 易生订单号,易生生成后上送渠道的订单号 + */ + private String outTrace; + /** + * 渠道订单号,渠道生成返回给易生的订单号,例如微信、支付宝订单号 + */ + private String pcTrace; + /** + * 产品订单号,易生系统内流转的订单号求易生的流水号 + */ + private String productTrace; + /** + * 交易金额,单位分 + */ + private Long transAmount; + /** + * 银联订单号 + */ + private String unTrace; + + /** + * 商户费率信息 + */ + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + private static class Empty { + + /** + * 附加手续费计算方式 + */ + private AddCalcType addCalcType; + /** + * 附加手续费值,单位:分 + */ + private String addCalcVal; + /** + * 交易金额最高值,单位:分 + */ + private String amtHighLt; + /** + * 交易金额最低值,单位:分 + */ + private String amtLowLt; + /** + * 结算周期 + */ + private BillingCycle billingCycle; + /** + * 手续费计算方式 + */ + private AddCalcType calcType; + /** + * 手续费费率值,按笔单位为分;按比例为百万比 + */ + private String calcVal; + /** + * 卡机构编号,见F.10 + */ + private String cardIssuerCode; + /** + * 卡类型 + */ + private CardType cardType; + /** + * 生效时间 + */ + private String effDatetime; + /** + * 失效时间 + */ + private String expDatetime; + /** + * 手续费处理模式 + */ + private String feeCalcMode; + /** + * 手续费最高值,单位:分 + */ + private String feeHighLimit; + /** + * feeId + */ + private String feeId; + /** + * 手续费承担机构 + */ + private String feeInst; + /** + * 手续费最低值,单位:分 + */ + private String feeLowLimit; + /** + * 手续费费用类型 + */ + private AddCalcType feeMode; + /** + * 手续费处理模式 + */ + private FeeType feeType; + /** + * 分期期数 + */ + private String instalNum; + /** + * 是否需求配置费率 + */ + private String isNeedConfigFee; + /** + * 是否允许退货 + */ + private AddCalcType isRefundFee; + /** + * 优先级 + */ + private String levelOrder; + /** + * 内部商户号 + */ + private String mchtCode; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/resp/QrJsApiRespParamInfoVO.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/resp/QrJsApiRespParamInfoVO.java new file mode 100644 index 000000000..c25310048 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/resp/QrJsApiRespParamInfoVO.java @@ -0,0 +1,21 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.jsapi.resp; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 银联业务参数 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class QrJsApiRespParamInfoVO { + + /** + * 云闪付支付调用凭证,payType=UnionPayJsMini时,需要用此参数拼接跳转至云微小程序,具体跳转方法见F.17. + */ + private String qrRedirectUrl; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/resp/WxJsApiRespParamInfoVO.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/resp/WxJsApiRespParamInfoVO.java new file mode 100644 index 000000000..f669da39c --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/jsapi/resp/WxJsApiRespParamInfoVO.java @@ -0,0 +1,26 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.jsapi.resp; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * wx业务参数 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class WxJsApiRespParamInfoVO { + /** + * 调起微信支付参数,调起微信支付调起参考 + * [附录F.15](https://apifox.com/apidoc/shared/9758ecc8-2c38-4ec6-914f-b09be6f563bc/doc-5662571) + */ + private String wcPayData; + + /** + * 预支付交易会话标识 + */ + private String prepayId; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/refund/apply/req/QrCodeRefundExt.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/refund/apply/req/QrCodeRefundExt.java new file mode 100644 index 000000000..5c8954c31 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/refund/apply/req/QrCodeRefundExt.java @@ -0,0 +1,85 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.refund.apply.req; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * AT交易退货请求扩展信息 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class QrCodeRefundExt { + /** + * 退款货币种类 + */ + private String currency; + /** + * 商品详情列表 + */ + private List goodsDetail; + /** + * 商户的操作员编号 + */ + private String operatorId; + /** + * 查询选项 + */ + private List queryOption; + /** + * 退款原因 + */ + private String refundReason; + /** + * 商户的门店编号 + */ + private String storeCode; + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class GoodsDetail { + /** + * 支付宝定义的统一商品编号 + */ + private String alipayGoodsid; + /** + * 商品描述信息 + */ + private String body; + /** + * 商品类目树 + */ + private String categoriesTree; + /** + * 商品类目 + */ + private String goodsCategory; + /** + * 商品的编号 + */ + private String goodsid; + /** + * 商品名称 + */ + private String goodsName; + /** + * 商品单价(单位:元) + */ + private Double price; + /** + * 商品数量 + */ + private Long quantity; + /** + * 商品的展示地址 + */ + private String showurl; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/refund/apply/req/RefundApplyReqBody.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/refund/apply/req/RefundApplyReqBody.java new file mode 100644 index 000000000..0bac45a30 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/refund/apply/req/RefundApplyReqBody.java @@ -0,0 +1,34 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.refund.apply.req; + +import com.wzj.soopin.transaction.domain.bo.easypay.PayInfo; +import com.wzj.soopin.transaction.domain.bo.easypay.ReqInfo; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 发起退款请求体 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class RefundApplyReqBody { + /** + * 请求方信息 + */ + private ReqInfo reqInfo; + /** + * 基础订单信息 + */ + private RefundReqOrderInfo reqOrderInfo; + /** + * AT交易退货请求扩展信息 + */ + private QrCodeRefundExt qrCodeRefundExt; + /** + * 支付信息 + */ + private PayInfo payInfo; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/refund/apply/req/RefundReqOrderInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/refund/apply/req/RefundReqOrderInfo.java new file mode 100644 index 000000000..e35d7c360 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/refund/apply/req/RefundReqOrderInfo.java @@ -0,0 +1,40 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.refund.apply.req; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 基础订单信息 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class RefundReqOrderInfo { + /** + * 下游手续费 + */ + private Double handlingFee; + /** + * 商户订单号,商户生成请求易生的流水号 + */ + private String orgTrace; + /** + * 原商户订单号,传的是支付交易的orgTrace,代表要退哪笔支付交易, 可以是主单订单号和子单订单号 + */ + private String oriOrgTrace; + /** + * 原订单outTrace,oriOrgTrace与oriOutTrace二选一必填 + */ + private String oriOutTrace; + /** + * 原业务日期,原交易业务日期,格式:yyyyMMdd + */ + private String oriTransDate; + /** + * 退款金额,单位分 separate + */ + private long refundAmount; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/refund/apply/resp/RefundApplyRespBody.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/refund/apply/resp/RefundApplyRespBody.java new file mode 100644 index 000000000..47b92d8d5 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/refund/apply/resp/RefundApplyRespBody.java @@ -0,0 +1,32 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.refund.apply.resp; + +import com.wzj.soopin.transaction.domain.bo.easypay.RespStateInfo; +import com.wzj.soopin.transaction.domain.bo.easypay.trade.query.resp.SettleRespParamInfo; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 发起退款响应体 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class RefundApplyRespBody { + + /** + * 基础订单信息 + */ + private RefundRespOrderInfo respOrderInfo; + /** + * 返回码信息,详见 + * [附录F.1](https://apifox.com/apidoc/shared/9758ecc8-2c38-4ec6-914f-b09be6f563bc/doc-5523713) + */ + private RespStateInfo respStateInfo; + /** + * 清算信息 + */ + private SettleRespParamInfo settleRespParamInfo; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/refund/apply/resp/RefundRespOrderInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/refund/apply/resp/RefundRespOrderInfo.java new file mode 100644 index 000000000..831a0cd96 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/refund/apply/resp/RefundRespOrderInfo.java @@ -0,0 +1,49 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.refund.apply.resp; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class RefundRespOrderInfo { + /** + * 请求方自定义信息 + */ + private String orgInfo; + /** + * 商户订单号,商户生成请求易生的流水号 + */ + private String orgTrace; + /** + * 原商户订单号 + */ + private String oriOrgTrace; + /** + * 原易生订单号 + */ + private String oriOutTrace; + /** + * 易生订单号,易生生成后上送渠道的订单号 + */ + private String outTrace; + /** + * 渠道订单号,渠道生成返回给易生的订单号,例如微信、支付宝订单号 + */ + private String pcTrace; + /** + * 产品订单号 + */ + private String productTrace; + /** + * 退货金额,单位分 + */ + private Long refundAmount; + /** + * 银联订单号 + */ + private String unTrace; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/refund/query/req/RefundQueryReqBody.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/refund/query/req/RefundQueryReqBody.java new file mode 100644 index 000000000..3b560dd33 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/refund/query/req/RefundQueryReqBody.java @@ -0,0 +1,31 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.refund.query.req; + +import com.wzj.soopin.transaction.domain.bo.easypay.PayInfo; +import com.wzj.soopin.transaction.domain.bo.easypay.ReqInfo; +import com.wzj.soopin.transaction.domain.bo.easypay.refund.apply.req.RefundReqOrderInfo; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 退款查询请求体 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class RefundQueryReqBody { + /** + * 支付信息 + */ + private PayInfo payInfo; + /** + * 请求方信息 + */ + private ReqInfo reqInfo; + /** + * 基础订单信息 + */ + private RefundReqOrderInfo reqOrderInfo; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/apply/req/SeparateApplyReqBody.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/apply/req/SeparateApplyReqBody.java new file mode 100644 index 000000000..003fe6bda --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/apply/req/SeparateApplyReqBody.java @@ -0,0 +1,30 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.separate.apply.req; + +import com.wzj.soopin.transaction.domain.bo.easypay.PayInfo; +import com.wzj.soopin.transaction.domain.bo.easypay.ReqInfo; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 发起分账请求体 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SeparateApplyReqBody { + /** + * 支付信息 + */ + private PayInfo payInfo; + /** + * 请求方信息 + */ + private ReqInfo reqInfo; + /** + * 基础订单信息 + */ + private SeparateReqOrderInfo reqOrderInfo; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/apply/req/SeparateReqOrderInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/apply/req/SeparateReqOrderInfo.java new file mode 100644 index 000000000..bedadcf35 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/apply/req/SeparateReqOrderInfo.java @@ -0,0 +1,63 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.separate.apply.req; + +import com.wzj.soopin.transaction.domain.bo.easypay.jsapi.req.SeparateInfo; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SeparateReqOrderInfo { + /** + * 异步通知地址 + */ + private String backUrl; + /** + * 原商户交易的orgTrace,需要对哪笔交易进行分账,就传那笔交易的orgTrace。就是支付交易的orgTrace + */ + private String oriOrgTrace; + /** + * 原商户交易日期 + */ + private String oriTransDate; + /** + * 营销信息,仅支持ToB营销活动 + */ + private List promoList; + /** + * 分账总单流水号,分账批次号,自定义,要唯一 + */ + private String separateBatchTrace; + /** + * 分账子单明细,可送多条 + */ + private List separateOrderDetailList; + /** + * 分账订单总金额,单位分 + */ + private long transSumAmt; + /** + * 分账订单总笔数 + */ + private long transSumCount; + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class PromoList { + /** + * 营销ID + */ + private String promoCode; + /** + * 营销手续费金额 + */ + private long promoFeeAmount; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/apply/resp/SeparateApplyRespBody.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/apply/resp/SeparateApplyRespBody.java new file mode 100644 index 000000000..9b8bd481a --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/apply/resp/SeparateApplyRespBody.java @@ -0,0 +1,25 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.separate.apply.resp; + +import com.wzj.soopin.transaction.domain.bo.easypay.RespStateInfo; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 发起分账响应体 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SeparateApplyRespBody { + /** + * 基础订单信息 + */ + private SeparateRespOrderInfo respOrderInfo; + /** + * 返回码信息 + */ + private RespStateInfo respStateInfo; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/apply/resp/SeparateRespInfoList.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/apply/resp/SeparateRespInfoList.java new file mode 100644 index 000000000..5b7e020fc --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/apply/resp/SeparateRespInfoList.java @@ -0,0 +1,70 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.separate.apply.resp; + +import com.wzj.soopin.transaction.enums.easypay.DelaySettleFlag; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SeparateRespInfoList { + /** + * 分账单标题 + */ + private String body; + /** + * 分账D0标识 + */ + private DelaySettleFlag isD0; + /** + * 分账收款方商户号 + */ + private String receiveMchtCode; + /** + * 当前分账单已退款金额总计 + */ + private Long refundAmountSum; + /** + * 分账手续费承担本金金额 + */ + private Long sepaFeeAmount; + /** + * 分账手续费承担本金比例 + */ + private Long sepaFeeRatio; + /** + * 分账平台应结金额 + */ + private Long sepaPlatStlmAmount; + /** + * 分账单流水号,同一日期内唯一,否则会因流水号重复而失败 + */ + private String separateTrade; + /** + * 分账比例 + */ + private Long sepaRatio; + /** + * 分账单状态,(处理中、成功、失败、已退款) + */ + private String sepaStatus; + /** + * 分账单状态说明 + */ + private String sepaStatusDesc; + /** + * 分账实际结算金额 + */ + private Long sepaStlmAmount; + /** + * 分账金额,单位分 + */ + private Long sepaTransAmount; + /** + * 分账订单描述 + */ + private String subject; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/apply/resp/SeparateRespOrderInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/apply/resp/SeparateRespOrderInfo.java new file mode 100644 index 000000000..054514439 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/apply/resp/SeparateRespOrderInfo.java @@ -0,0 +1,40 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.separate.apply.resp; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SeparateRespOrderInfo { + + /** + * 同批次分账笔数 + */ + private Long batchNum; + /** + * 同批次分账金额合计 + */ + private Long batchTransAmount; + /** + * 原商户交易的orgTrace + */ + private String oriOrgTrace; + /** + * 产品订单号 + */ + private String productTrace; + /** + * 分账商户批次号,同一日期内唯一,否则会因为流水号重复失败 + */ + private String separateBatchTrace; + /** + * 分账单信息列表 + */ + private List separateRespInfoList; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/cancel/req/CancelReqOrderInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/cancel/req/CancelReqOrderInfo.java new file mode 100644 index 000000000..e62e41062 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/cancel/req/CancelReqOrderInfo.java @@ -0,0 +1,33 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.separate.cancel.req; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class CancelReqOrderInfo { + /** + * 商户订单号,商户生成请求易生的流水号 + */ + private String orgTrace; + /** + * 原分账总单流水 + */ + private String oriSeparateBatchTrace; + /** + * 原分账子单流水号 + */ + private String oriSeparateTrade; + /** + * 原业务日期 + */ + private String oriTransDate; + /** + * 回退金额 + */ + private long refundAmount; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/cancel/req/SeparateCancelReqBody.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/cancel/req/SeparateCancelReqBody.java new file mode 100644 index 000000000..36d0c5fcd --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/cancel/req/SeparateCancelReqBody.java @@ -0,0 +1,30 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.separate.cancel.req; + +import com.wzj.soopin.transaction.domain.bo.easypay.PayInfo; +import com.wzj.soopin.transaction.domain.bo.easypay.ReqInfo; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 分账回退请求体 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SeparateCancelReqBody { + /** + * 支付信息 + */ + private PayInfo payInfo; + /** + * 请求方信息 + */ + private ReqInfo reqInfo; + /** + * 基础订单信息 + */ + private CancelReqOrderInfo reqOrderInfo; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/cancel/resp/CancelRespOrderInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/cancel/resp/CancelRespOrderInfo.java new file mode 100644 index 000000000..78af9ebd7 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/cancel/resp/CancelRespOrderInfo.java @@ -0,0 +1,106 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.separate.cancel.resp; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class CancelRespOrderInfo { + /** + * 同批次分账笔数 + */ + private Long batchNum; + /** + * 同批次分账金额合计 + */ + private Long batchTransAmount; + /** + * 商户订单号,商户生成请求易生的流水号,每次都要唯一 + */ + private String orgTrace; + /** + * 原商户订单号 + */ + private String oriOrgTrace; + /** + * 产品订单号 + */ + private String productTrace; + /** + * 原分账批次号,原分账总单流水号 + */ + private String separateBatchTrace; + /** + * 分账单信息列表 + */ + private List separateRespInfoList; + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class SeparateRespInfoList { + /** + * 分账订单标题,请求方上送 + */ + private String body; + /** + * 分账-秒到标识(1:是 0:否) + */ + private String isD0; + /** + * 分账收款方商户号,请求方上送 + */ + private String receiveMchtCode; + /** + * 当前分账单已退款金额总计 + */ + private Long refundAmountSum; + /** + * 分账手续费承担金额(金额和比例都支持传,2选1),根据资产单金额来算比例,请求方上送 + */ + private Long sepaFeeAmount; + /** + * 分账手续费承担比例(金额和比例都支持传,2选1),30代表百万分之30,请求方上送 + */ + private Long sepaFeeRatio; + /** + * 分账平台应结金额 + */ + private Long sepaPlatStlmAmount; + /** + * 分账单商户订单号,请求方上送 + */ + private String separateTrade; + /** + * 分账比例,请求方上送 + */ + private Long sepaRatio; + /** + * 分账单状态(处理中、成功、失败、已退款) + */ + private String sepaStatus; + /** + * 分账单状态说明 + */ + private String sepaStatusDesc; + /** + * 分账实际结算金额 + */ + private Long sepaStlmAmount; + /** + * 分账金额,请求方上送 + */ + private Long sepaTransAmount; + /** + * 分账订单描述,请求方上送 + */ + private String subject; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/cancel/resp/SeparateCancelRespBody.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/cancel/resp/SeparateCancelRespBody.java new file mode 100644 index 000000000..0abb87c8f --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/cancel/resp/SeparateCancelRespBody.java @@ -0,0 +1,25 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.separate.cancel.resp; + +import com.wzj.soopin.transaction.domain.bo.easypay.RespStateInfo; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 分账回退响应体 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SeparateCancelRespBody { + /** + * 基础订单信息 + */ + private CancelRespOrderInfo respOrderInfo; + /** + * 返回码信息 + */ + private RespStateInfo respStateInfo; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/query/req/CancelQueryReqOrderInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/query/req/CancelQueryReqOrderInfo.java new file mode 100644 index 000000000..749071172 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/query/req/CancelQueryReqOrderInfo.java @@ -0,0 +1,21 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.separate.query.req; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class CancelQueryReqOrderInfo { + /** + * 原分账批次流水separateBatchTrace,原分账总单流水号 + */ + private String oriSeparateBatchTrace; + /** + * 原请求分账日期 + */ + private String oriTransDate; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/query/req/SeparateQueryReqBody.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/query/req/SeparateQueryReqBody.java new file mode 100644 index 000000000..bcf19b0f5 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/query/req/SeparateQueryReqBody.java @@ -0,0 +1,30 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.separate.query.req; + +import com.wzj.soopin.transaction.domain.bo.easypay.PayInfo; +import com.wzj.soopin.transaction.domain.bo.easypay.ReqInfo; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 分账查询请求体 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SeparateQueryReqBody { + /** + * 支付信息 + */ + private PayInfo payInfo; + /** + * 请求方信息 + */ + private ReqInfo reqInfo; + /** + * 基础订单信息 + */ + private CancelQueryReqOrderInfo reqOrderInfo; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/query/resp/CancelQueryRespOrderInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/query/resp/CancelQueryRespOrderInfo.java new file mode 100644 index 000000000..fb3ab66dd --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/query/resp/CancelQueryRespOrderInfo.java @@ -0,0 +1,99 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.separate.query.resp; + +import com.wzj.soopin.transaction.enums.easypay.SepaStatus; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class CancelQueryRespOrderInfo { + /** + * 同批次分账笔数 + */ + private Long batchNum; + /** + * 同批次分账金额合计 + */ + private Long batchTransAmount; + /** + * 分账总单流水号 + */ + private String oriSeparateBatchTrace; + /** + * 产品订单号 + */ + private String productTrace; + /** + * 分账单信息列表 + */ + private List separateRespInfoList; + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class SeparateRespInfoList { + /** + * 分账订单标题 + */ + private String body; + /** + * 分账D0标识 + */ + private String isD0; + /** + * 分账收款方商户号 + */ + private String receiveMchtCode; + /** + * 当前分账单已退款金额总计 + */ + private Long refundAmountSum; + /** + * 分账手续费承担本金金额 + */ + private Long sepaFeeAmount; + /** + * 分账手续费承担本金比例 + */ + private Long sepaFeeRatio; + /** + * 分账平台应结金额 + */ + private Long sepaPlatStlmAmount; + /** + * 分账单商户订单号 + */ + private String separateTrade; + /** + * 分账比例 + */ + private Long sepaRatio; + /** + * 分账单状态,(处理中、成功、失败、已退款) + */ + private SepaStatus sepaStatus; + /** + * 分账单状态说明 + */ + private String sepaStatusDesc; + /** + * 分账实际结算金额 + */ + private Long sepaStlmAmount; + /** + * 分账金额 + */ + private Long sepaTransAmount; + /** + * 分账订单描述 + */ + private String subject; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/query/resp/SeparateQueryRespBody.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/query/resp/SeparateQueryRespBody.java new file mode 100644 index 000000000..fe27008c9 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/separate/query/resp/SeparateQueryRespBody.java @@ -0,0 +1,26 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.separate.query.resp; + +import com.wzj.soopin.transaction.domain.bo.easypay.RespStateInfo; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 查询分账响应体 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SeparateQueryRespBody { + /** + /** + * 基础订单信息 + */ + private CancelQueryRespOrderInfo respOrderInfo; + /** + * 返回码信息 + */ + private RespStateInfo respStateInfo; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/notice/req/TradeNoticeReqBody.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/notice/req/TradeNoticeReqBody.java new file mode 100644 index 000000000..7df242819 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/notice/req/TradeNoticeReqBody.java @@ -0,0 +1,57 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.trade.notice.req; + +import com.wzj.soopin.transaction.domain.bo.easypay.jsapi.req.Promo; +import com.wzj.soopin.transaction.domain.bo.easypay.RespStateInfo; +import com.wzj.soopin.transaction.domain.bo.easypay.trade.query.resp.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 交易结果通知请求体 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class TradeNoticeReqBody { + /** + * ali业务参数 + */ + private AliQueryRespParamInfoVO aliRespParamInfo; + /** + * toC营销信息 + */ + private List marketingRespParamInfoList; + /** + * toB营销信息 + */ + private List promos; + /** + * 银联业务参数 + */ + private QrRespParamInfoVO qrRespParamInfo; + /** + * 基础订单信息 + */ + private TradeQueryRespOrderInfo respOrderInfo; + /** + * 返回码信息,规则详见F.1 + */ + private RespStateInfo respStateInfo; + /** + * 风险控制信息 + */ + private RiskInfo riskParamInfo; + /** + * 清算信息 + */ + private SettleRespParamInfo settleRespParamInfo; + /** + * wx业务参数 + */ + private WxQueryRespParamInfoVO wxRespParamInfo; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/req/TradeQueryReqBody.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/req/TradeQueryReqBody.java new file mode 100644 index 000000000..2820b6a9f --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/req/TradeQueryReqBody.java @@ -0,0 +1,54 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.trade.query.req; + +import com.wzj.soopin.transaction.domain.bo.easypay.PayInfo; +import com.wzj.soopin.transaction.domain.bo.easypay.ReqInfo; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 交易查询请求体 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class TradeQueryReqBody { + /** + * 请求方信息 + */ + private ReqInfo reqInfo; + /** + * 基础订单信息 + */ + private ReqOrderInfo reqOrderInfo; + /** + * 支付信息 + */ + private PayInfo payInfo; + + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class ReqOrderInfo { + /** + * 商户订单号,要唯一,代表本次请求 + */ + private String orgTrace; + /** + * 原商户交易的orgTrace,需要查询哪笔交易,就传那笔交易的orgTrace + */ + private String oriOrgTrace; + /** + * 原订单outTrace,oriOrgTrace与oriOutTrace二选一必填 + */ + private String oriOutTrace; + /** + * 原商户交易日期,yyyyMMdd + */ + private String oriTransDate; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/AliQueryRespParamInfoVO.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/AliQueryRespParamInfoVO.java new file mode 100644 index 000000000..b5197d791 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/AliQueryRespParamInfoVO.java @@ -0,0 +1,206 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.trade.query.resp; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class AliQueryRespParamInfoVO { + /** + * 支付宝店铺编号 支付宝支付能力出现 + */ + private String alipayStoreId; + /** + * 买家的支付宝唯一用户号 + */ + private String buyerId; + /** + * 买方登录ID + */ + private String buyerLogonId; + /** + * 费率活动标识 + */ + private String chargeFlags; + /** + * 支付货币类型 默认人民 币: CNY,网联微信&银联微信,必填;网联支付宝&银联支付宝,选填,币种 + */ + private String currency; + private AliDiscountsInfoVO discountsInfo; + /** + * 交易额外信息 网联支付宝渠道出现 特殊场景下与支付宝约定返回。 json字符串 + */ + private String extInfos; + private List fundBillList; + private HbFqPayInfo hbFqPayInfo; + /** + * 行业特殊信息 支付宝能力出现( 例如在医保卡 支付业务中, 向用户返回医疗信息) + */ + private String industrySepcDetail; + /** + * 结算币种兑换标价币种汇率 + */ + private String settleTransRate; + /** + * 商户门店编号 支付宝支付能力出现 + */ + private String storeId; + /** + * 发生支付交易的商户门店名称 + */ + private String storeName; + /** + * 商户机具终端编号 银联支付宝支付能力出现 + */ + private String terminalId; + /** + * 标价币种兑换支付币种汇率 + */ + private String transPayRate; + /** + * 买家名称 银联支付宝支付能力出现 + */ + private String userName; + /** + * 买家用户类型 + */ + private String userType; + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class AliDiscountsInfoVO { + + /** + * 商家优惠金额 + */ + private Long mchtDiscountAmount; + /** + * 平台优惠金额 + */ + private Long platDiscountAmount; + /** + * 优惠信息 + */ + private List promotionDetail; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class FundBillVO { + /** + * 该支付工具类型所使用的金额 + */ + private Long amount; + /** + * 银行卡支付时的银行代码 + */ + private String bankCode; + /** + * 交易使用的资金渠道 + */ + private String fundChannel; + /** + * 渠道所使用的资金类型 + */ + private String fundType; + /** + * 渠道实际付款金额 + */ + private Long realAmount; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class HbFqPayInfo { + /** + * 分期金额 + */ + private Long fqAmount; + /** + * 花呗分期数 + */ + private Long hbFqNum; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class AliDiscountsDetailVO { + /** + * 活动ID + */ + private String activityId; + /** + * 优惠券面额 + */ + private Long amount; + /** + * 优惠券备注信息 + */ + private String memo; + /** + * 商户出资 + */ + private Long merchantContribute; + /** + * 优惠名称 + */ + private String name; + /** + * 其他出资方出资金额 + */ + private Long otherContribute; + /** + * 其他出资 + */ + private List otherContributeDetails; + /** + * 券ID + */ + private String promotionId; + /** + * 如果使用的这张券是用户购买的,则该字段代表用户在购买这张券时平台优惠的金额 + */ + private Long purchaseAntContribute; + /** + * 如果使用的这张券是用户购买的,则该字段代表用户在购买这张券时用户实际付款的金额 + */ + private Long purchaseBuyerContribute; + /** + * 如果使用的这张券是用户购买的,则该字段代表用户在购买这张券时商户优惠的金额 + */ + private Long purchaseMerchanCcontribute; + /** + * 优惠类型 + */ + private String type; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class OtherContribute { + /** + * 出资金额 + */ + private Long contributeAmount; + /** + * 出资类型 + */ + private String contributeType; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/MarketingParamInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/MarketingParamInfo.java new file mode 100644 index 000000000..c401ac5bf --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/MarketingParamInfo.java @@ -0,0 +1,51 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.trade.query.resp; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +/** + * 营销单返回 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class MarketingParamInfo { + /** + * 活动id + */ + private Double activityId; + /** + * 营销币种 + */ + private String currency; + /** + * 营销业务场景码 + */ + private String marketingBusinessSceneCode; + /** + * 营销支付能力编号 + */ + private String marketingPayAbilityCode; + /** + * 营销支付方式编号 + */ + private String marketingPayMethodCode; + /** + * 营销报名id + */ + private Double registrationId; + /** + * 收银台营销请求扩展信息 + */ + private Map> respMarketingExtInfo; + /** + * 营销金额 + */ + private Double transAmount; + private String upOrderNo; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/MergeOrderSubRespList.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/MergeOrderSubRespList.java new file mode 100644 index 000000000..6f3bbdcaf --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/MergeOrderSubRespList.java @@ -0,0 +1,49 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.trade.query.resp; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 子单返回信息 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class MergeOrderSubRespList { + + /** + * 订单币种,默认CNY + */ + private String currency; + /** + * 延时结算标识 + */ + private String delaySettleFlag; + /** + * 秒到标识 + */ + private String patnerSettleFlag; + /** + * 分账标识 + */ + private String splitSettleFlag; + /** + * 子单请求流水号 + */ + private String subOrgTrace; + /** + * 订单金额 + */ + private String transAmount; + /** + * 交易状态码 + */ + private String transState; + /** + * 交易状态说明 + */ + private String transStatusDesc; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/QrRespParamInfoVO.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/QrRespParamInfoVO.java new file mode 100644 index 000000000..195cad238 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/QrRespParamInfoVO.java @@ -0,0 +1,121 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.trade.query.resp; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 银联业务参数 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class QrRespParamInfoVO { + + private List discountsInfo; + /** + * QrPayerInfoInner + */ + private QrPayerInfo qrPayerInfo; + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class QrDiscountsDetailVO { + /** + * 活动id + */ + private String activityId; + /** + * 附件信息 + */ + private String addnInfo; + /** + * 金额 + */ + private Long amount; + /** + * 项目发行,发行优惠活动的平台或机构,取值如下: + * 银联卡券平台:固定填写KQPT + * 付款方:填写8 位付款方机构代码 + */ + private String issuerId; + /** + * 姓名 + */ + private String name; + /** + * 营销活动id + */ + private String promotionId; + /** + * 类型 + */ + private String type; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class QrPayerInfo { + /** + * 卡号 + */ + private String accNo; + /** + * 账户类型 + */ + private String acctClass; + /** + * 卡属性,01 – 借记卡 + * 02 – 贷记卡(含准贷记卡) + */ + private String cardAttr; + /** + * 证件号码,可选 + * 取证件号后6 位数字, + * 再左补0 到与原证件号 + * 相同长度 + */ + private String certifId; + /** + * 证件类型 + */ + private String certifTp; + /** + * cvn2 + */ + private String cvn2; + /** + * 卡有效期 + */ + private String expired; + /** + * 付款方机构代码,代表付款方机构。 + */ + private String issCode; + /** + * 手机号,必选 + * 银行卡预留手机号码, + * 11 位,不包括+86 等信 + * 息。银联将根据综合情 + * 况决定是否送发卡行 + * 验证 + */ + private String mobile; + /** + * 姓名 + */ + private String name; + /** + * 付款银行信息 + */ + private String payerBankInfo; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/RiskInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/RiskInfo.java new file mode 100644 index 000000000..629b7683c --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/RiskInfo.java @@ -0,0 +1,31 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.trade.query.resp; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 风险控制信息 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class RiskInfo { + + /** + * 终端IP + * 绑卡设备(付款 APP)所在的公网 IP,可用于 + * 定位所属地区,不是 wifi 连接时的局域网 IP。 + * 局域网 IP 包括: + * A 类:10.0.0.0-10.255.255.255 + * B 类:172.16.0.0-172.31.255.255 + * C 类:192.168.0.0-192.168.255.255 + */ + private String terminalIp; + /** + * 终端位置 (付款 APP)设备 GPS 位置,格式为纬度/经度, +表示北纬、东经,-表示南纬、西经 + */ + private String terminalLocation; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/SettleRespParamInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/SettleRespParamInfo.java new file mode 100644 index 000000000..dffece776 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/SettleRespParamInfo.java @@ -0,0 +1,49 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.trade.query.resp; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 清算信息 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SettleRespParamInfo { + /** + * 延迟结算标识,(1:是 0:否) + */ + private String delaySettleFlag; + /** + * D0标识,(1:是 0:否) + */ + private String patnerSettleFlag; + /** + * 分账标识,(1:交易后分账 2:交易中分账 0:非分账交易) + */ + private String splitSettleFlag; + /** + * 手续费金额 + */ + private Long fee; + /** + * 清算金额,含手续费的金额 + */ + private Long settleAmt; + /** + * 结算金额,单位分.扣除手续费后的金额 + */ + private Long finalAmt; + /** + * 支付完成/清算 日期,yyMMdd + */ + private String settleDate; + /** + * 支付完成/清算时间,HHMMSS + */ + private String settleTime; + +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/TradeQueryRespBody.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/TradeQueryRespBody.java new file mode 100644 index 000000000..83b6e7fed --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/TradeQueryRespBody.java @@ -0,0 +1,60 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.trade.query.resp; + +import com.wzj.soopin.transaction.domain.bo.easypay.jsapi.req.Promo; +import com.wzj.soopin.transaction.domain.bo.easypay.RespStateInfo; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 交易查询响应 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class TradeQueryRespBody { + /** + * ali业务参数 + */ + private AliQueryRespParamInfoVO aliRespParamInfo; + /** + * toC营销信息 + */ + private List marketingRespParamInfoList; + /** + * 子单返回信息 + */ + private MergeOrderSubRespList mergeOrderSubRespList; + /** + * toB营销信息 + */ + private List promos; + /** + * 银联业务参数 + */ + private QrRespParamInfoVO qrRespParamInfo; + /** + * 基础订单信息 + */ + private TradeQueryRespOrderInfo respOrderInfo; + /** + * 返回码信息,规则详见F.1 + */ + private RespStateInfo respStateInfo; + /** + * 风险控制信息 + */ + private RiskInfo riskParamInfo; + /** + * 清算信息 + */ + private SettleRespParamInfo settleRespParamInfo; + /** + * wx业务参数 + */ + private WxQueryRespParamInfoVO wxRespParamInfo; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/TradeQueryRespOrderInfo.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/TradeQueryRespOrderInfo.java new file mode 100644 index 000000000..ca038a834 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/TradeQueryRespOrderInfo.java @@ -0,0 +1,79 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.trade.query.resp; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 基础订单信息 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class TradeQueryRespOrderInfo { + + /** + * 付款卡类型,详见F.9 + */ + private String cardType; + /** + * 支付完成日期 + */ + private String dateEnd; + /** + * 商户订单号,商户生成请求易生的流水号 + */ + private String orgTrace; + /** + * 易生订单号,易生生成后上送渠道的订单号 + */ + private String outTrace; + /** + * 实际付款金额,用户实付金额,与订单是否参与渠道优惠活动有关。比如当订单发起金额1000,参加渠道用户减免100,实际付款金额为900。 + */ + private Long payAmount; + /** + * 渠道订单号,渠道生成返回给易生的订单号,例如微信、支付宝订单号 + */ + private String pcTrace; + /** + * 产品订单号 + */ + private String productTrace; + /** + * 已退款金额总额 + */ + private Long refundAmountSum; + /** + * 已分账金额总额 + */ + private Long separateAmountSum; + /** + * 应结订单金额,上游渠道清算金额,与订单是否参与上游渠道优惠活动有关。 + * 1、假如一笔微信订单1000,参与了微信的满减1000-100,比如由银行出资,微信会按1000给我们结算,这个应结金额=1000。 + * 2、假如一笔微信订单1000,由商户自己在微信那边配了营销活动,且配的是从商户的结算款扣除营销款,这时候微信按900来款,应结金额=900 + */ + private Long stlmAmount; + /** + * 已结算金额总额 + */ + private Long stlmAmountSum; + /** + * 支付完成/清算时间 + */ + private String timeEnd; + /** + * 交易金额 + */ + private Long transAmount; + /** + * 银联订单号,银联生成并返回给易生的订单号 + */ + private String unTrace; + /** + * 用户唯一编码 + */ + private String userId; +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/WxQueryRespParamInfoVO.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/WxQueryRespParamInfoVO.java new file mode 100644 index 000000000..2bcc20691 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/bo/easypay/trade/query/resp/WxQueryRespParamInfoVO.java @@ -0,0 +1,135 @@ +package com.wzj.soopin.transaction.domain.bo.easypay.trade.query.resp; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/* + * wx业务参数 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class WxQueryRespParamInfoVO { + /** + * 附加信息 + */ + private String attach; + /** + * 付款银行类型 + */ + private String cardIssuerCode; + /** + * 用户标识, 微信在易生appid下面openid + */ + private String openId; + /** + * 子商户openId + */ + private String subOpenId; + /** + * 优惠信息 + */ + private WxDiscountsInfoVO wxDiscountsInfo; + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class WxDiscountsInfoVO { + /** + * 代金券金额 + */ + private Long couponAmount; + /** + * 优惠详细信息 + */ + private List discountsInfo; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class WxDiscountsDetailVO { + /** + * 活动ID + */ + private String activityId; + /** + * 优惠券面额 + */ + private Long amount; + /** + * 优惠币种 + */ + private String currency; + /** + * 单品列表 + */ + private GoodsDetailRespVO goodsDetail; + /** + * 商户出资 + */ + private Long merchantContribute; + /** + * 优惠名称 + */ + private String name; + /** + * 其他出资方出资金额 + */ + private Long otherContribute; + /** + * 券ID + */ + private String promotionId; + /** + * 优惠范围 + */ + private String scope; + /** + * 优惠类型 + */ + private String type; + /** + * 微信出资 + */ + private Long wxpayContribute; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class GoodsDetailRespVO { + /** + * 商品优惠金额 ,单位为:分 + */ + private Long discountAmount; + /** + * 商品编号 + */ + private String goodsId; + /** + * 商品名称 + */ + private String goodsName; + /** + * 商品备注 + */ + private String goodsRemark; + /** + * 商品单价 单位为分 + */ + private Long price; + /** + * 商品数量 + */ + private Long quantity; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/vo/YishengAccountVO.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/vo/EasypayAccountVO.java similarity index 92% rename from ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/vo/YishengAccountVO.java rename to ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/vo/EasypayAccountVO.java index 93a687f23..a0600b75e 100644 --- a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/vo/YishengAccountVO.java +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/vo/EasypayAccountVO.java @@ -15,7 +15,7 @@ import java.math.BigDecimal; */ @Builder(toBuilder = true) @Data -public class YishengAccountVO { +public class EasypayAccountVO { /** * 主键 diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/vo/EasypayPrePayVO.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/vo/EasypayPrePayVO.java new file mode 100644 index 000000000..ab8e39e2e --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/domain/vo/EasypayPrePayVO.java @@ -0,0 +1,50 @@ +package com.wzj.soopin.transaction.domain.vo; + +import com.wzj.soopin.transaction.enums.easypay.PayType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class EasypayPrePayVO { + + private PayType payType; + + private WxPrePayParamInfo wxPrePayParamInfo; + + private String tradeNo; + + private String QrRedirectUrl; + + /** + * 微信网页或小程序中拉起支付的参数 + */ + @Data + @AllArgsConstructor + @NoArgsConstructor + public class WxPrePayParamInfo { + + private String appId; + + + private String timeStamp; + + + private String nonceStr; + + + private String signType; + + + private String paySign; + + + private String prepayId; + + + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/AddCalcType.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/AddCalcType.java new file mode 100644 index 000000000..874d2157d --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/AddCalcType.java @@ -0,0 +1,32 @@ +package com.wzj.soopin.transaction.enums.easypay; + + +import java.io.IOException; + +/** + * 附加手续费计算方式 + * + * 手续费计算方式 + * + * 手续费费用类型 + * + * 是否允许退货 + */ +public enum AddCalcType { + THE_0, THE_1; + + public String toValue() { + return switch (this) { + case THE_0 -> "0"; + case THE_1 -> "1"; + }; + } + + public static AddCalcType forValue(String value) throws IOException { + return switch (value) { + case "0" -> THE_0; + case "1" -> THE_1; + default -> throw new IOException("Cannot deserialize AddCalcType"); + }; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/BillingCycle.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/BillingCycle.java new file mode 100644 index 000000000..3a60c5984 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/BillingCycle.java @@ -0,0 +1,30 @@ +package com.wzj.soopin.transaction.enums.easypay; + +import java.io.IOException; + +/** + * 结算周期 + */ +public enum BillingCycle { + + D0, D1, DD, T1; + + public String toValue() { + return switch (this) { + case D0 -> "D0"; + case D1 -> "D1"; + case DD -> "DD"; + case T1 -> "T1"; + }; + } + + public static BillingCycle forValue(String value) throws IOException { + return switch (value) { + case "D0" -> D0; + case "D1" -> D1; + case "DD" -> DD; + case "T1" -> T1; + default -> throw new IOException("Cannot deserialize BillingCycle"); + }; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/CardType.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/CardType.java new file mode 100644 index 000000000..5a6374444 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/CardType.java @@ -0,0 +1,30 @@ +package com.wzj.soopin.transaction.enums.easypay; + +import java.io.IOException; + +/** + * 卡类型 + */ +public enum CardType { + + THE_000_C, THE_000_D, THE_000_U, XXXX; + + public String toValue() { + return switch (this) { + case THE_000_C -> "000C"; + case THE_000_D -> "000D"; + case THE_000_U -> "000U"; + case XXXX -> "XXXX"; + }; + } + + public static CardType forValue(String value) throws IOException { + return switch (value) { + case "000C" -> THE_000_C; + case "000D" -> THE_000_D; + case "000U" -> THE_000_U; + case "XXXX" -> XXXX; + default -> throw new IOException("Cannot deserialize CardType"); + }; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/DelaySettleFlag.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/DelaySettleFlag.java new file mode 100644 index 000000000..e1d171d92 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/DelaySettleFlag.java @@ -0,0 +1,37 @@ +package com.wzj.soopin.transaction.enums.easypay; + +import java.io.IOException; + +/** + * 延时结算标识 + * + * 分账D0标识,D0需提前开通 + * + * 分账D0标识 + * + * D0标识 + */ +public enum DelaySettleFlag { + + THE_0, THE_1; + + public String toValue() { + switch (this) { + case THE_0 -> { + return "0"; + } + case THE_1 -> { + return "1"; + } + } + return null; + } + + public static DelaySettleFlag forValue(String value) throws IOException { + return switch (value) { + case "0" -> THE_0; + case "1" -> THE_1; + default -> throw new IOException("Cannot deserialize DelaySettleFlag"); + }; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/FeeType.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/FeeType.java new file mode 100644 index 000000000..0b0b4dfb8 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/FeeType.java @@ -0,0 +1,29 @@ +package com.wzj.soopin.transaction.enums.easypay; + +import java.io.IOException; + +/** + * 手续费处理模式 + */ + +public enum FeeType { + + EARNING_EXPENSE, NETTING, OFFLINE; + + public String toValue() { + return switch (this) { + case EARNING_EXPENSE -> "EARNING_EXPENSE"; + case NETTING -> "NETTING"; + case OFFLINE -> "OFFLINE"; + }; + } + + public static FeeType forValue(String value) throws IOException { + return switch (value) { + case "EARNING_EXPENSE" -> EARNING_EXPENSE; + case "NETTING" -> NETTING; + case "OFFLINE" -> OFFLINE; + default -> throw new IOException("Cannot deserialize FeeType"); + }; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/IDCheckIn.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/IDCheckIn.java new file mode 100644 index 000000000..19468ffb2 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/IDCheckIn.java @@ -0,0 +1,39 @@ +package com.wzj.soopin.transaction.enums.easypay; + +import java.io.IOException; + +/** + * 实名支付验证标识 + */ +public enum IDCheckIn { + + EMPTY, THE_0, THE_1, THE_2; + + public String toValue() { + switch (this) { + case EMPTY -> { + return "其他"; + } + case THE_0 -> { + return "0"; + } + case THE_1 -> { + return "1"; + } + case THE_2 -> { + return "2"; + } + } + return null; + } + + public static IDCheckIn forValue(String value) throws IOException { + return switch (value) { + case "其他" -> EMPTY; + case "0" -> THE_0; + case "1" -> THE_1; + case "2" -> THE_2; + default -> throw new IOException("Cannot deserialize IDCheckIn"); + }; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/LimitCreditPay.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/LimitCreditPay.java new file mode 100644 index 000000000..3b3893631 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/LimitCreditPay.java @@ -0,0 +1,31 @@ +package com.wzj.soopin.transaction.enums.easypay; + +import java.io.IOException; + +/** + * 禁用支付渠道 + */ +public enum LimitCreditPay { + + CREDIT_CARD, DEBIT_CARD; + + public String toValue() { + switch (this) { + case CREDIT_CARD -> { + return "creditCard"; + } + case DEBIT_CARD -> { + return "debitCard"; + } + } + return null; + } + + public static LimitCreditPay forValue(String value) throws IOException { + return switch (value) { + case "creditCard" -> CREDIT_CARD; + case "debitCard" -> DEBIT_CARD; + default -> throw new IOException("Cannot deserialize LimitCreditPay"); + }; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/PatnerSettleFlag.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/PatnerSettleFlag.java new file mode 100644 index 000000000..643219e5d --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/PatnerSettleFlag.java @@ -0,0 +1,29 @@ +package com.wzj.soopin.transaction.enums.easypay; + +import java.io.IOException; + +/** + * D0标识,0: 非D0交易,即按进件商户开通功能结算周期结算 + * 1: D0交易,商户需开通了D0,且此字段送1时,会d0到账 + * 默认值0 + */ +public enum PatnerSettleFlag { + + EMPTY, THE_0, THE_1; + + public String toValue() { + switch (this) { + case EMPTY: return ""; + case THE_0: return "0"; + case THE_1: return "1"; + } + return null; + } + + public static PatnerSettleFlag forValue(String value) throws IOException { + if (value.equals("")) return EMPTY; + if (value.equals("0")) return THE_0; + if (value.equals("1")) return THE_1; + throw new IOException("Cannot deserialize PatnerSettleFlag"); + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/PayChannel.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/PayChannel.java new file mode 100644 index 000000000..2698365b2 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/PayChannel.java @@ -0,0 +1,31 @@ +package com.wzj.soopin.transaction.enums.easypay; + +import java.io.IOException; + +/** + * 支付方式,payType=UnionPayJsMini时必填 + */ +public enum PayChannel { + + THE_01, THE_02; + + public String toValue() { + switch (this) { + case THE_01 -> { + return "01"; + } + case THE_02 -> { + return "02"; + } + } + return null; + } + + public static PayChannel forValue(String value) throws IOException { + return switch (value) { + case "01" -> THE_01; + case "02" -> THE_02; + default -> throw new IOException("Cannot deserialize PayChannel"); + }; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/PayType.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/PayType.java new file mode 100644 index 000000000..8000336b7 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/PayType.java @@ -0,0 +1,52 @@ +package com.wzj.soopin.transaction.enums.easypay; + +import java.io.IOException; + +/** + * 支付方式 + */ +public enum PayType { + + ALI_PAY_JSAPI, + ALI_PAY_MINI_APP, + UNION_PAY_JSAPI, + UNION_PAY_Js_MINI, + WE_CHAT_MINI_APP, + WE_CHAT_JSAPI; + + public String toValue() { + switch (this) { + case ALI_PAY_JSAPI -> { + return "AliPayJsapi"; + } + case ALI_PAY_MINI_APP -> { + return "AliPayMiniApp"; + } + case UNION_PAY_JSAPI -> { + return "UnionPayJsapi"; + } + case UNION_PAY_Js_MINI -> { + return "UnionPayJsMini"; + } + case WE_CHAT_JSAPI -> { + return "WeChatJsapi"; + } + case WE_CHAT_MINI_APP -> { + return "WeChatMiniApp"; + } + } + return null; + } + + public static PayType forValue(String value) throws IOException { + return switch (value) { + case "AliPayJsapi" -> ALI_PAY_JSAPI; + case "AliPayMiniApp" -> ALI_PAY_MINI_APP; + case "UnionPayJsapi" -> UNION_PAY_JSAPI; + case "UnionPayJsMini" -> UNION_PAY_Js_MINI; + case "WeChatJsapi" -> WE_CHAT_JSAPI; + case "WeChatMiniApp" -> WE_CHAT_MINI_APP; + default -> throw new IOException("Cannot deserialize PayType"); + }; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/QrCodeType.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/QrCodeType.java new file mode 100644 index 000000000..57b44022d --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/QrCodeType.java @@ -0,0 +1,35 @@ +package com.wzj.soopin.transaction.enums.easypay; + +import java.io.IOException; + +/** + * 二维码类型,payType=UnionPayJsapi、UnionPayJsMini时必填。 + */ +public enum QrCodeType { + + EMPTY, THE_0, THE_1; + + public String toValue() { + switch (this) { + case EMPTY -> { + return "其他"; + } + case THE_0 -> { + return "0"; + } + case THE_1 -> { + return "1"; + } + } + return null; + } + + public static QrCodeType forValue(String value) throws IOException { + return switch (value) { + case "其他" -> EMPTY; + case "0" -> THE_0; + case "1" -> THE_1; + default -> throw new IOException("Cannot deserialize QrCodeType"); + }; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/SepaStatus.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/SepaStatus.java new file mode 100644 index 000000000..84f936b6c --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/SepaStatus.java @@ -0,0 +1,27 @@ +package com.wzj.soopin.transaction.enums.easypay; + +import java.io.IOException; + +/** + * 分账单状态,(处理中、成功、失败、已退款) + */ +public enum SepaStatus { + PENDING, REFUNDED, SUCCESS; + + public String toValue() { + return switch (this) { + case PENDING -> "PENDING"; + case REFUNDED -> "REFUNDED"; + case SUCCESS -> "SUCCESS"; + }; + } + + public static SepaStatus forValue(String value) throws IOException { + return switch (value) { + case "PENDING" -> PENDING; + case "REFUNDED" -> REFUNDED; + case "SUCCESS" -> SUCCESS; + default -> throw new IOException("Cannot deserialize SepaStatus"); + }; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/SpecialTag.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/SpecialTag.java new file mode 100644 index 000000000..d0a01826c --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/SpecialTag.java @@ -0,0 +1,29 @@ +package com.wzj.soopin.transaction.enums.easypay; + +import java.io.IOException; + +/** + * 费用类型 + */ +public enum SpecialTag { + + THE_2301, THE_2302, THE_2303, THE_2304; + + public String toValue() { + switch (this) { + case THE_2301: return "2301"; + case THE_2302: return "2302"; + case THE_2303: return "2303"; + case THE_2304: return "2304"; + } + return null; + } + + public static SpecialTag forValue(String value) throws IOException { + if (value.equals("2301")) return THE_2301; + if (value.equals("2302")) return THE_2302; + if (value.equals("2303")) return THE_2303; + if (value.equals("2304")) return THE_2304; + throw new IOException("Cannot deserialize SpecialTag"); + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/SplitSettleFlag.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/SplitSettleFlag.java new file mode 100644 index 000000000..10b01e305 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/SplitSettleFlag.java @@ -0,0 +1,37 @@ +package com.wzj.soopin.transaction.enums.easypay; + +import java.io.IOException; + +/** + * 分账标识,与主单分账标识互斥,不能同时上送1 + * + * 分账标识 + */ +public enum SplitSettleFlag { + + THE_0, THE_1, THE_2; + + public String toValue() { + switch (this) { + case THE_0 -> { + return "0"; + } + case THE_1 -> { + return "1"; + } + case THE_2 -> { + return "2"; + } + } + return null; + } + + public static SplitSettleFlag forValue(String value) throws IOException { + return switch (value) { + case "0" -> THE_0; + case "1" -> THE_1; + case "2" -> THE_2; + default -> throw new IOException("Cannot deserialize SplitSettleFlag"); + }; + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/WxLimitPay.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/WxLimitPay.java new file mode 100644 index 000000000..29f55d6a1 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/enums/easypay/WxLimitPay.java @@ -0,0 +1,24 @@ +package com.wzj.soopin.transaction.enums.easypay; + +import java.io.IOException; + +/** + * 交易限制支付类型 + */ +public enum WxLimitPay { + NO_CREDIT; + + public String toValue() { + if (this == WxLimitPay.NO_CREDIT) { + return "no_credit"; + } + return null; + } + + public static WxLimitPay forValue(String value) throws IOException { + if (value.equals("no_credit")) { + return NO_CREDIT; + } + throw new IOException("Cannot deserialize WxLimitPay"); + } +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/IEasypayService.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/IEasypayService.java new file mode 100644 index 000000000..8fb2999ed --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/IEasypayService.java @@ -0,0 +1,70 @@ +package com.wzj.soopin.transaction.service; + + +import com.wzj.soopin.transaction.domain.bo.PaymentBO; +import com.wzj.soopin.transaction.domain.bo.easypay.EasyPayRequest; +import com.wzj.soopin.transaction.domain.vo.EasypayAccountVO; +import com.wzj.soopin.transaction.domain.vo.EasypayPrePayVO; + +import java.math.BigDecimal; +import java.rmi.ServerException; + +public interface IEasypayService { + + /** + * 发起支付 + * @param jsApiReqBody + * @return + */ + EasypayPrePayVO payment(PaymentBO paymentBO) throws ServerException; + + /** + * 获取易生账户 + * @param memberId + * @return + */ + EasypayAccountVO getEasypayAccount(Long memberId); + + /** + * 提现 + * @param memberId + * @param money + * @return + */ + boolean withdraw(Long memberId, BigDecimal money); + + + /** + * 同步会员账户 + * @return + */ + boolean syncMemberAccount(); + + /** + * 开通商户 + */ + void openMerchant(Long memberId); + + /** + * 获取商户账户 + */ + EasypayAccountVO getMerchantAccount(Long memberId); + + /** + * 分账 + */ + boolean split(Long memberId, BigDecimal money); + + + /** + * 商户审核通知 + * */ + + boolean merchantAuditNotice(String request); + + /** + * 处理易生支付结果通知回调 + * @param request + */ + void handlerTradeCallback(EasyPayRequest request); +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/IYishengService.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/IYishengService.java deleted file mode 100644 index 915d88197..000000000 --- a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/IYishengService.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.wzj.soopin.transaction.service; - - -import com.wzj.soopin.transaction.domain.vo.YishengAccountVO; - -import java.math.BigDecimal; - -public interface IYishengService { - /** - * 获取易生账户 - * @param memberId - * @return - */ - YishengAccountVO getYishengAccount(Long memberId); - - /** - * 提现 - * @param memberId - * @param money - * @return - */ - boolean withdraw(Long memberId, BigDecimal money); - - - /** - * 同步会员账户 - * @return - */ - boolean syncMemberAccount(); - - /** - * 开通商户 - */ - void openMerchant(Long memberId); - - /** - * 获取商户账户 - */ - YishengAccountVO getMerchantAccount(Long memberId); - - /** - * 分账 - */ - boolean split(Long memberId, BigDecimal money); - - - /** - * 商户审核通知 - * */ - - boolean merchantAuditNotice(String request); -} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/impl/DivideServiceImpl.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/impl/DivideServiceImpl.java index 074c5019d..7571cc21a 100644 --- a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/impl/DivideServiceImpl.java +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/impl/DivideServiceImpl.java @@ -24,7 +24,7 @@ import com.wzj.soopin.transaction.mapper.DivideDetailMapper; import com.wzj.soopin.transaction.mapper.DivideMapper; import com.wzj.soopin.transaction.service.IDivideRuleService; import com.wzj.soopin.transaction.service.IDivideService; -import com.wzj.soopin.transaction.service.IYishengService; +import com.wzj.soopin.transaction.service.IEasypayService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.dromara.common.core.exception.ServiceException; @@ -66,7 +66,7 @@ public class DivideServiceImpl extends ServiceImpl impleme private final ConfigService configService; - private final IYishengService yishengService; + private final IEasypayService easypayService; private final IMemberAccountService accountService; diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/impl/EasypayServiceImpl.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/impl/EasypayServiceImpl.java new file mode 100644 index 000000000..3c2ad4e93 --- /dev/null +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/impl/EasypayServiceImpl.java @@ -0,0 +1,367 @@ +package com.wzj.soopin.transaction.service.impl; + + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.codec.Base64; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.text.StrBuilder; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.asymmetric.Sign; +import cn.hutool.crypto.asymmetric.SignAlgorithm; +import cn.hutool.http.HttpRequest; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.wzj.soopin.transaction.config.EasypayConfig; +import com.wzj.soopin.transaction.config.WechatMiniProgramConfig; +import com.wzj.soopin.transaction.domain.bo.PaymentBO; +import com.wzj.soopin.transaction.domain.bo.easypay.*; +import com.wzj.soopin.transaction.domain.bo.easypay.jsapi.req.*; +import com.wzj.soopin.transaction.domain.bo.easypay.jsapi.resp.JsApiRespBody; +import com.wzj.soopin.transaction.domain.bo.easypay.jsapi.resp.JsapiRespOrderInfo; +import com.wzj.soopin.transaction.domain.bo.easypay.jsapi.resp.WxJsApiRespParamInfoVO; +import com.wzj.soopin.transaction.domain.vo.EasypayAccountVO; +import com.wzj.soopin.transaction.domain.vo.EasypayPrePayVO; +import com.wzj.soopin.transaction.enums.easypay.DelaySettleFlag; +import com.wzj.soopin.transaction.enums.easypay.PatnerSettleFlag; +import com.wzj.soopin.transaction.enums.easypay.PayType; +import com.wzj.soopin.transaction.enums.easypay.SplitSettleFlag; +import com.wzj.soopin.transaction.service.IEasypayService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.enums.FormatsType; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.DateUtils; +import org.dromara.common.core.utils.ServletUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.rmi.ServerException; +import java.util.Date; +import java.util.Map; +import java.util.TreeMap; + +import static com.wzj.soopin.transaction.constans.EasypayConstants.*; + +/** + * 会员封禁 + * + * @author zcc + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class EasypayServiceImpl implements IEasypayService { + + @Autowired + private EasypayConfig easypayConfig; + + @Autowired + private WechatMiniProgramConfig wechatMiniProgramConfig; + + + /** + * 发起支付 + * + * @param paymentBO + * @return + */ + @Override + public EasypayPrePayVO payment(PaymentBO paymentBO) throws ServerException { + checkPaymentParamByPayType(paymentBO); + EasyPayRequestHeader reqHeader = generateEasyPayRequestHeader(); + SettleParamInfo settleParamInfo = SettleParamInfo.builder() + .delaySettleFlag(DelaySettleFlag.THE_0.toValue()) + .patnerSettleFlag(PatnerSettleFlag.THE_1.toValue()) + .splitSettleFlag(SplitSettleFlag.THE_0.toValue()) + .build(); + JsApiReqBody apiReqBody = JsApiReqBody.builder() + .riskData(RiskData.builder().customerIp(ServletUtils.getClientIP()).build()) + .settleParamInfo(settleParamInfo) + .build(); + //封装请求方信息 + apiReqBody.setReqInfo(ReqInfo.builder().mchtCode(easypayConfig.getMchtCode()).build()); + //封装基础订单信息 + setReqOrderInfo(apiReqBody, paymentBO); + // 封装支付信息 + setPayInfo(apiReqBody, paymentBO); + String reqSign = getSignStr(reqHeader, apiReqBody); + EasyPayRequest easyPayRequest = EasyPayRequest.builder() + .reqHeader(reqHeader) + .reqBody(apiReqBody) + .reqSign(reqSign) + .build(); + log.debug("调用易生发起支付接口请求:{}",JSONObject.toJSONString(easyPayRequest)); + String url = StrBuilder.create(easypayConfig.getApiPathPrefix()).append("/trade/jsapi").toString(); + String body = HttpRequest.post(url) + .timeout(3000) + .body(JSON.toJSONString(easyPayRequest)) + .execute() + .body(); + log.debug("调用易生发起支付接口响应:{}",body); + EasyPayResponse easyPayResponse = JSONObject.parseObject(body, EasyPayResponse.class); + if (StrUtil.equals(RSP_HEADER_OK, easyPayResponse.getRspHeader().getRspCode())) { + verify(easyPayResponse); + JsApiRespBody jsApiRespBody = JSON.parseObject(JSONObject.toJSONString(easyPayResponse.getRspBody()), JsApiRespBody.class); + RespStateInfo respStateInfo = jsApiRespBody.getRespStateInfo(); + if (StrUtil.equals(RSP_BODY_RESP_OK, respStateInfo.getRespCode())) { + if (StrUtil.equalsAny(respStateInfo.getTransState(), RSP_BODY_TRANS_OK, RSP_BODY_TRANS_OK_WETCAT, RSP_BODY_TRANS_PROCESSING)) { + // TODO 保存支付订单信息到数据库 + JsapiRespOrderInfo respOrderInfo = jsApiRespBody.getRespOrderInfo(); + + // 生成返回前端的预支付信息 + return generatePrePayVO(paymentBO.getPayType(), jsApiRespBody); + } else { + log.error("发起支付失败:{}", respStateInfo.getTransStatusDesc() + (StrUtil.isNotBlank(respStateInfo.getAppendRetMsg()) ? "渠道返回码描述:" + respStateInfo.getAppendRetMsg() : "")); + throw new ServiceException("发起支付失败:" + respStateInfo.getTransStatusDesc() + (StrUtil.isNotBlank(respStateInfo.getAppendRetMsg()) ? "渠道返回码描述:" + respStateInfo.getAppendRetMsg() : "")); + } + } else { + log.error("发起支付业务失败:{}", respStateInfo.getRespDesc()); + throw new ServiceException("发起支付业务失败:" + respStateInfo.getRespDesc()); + } + } else { + log.error("发起支付通讯失败:{}", easyPayResponse.getRspHeader().getRspInfo()); + throw new ServiceException("发起支付通讯失败:" + easyPayResponse.getRspHeader().getRspInfo()); + } + } + + /** + * 根据支付类型校验请求参数是否完整 + * + * @param paymentBO + */ + private void checkPaymentParamByPayType(PaymentBO paymentBO) throws ServerException { + switch (paymentBO.getPayType()) { + case ALI_PAY_JSAPI, ALI_PAY_MINI_APP -> Assert.notBlank(paymentBO.getBuyerId(), () -> new ServiceException("支付宝支付缺少必要参数:buyerId")); + case WE_CHAT_JSAPI, WE_CHAT_MINI_APP -> Assert.notBlank(paymentBO.getSubOpenId(), () -> new ServiceException("微信支付缺少必要参数:subOpenId")); + case UNION_PAY_JSAPI, UNION_PAY_Js_MINI -> Assert.isTrue(StrUtil.isAllNotBlank(paymentBO.getTransType(), paymentBO.getUserAuthCode(), + paymentBO.getUserId(), paymentBO.getAreaInfo(), paymentBO.getPaymentValidTime(), + paymentBO.getQrCode(), paymentBO.getQrCodeType()), () -> new ServiceException("银联支付缺少必要参数")); + default -> throw new ServerException("不支持的支付方式"); + } + } + + /** + * 生成预支付信息 + * + * @param payType + * @param jsApiRespBody + * @return + */ + private EasypayPrePayVO generatePrePayVO(PayType payType, JsApiRespBody jsApiRespBody) throws ServerException { + EasypayPrePayVO easypayPrePayVO = EasypayPrePayVO.builder() + .payType(payType) + .build(); + + switch (payType) { + case ALI_PAY_JSAPI, ALI_PAY_MINI_APP -> { + easypayPrePayVO.setTradeNo(jsApiRespBody.getAliRespParamInfo().getTradeNo()); + } + case WE_CHAT_JSAPI, WE_CHAT_MINI_APP -> { + WxJsApiRespParamInfoVO wxRespParamInfo = jsApiRespBody.getWxRespParamInfo(); + EasypayPrePayVO.WxPrePayParamInfo wxPrePayParamInfo = JSONObject.parseObject(wxRespParamInfo.getWcPayData(), EasypayPrePayVO.WxPrePayParamInfo.class); + wxPrePayParamInfo.setPrepayId(wxRespParamInfo.getPrepayId()); + easypayPrePayVO.setWxPrePayParamInfo(wxPrePayParamInfo); + } + case UNION_PAY_JSAPI, UNION_PAY_Js_MINI -> { + easypayPrePayVO.setQrRedirectUrl(jsApiRespBody.getQrRespParamInfo().getQrRedirectUrl()); + } + default -> throw new ServerException("不支持的支付方式"); + } + return easypayPrePayVO; + } + + /** + * 验签 + * 待签名串 = ASCII排序(rspHeader) + MD5大写(ASCII排序(rspBody)) + * 验签结果 = RSA公钥验签(待签名串, rspSign, 易生公钥) + * + * @param easyPayResponse + */ + private void verify(EasyPayResponse easyPayResponse) { + // ======= 测试数据start ====== +// String s1 = "{\"rspBody\":{\"aliRespParamInfo\":{\"buyerId\":\"2088612409652625\",\"buyerLogonId\":\"177****1294\",\"fundBillList\":[{\"amount\":1,\"fundChannel\":\"ALIPAYACCOUNT\"}]},\"respStateInfo\":{\"respDesc\":\"处理成功\",\"transStatusDesc\":\"支付成功\",\"appendRetMsg\":\"Success\",\"appendRetCode\":\"10000\",\"transState\":\"0\",\"respCode\":\"000000\"},\"settleRespParamInfo\":{\"delaySettleFlag\":\"0\",\"settleTime\":\"20250530103056\",\"splitSettleFlag\":\"0\",\"settleAmt\":1,\"settleDate\":\"20250530\",\"patnerSettleFlag\":\"0\"},\"respOrderInfo\":{\"orgTrace\":\"161800076769295477468\",\"timeEnd\":\"103056\",\"cardType\":\"000U\",\"productTrace\":\"YQ2025053011082155263\",\"stlmAmountSum\":0,\"dateEnd\":\"20250530\",\"pcTrace\":\"2025053022001452621411391226\",\"userId\":\"2088612409652625\",\"unTrace\":\"792025053022001452621411391226\",\"transAmount\":1,\"payAmount\":1,\"refundAmountSum\":0,\"outTrace\":\"9961683369423587676160\",\"separateAmountSum\":0}},\"rspHeader\":{\"easyPayCertificateId\":\"00000000\",\"rspCode\":\"000000\",\"rspInfo\":\"SUCCESS\"},\"rspSign\":\"n+wyM0lzrbovU61wIF4mH6bdaZkPNZsm8BDn6hpcSBSPk4LQfVHLhaTlKuK5ehCyvktethjr9Vhmtk/5Hgq3fSxvyawJIhMs4/PX+lWhLycs2WAkjSIXUONi0SwZXm2sqi2DXNLxaoNzuOM9zwtZvYnggjca3aweBbXznc++OIzstzMOWg6nlPQiFxBGVv7tUXVQVnJUp5qhTDIomw3dhYgi6b6MrMlyeV4YvChsOdFwblc/dZHWX1riVl9KbbjyiFuHUhA4eZOCDEHMz+l3hVk+ibkw7HwfJY8bcfHRBkQpBSbz82HntSG5A6zcH/iLI2CnUHOVhhEORoAzXwRSYA==\"}"; +// easyPayResponse = JSONObject.parseObject(s1, EasyPayResponse.class); + // ======= 测试数据end ====== + + TreeMap respHearMap = new TreeMap<>(JSONObject.from(easyPayResponse.getRspHeader())); + TreeMap respBodyMap = new TreeMap<>(JSONObject.from(easyPayResponse.getRspBody())); + // 递归排序 + sortMap(respHearMap); + sortMap(respBodyMap); + String md5Str = SecureUtil.md5(JSON.toJSONString(respBodyMap)).toUpperCase(); + String sourceStr = StrBuilder.create(JSON.toJSONString(respHearMap)).append(md5Str).toString(); + log.debug("待验签字符串为:{}", sourceStr); + Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, null, easypayConfig.getEasypayPublicKey()); + boolean verify = sign.verify(sourceStr.getBytes(CharsetUtil.CHARSET_UTF_8), Base64.decode(easyPayResponse.getRspSign())); + Assert.isTrue(verify, () -> new ServiceException("易生响应数据验签失败")); + } + + /** + * 获取签名串 + * 待签名串 = ASCII升序(reqHeader) + MD5大写(ASCII升序(reqBody)) + * reqSign = RSA私钥加签(待签名串, 商户私钥) //RSA模式 ECB/PKCS1_PADDING + * + * @param reqHeader + * @param apiReqBody + * @return + */ + private String getSignStr(EasyPayRequestHeader reqHeader, JsApiReqBody apiReqBody) { + // ======= 测试数据start ====== +// String s1 = "{\"transTime\":\"20250721142109\",\"reqId\":\"D01X66666667068\",\"reqType\":\"2\"}"; +// JSONObject jsonObject1 = JSONObject.parseObject(s1); +// TreeMap reqHeaderMap = new TreeMap<>(jsonObject1); +// String s2 = "{\"applyInfo\":{\"applyNo\":\"XPW)O^K2%YTUS\",\"backUrl\":\"o2FSmVVqz8TouyAweZ4*!4HX2w1A2Z&8&5#ereo4)YLMI2H@f#GPJo0%nZvgdL(pHy5zHQu4Py5*E*ew9iAqGf\",\"applyReason\":\"Kv\"},\"merchantInfo\":{\"mchtName\":\"斛国平\",\"mchtMode\":\"2\",\"businName\":\"愚梓浩\",\"mchtEngName\":\"益雨涵\",\"mchtMccCode\":\"84\",\"mchtAreaCode\":\"14\",\"standardFlag\":\"0\",\"mchtAddr\":\"id sunt fugiat non dolor\",\"businBegtime\":\"2026-03-13 22:44:30\",\"businEndtime\":\"2025-11-30 10:27:29\",\"businForm\":\"02\",\"agentPerson\":\"sed aliqua\",\"agentIdType\":\"30\",\"agentIdNo\":\"97\",\"agentIdValidityStart\":\"86\",\"agentIdValidityEnd\":\"19\",\"agentMobile\":\"14860035247\",\"agentIdAddr\":\"25\",\"conMode\":\"1\",\"projectId\":\"73\",\"projectBank\":\"est\",\"agentMchtCode\":\"82\",\"merSelf\":\"1\",\"instCode\":\"31\",\"settleStyle\":\"2\",\"superMchtCode\":\"46=46=46=46=46=\"},\"merchantFunctionInfo\":{\"weChat\":{\"businessId\":\"1201\",\"cardType\":\"000U\",\"calcType\":\"1\",\"calcVal\":\"Lorem\",\"feeHighLimit\":\"cupidatat dolor laboris Lorem in\",\"feeLowLimit\":\"officia sed cillum\",\"businessCode\":\"12\",\"servicePhone\":\"47389024522\",\"serviceCodes\":\"79\",\"siteInfo\":{\"key\":\"in dolore\"},\"jsapiPath\":\"/lost+found\",\"subAppId\":\"37\",\"source\":\"aute tempor cupidatat nostrud officia\",\"sourceName\":\"接治文\"},\"aliPay\":{\"businessId\":\"1301\",\"cardType\":\"000U\",\"calcType\":\"1\",\"calcVal\":\"in ipsum consequat\",\"feeHighLimit\":\"occaecat Excepteur nulla sint\",\"feeLowLimit\":\"incididunt cupidatat in\",\"channelMccCode\":\"70\",\"serviceCodes\":\"50\",\"siteInfo\":{\"key\":\"aute id pariatur\"},\"source\":\"non in laborum in\",\"sourceName\":\"茹鹏\"},\"union\":[{\"businessId\":\"1404\",\"cardType\":\"000U\",\"isDiscount\":\"0\",\"calcType\":\"1\",\"calcVal\":\"Ut commodo\",\"feeHighLimit\":\"eu occaecat pariatur commodo\",\"feeLowLimit\":\"sit\"},{\"businessId\":\"1401\",\"cardType\":\"XXXX\",\"isDiscount\":\"1\",\"calcType\":\"1\",\"calcVal\":\"consectetur Duis commodo id\",\"feeHighLimit\":\"exercitation ullamco labore pariatur\",\"feeLowLimit\":\"eiusmod consequat culpa nostrud\"}],\"bankCard\":[{\"businessId\":\"1001\",\"calcType\":\"1\",\"isDiscount\":\"1\",\"calcVal\":\"Excepteur est\",\"cardType\":\"000D\",\"feeHighLimit\":\"dolor qui veniam ipsum\",\"feeLowLimit\":\"ad ex magna incididunt\"},{\"businessId\":\"1002\",\"calcType\":\"1\",\"isDiscount\":\"1\",\"calcVal\":\"Lorem aute nostrud tempor\",\"cardType\":\"000C\",\"feeHighLimit\":\"ut qui sint Duis\",\"feeLowLimit\":\"Excepteur\"}],\"foreignCard\":[{\"businessId\":\"1101\",\"isDiscount\":\"0\",\"feeType\":\"DCC\",\"calcType\":\"1\",\"calcVal\":\"irure pariatur\",\"feeHighLimit\":\"ipsum Duis\",\"feeLowLimit\":\"eiu\",\"cardType\":\"0|0C\",\"dccFlag\":\"1\",\"mchtEngBusinName\":\"阳熙成\",\"merZip\":\"234.9.227.86\"},{\"businessId\":\"1101\",\"isDiscount\":\"1\",\"feeType\":\"DCC\",\"calcType\":\"1\",\"calcVal\":\"labore \",\"feeHighLimit\":\"volup\",\"feeLowLimit\":\"in anim i\",\"cardType\":\"CC|D\",\"dccFlag\":\"1\",\"mchtEngBusinName\":\"冯杰\",\"merZip\":\"53.223.233.100\"},{\"businessId\":\"1102\",\"isDiscount\":\"0\",\"feeType\":\"EDC\",\"calcType\":\"1\",\"calcVal\":\"\",\"feeHighLimit\":\"\",\"feeLowLimit\":\"\",\"cardType\":\"X|00\",\"dccFlag\":\"1\",\"mchtEngBusinName\":\"寻文韬\",\"merZip\":\"195.95.244.149\"}],\"dcep\":{\"businessId\":\"28\",\"cardType\":\"$4b\",\"calcType\":\"\",\"calcVal\":\"[\",\"feeLowLimit\":\"KmFi6^5BsZQ^n\",\"feeHighLimit\":\"nSesJRbliKx2dj[29\",\"bocBusinessCode\":\"8\"},\"prePay\":{\"businessId\":\"64\",\"cardType\":\"p\",\"calcType\":\"\",\"calcVal\":\"$axMWEj^kh3\",\"feeLowLimit\":\"PF9#j!RrD(zwcxEk\",\"feeHighLimit\":\"f&$MdtcGvKYi#\"},\"refund\":{\"businessId\":\"1703\"},\"tf\":{\"businessId\":\"2102\",\"cardType\":\"Z*A\",\"calcType\":\"\",\"calcVal\":\"rw#5eX*2dnt@\",\"feeLowLimit\":\"IdgDwJkC\",\"feeHighLimit\":\"7roiGY\"},\"wd\":{\"businessId\":\"2001\",\"cardType\":\"8\",\"calcType\":\"\",\"calcVal\":\"hskTt\",\"feeLowLimit\":\"puuDKdMRVTk\",\"feeHighLimit\":\"%NEVmQeW6VGY\"},\"orderAcct\":{\"businessId\":\"11\",\"cardType\":\"zV\",\"calcType\":\"\",\"calcVal\":\"Gac!v7lekCi4tR\",\"feeLowLimit\":\"MeBD\",\"feeHighLimit\":\"!\"}},\"licInfoInfo\":{\"licenseType\":\"\",\"licenseNo\":\"!ie2NO2zFJ)ca[fkMiC*dwDH\",\"licenseNoValidityStart\":\"p4!\",\"licenseNoValidityEnd\":\"JfXsaeJsFaw!Hz\",\"licenseName\":\"康超\",\"licenseCapital\":\"ipsum\",\"licenseScope\":\"non veniam adipisicing minim\",\"licenseAddr\":\"sunt Excepteur labore culpa consectetur\",\"licenseLegalName\":\"苑勇\",\"legalName\":\"将辉\",\"legalIdType\":\"*x\",\"legalIdNo\":\"htyUC)TwLcMNYaH\",\"legalIdValidityStart\":\"hq4LWJCxXN#\",\"legalIdValidityEnd\":\"9A60p\",\"legalMobile\":\"a\",\"legalIdAddr\":\"53\",\"controlerislegal\":\"0\",\"controlerName\":\"雀开慧\",\"controlerIdType\":\"i\",\"controlerIdNo\":\"LcU7qgI)z0#^f8]!gO\",\"controlerIdValidityStart\":\"fm2vdLOHMA\",\"controlerIdValidityEnd\":\"7hyz2Se6KAz\",\"controlerIdAddr\":\"61\",\"controlerCapital\":\"PGIo!m3b^cWlp5(j\",\"controlerDate\":\"!gg%Q^q&8qDj%qeHugdfllG\",\"controlerRate\":\"&&S$woj!]q7Wv1RL9f7r56(ylmy\"},\"plusInfo\":{\"contractMode\":\"0\",\"contractNo\":\"consequat velit nisi Excepteur\",\"signatoryName\":\"邴伟\",\"webSite\":\"Duis sunt in irure\",\"webName\":\"沈洁\",\"webIcp\":\"ONRQIQFSXXX\",\"additionMess\":\"voluptate ea in\",\"bankBranchName\":\"宗政宇泽\",\"bankBranchManager\":\"qui\",\"remarkJson\":\"et id eiusmod Lorem irure\"},\"beneficiaryInfoList\":[{\"beneficiaryIsLegal\":\"eiusmod occaecat ad esse eu\",\"beneficiaryName\":\"千静\",\"beneficiaryIdType\":\"19\",\"beneficiaryIdNo\":\"53\",\"beneficiaryIdAddr\":\"57\",\"beneficiaryIdValidityStart\":\"62\",\"beneficiaryIdValidityEnd\":\"29\",\"beneficiaryRate\":\"voluptate \"}],\"storeList\":[{\"storeName\":\"实奕泽\",\"storeArea\":\"laborum quis aute\",\"storeAddr\":\"amet\",\"longitude\":\"-90.6543\",\"latitude\":\"32.7401\",\"linkMan\":\"https://noxious-vision.biz/\",\"linkMobile\":\"g9kpY3d\",\"linkIdType\":\"xJ\",\"linkIdNo\":\"&rqRD2rEAV\",\"linkIdValidityStart\":\"mjx4D\",\"linkIdValidityEnd\":\"WGpX)DXbeDVutwi\",\"linkIdAddr\":\"https://ambitious-secrecy.net/\",\"storePictureList\":[{\"picType\":\"F[\",\"picPath\":\"V*I*cdjeN*x(6G@fbEOz!Qes4T8jveL0(x7s94@cJtsH64bRZdZ8\"},{\"picType\":\"of\",\"picPath\":\"0tgDqDrk&D0Rz93wV#e0cfv@wqUBi26ReLD^teMpF!]IuY\"},{\"picType\":\"W\",\"picPath\":\"!Fm!w!pft(H0u[VAW9FQ]cEz0gqJPcwfOXGct7)Y(f[1sp\"}],\"storeTerminfoList\":[{\"termMode\":\"1\",\"termModel\":\"fV21De$UO&&h4(8X6ofzH@CK[ZXYVI[ddRb9qfomf\",\"termModelType\":\"08\",\"termModelLic\":\"WUd^$Lkej3wc^4dw7U(oGt\",\"termCretCode\":\"55\",\"termMafNm\":\"ut\",\"termConnMd\":\"laboris cillum aliqua magna\",\"ncRemark\":\"sint voluptate sunt Excepteur ullamco\",\"ncStatus\":\"proident dolor ut eiusmod\"},{\"termMode\":\"7\",\"termModel\":\"CyLd3wX4dmfXR$fdY%uO#ENbR9TPUn(gs)w1t1GApLJs(kmO0EuJrwh!\",\"termModelType\":\"10\",\"termModelLic\":\"lq8E%[EW20Zf$tq8hZ%j8I]C7R)\",\"termCretCode\":\"51\",\"termMafNm\":\"sunt eiusmod sed in dolor\",\"termConnMd\":\"elit id\",\"ncRemark\":\"laborum\",\"ncStatus\":\"aute anim eu veniam\"},{\"termMode\":\"2\",\"termModel\":\"[No1UFx\",\"termModelType\":\"11\",\"termModelLic\":\"#ilSU1l]1x#M0w2&fInqX%VzU[g\",\"termCretCode\":\"46\",\"termMafNm\":\"amet ad dolore Ut\",\"termConnMd\":\"veniam laborum\",\"ncRemark\":\"et qui elit in\",\"ncStatus\":\"fugiat\"}]},{\"storeName\":\"翟梓妍\",\"storeArea\":\"occaecat ullamco\",\"storeAddr\":\"aliqua Excepteur\",\"longitude\":\"158.7283\",\"latitude\":\"1.6591\",\"linkMan\":\"https://any-pasta.biz/\",\"linkMobile\":\"#OlHr1\",\"linkIdType\":\"N\",\"linkIdNo\":\")^tmOFNkgSEwudr2W^&\",\"linkIdValidityStart\":\"E\",\"linkIdValidityEnd\":\"Dkct3#Wk*YyeFrI\",\"linkIdAddr\":\"https://nocturnal-fog.org/\",\"storePictureList\":[{\"picType\":\"Q\",\"picPath\":\"RB0IDk4moQbR&lqIP\"},{\"picType\":\"6W\",\"picPath\":\"3)0DtyXuJ5dSVzeC^1l\"},{\"picType\":\"@\",\"picPath\":\"E3@string(16,64)vAn2jRK&)3!cQ4YMal\"}],\"storeTerminfoList\":[{\"termMode\":\"2\",\"termModel\":\"N@WME9LI8wK\",\"termModelType\":\"04\",\"termModelLic\":\"qEBmRf6(6&FA#rEZrI^\",\"termCretCode\":\"8\",\"termMafNm\":\"Duis in aute\",\"termConnMd\":\"voluptate non aute\",\"ncRemark\":\"sunt in esse\",\"ncStatus\":\"consectetur cupidatat\"}]},{\"storeName\":\"樊凤英\",\"storeArea\":\"elit voluptate\",\"storeAddr\":\"et\",\"longitude\":\"13.1617\",\"latitude\":\"-65.9896\",\"linkMan\":\"https://trained-sundae.info/\",\"linkMobile\":\"M\",\"linkIdType\":\"\",\"linkIdNo\":\"hR\",\"linkIdValidityStart\":\"Je%lJ!e0z*B!#VH\",\"linkIdValidityEnd\":\"0CKp6Oq&2Q1NxKR\",\"linkIdAddr\":\"https://energetic-lieu.com/\",\"storePictureList\":[{\"picType\":\"vD\",\"picPath\":\"T%kz9BQ!zBjY)yaAnZ1L0tvLhOD8@0T&T99piilXC1h*6j]msOhu\"},{\"picType\":\"sy\",\"picPath\":\"^!zp0R02APK9@#(UlJNBgX40TKbfgccjt)bW2If\"},{\"picType\":\"W\",\"picPath\":\"c%nKVgQx)grs%crL&DjK&jXRB\"}],\"storeTerminfoList\":[{\"termMode\":\"0\",\"termModel\":\"8fI9iERjdk5iWW!NKzt[O3F^p3BnLTTN@j%T\",\"termModelType\":\"09\",\"termModelLic\":\"Kk(9A0I@J8M8YsurbEbeeVf(qs@l\",\"termCretCode\":\"74\",\"termMafNm\":\"sunt ullamco\",\"termConnMd\":\"sed laborum reprehenderit cupidatat\",\"ncRemark\":\"laborum\",\"ncStatus\":\"esse\"},{\"termMode\":\"7\",\"termModel\":\"jWPx2JT\",\"termModelType\":\"04\",\"termModelLic\":\"0IFQ@rSLkPQTUS\",\"termCretCode\":\"73\",\"termMafNm\":\"laborum commodo dolor dolore\",\"termConnMd\":\"cillum cupidatat\",\"ncRemark\":\"Ut cupidatat\",\"ncStatus\":\"enim nisi ipsum sit labore\"},{\"termMode\":\"0\",\"termModel\":\"2XU&9&L!%xPP\",\"termModelType\":\"02\",\"termModelLic\":\"nOnjRbp\",\"termCretCode\":\"26\",\"termMafNm\":\"cupidatat proident ex ea adipisicing\",\"termConnMd\":\"occaecat dolor anim consequat sint\",\"ncRemark\":\"reprehenderit in aliquip deserunt\",\"ncStatus\":\"in Ut esse consequat laboris\"}]}],\"payAccInfo\":{\"acctMode\":\"1\",\"acctType\":\"00\",\"bankcardNo\":\"旅行车\",\"bankcardName\":\"掀背车\",\"bankName\":\"真开慧\",\"bankNo\":\"U\",\"accMobile\":\"n\",\"accIdType\":\"X\",\"accIdNo\":\"e5dUgNYCHQH!TtxrCTC\",\"accIdValidityStart\":\"Ntt)Tx%PFQM61\",\"accIdValidityEnd\":\"\",\"accIdAddr\":\"9\"},\"settleInfoList\":[{\"businessId\":\"411222||0\",\"spayTime\":\"2026-06-27 23:08:16\",\"calcType\":\"\",\"cardType\":\"sj\",\"calcVal\":\"LW[)\",\"feeHighLimit\":\"p\",\"feeLowLimit\":\"\"}],\"productInfoList\":[{\"productCode\":\"XUEIM]LZ7\"}],\"pictureList\":[{\"picType\":\"01\",\"picPath\":\"4eqfNsGDOQi(fM%)A11(ADKD&otMAM2*XBYN$[AUAhguKA%mlm1Q\"},{\"picType\":\"01\",\"picPath\":\"jTVFmVX*K$VLg(QY\"}]}"; +// JSONObject jsonObject2 = JSONObject.parseObject(s2); +// TreeMap reqBodyMap = new TreeMap<>(jsonObject2); + // ======= 测试数据end ====== + + TreeMap reqHeaderMap = new TreeMap<>(JSONObject.from(reqHeader)); + TreeMap reqBodyMap = new TreeMap<>(JSONObject.from(apiReqBody)); + // 递归排序 + sortMap(reqHeaderMap); + sortMap(reqBodyMap); + String md5Str = SecureUtil.md5(JSON.toJSONString(reqBodyMap)).toUpperCase(); + String sourceStr = StrBuilder.create(JSON.toJSONString(reqHeaderMap)).append(md5Str).toString(); + log.debug("待加签字符串为:{}", sourceStr); + Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, easypayConfig.getMerRsaPrivateKey(), null); + byte[] signatureBytes = sign.sign(sourceStr.getBytes(CharsetUtil.CHARSET_UTF_8)); + String signStr = Base64.encode(signatureBytes); + log.debug("加签结果为:{}", signStr); + return signStr; + } + + /** + * 递归排序 Map 中的嵌套对象 + */ + private void sortMap(Map map) { + for (Map.Entry entry : map.entrySet()) { + Object value = entry.getValue(); + if (value instanceof JSONObject) { + TreeMap sortedMap = new TreeMap<>((JSONObject) value); + sortMap(sortedMap); + map.put(entry.getKey(), sortedMap); + } else if (value instanceof JSONArray) { + JSONArray array = (JSONArray) value; + for (int i = 0; i < array.size(); i++) { + Object item = array.get(i); + if (item instanceof JSONObject) { + TreeMap sortedMap = new TreeMap<>((JSONObject) item); + sortMap(sortedMap); + array.set(i, sortedMap); + } + } + } + } + } + + /** + * 封装支付信息 + * + * @param apiReqBody + * @param paymentBO + */ + private void setPayInfo(JsApiReqBody apiReqBody, PaymentBO paymentBO) { + PayInfo payInfo = PayInfo.builder() + .payType(paymentBO.getPayType().toValue()) + .transDate(DateUtils.parseDateToStr(FormatsType.YYYYMMDD, new Date())) + .build(); + apiReqBody.setPayInfo(payInfo); + if (paymentBO.getPayType() == PayType.ALI_PAY_JSAPI) { + Assert.isTrue(StrUtil.isNotBlank(paymentBO.getBuyerId()), () -> new ServiceException("支付宝支付需传入买家ID")); + apiReqBody.setAliBizParam(AliBizParam.builder().buyerId(paymentBO.getBuyerId()).build()); + } + if (paymentBO.getPayType() == PayType.WE_CHAT_JSAPI) { + Assert.isTrue(StrUtil.isAllNotBlank(paymentBO.getSubOpenId()), () -> new ServiceException("微信支付需传入appId和openId")); + apiReqBody.setWxBizParam(WxBizParam.builder().subAppid(wechatMiniProgramConfig.getAppId()).subOpenId(paymentBO.getSubOpenId()).build()); + } + if (paymentBO.getPayType() == PayType.UNION_PAY_JSAPI) { + Assert.isTrue(StrUtil.isAllNotBlank(paymentBO.getTransType(), paymentBO.getUserAuthCode(), + paymentBO.getUserId(), paymentBO.getAreaInfo(), paymentBO.getPaymentValidTime(), + paymentBO.getQrCode(), paymentBO.getQrCodeType()), () -> new ServiceException("银联支付传入参数不完整")); + QrBizParam qrBizParam = BeanUtil.copyProperties(paymentBO, QrBizParam.class); + apiReqBody.setQrBizParam(qrBizParam); + } + } + + /** + * 封装基础订单信息 + * + * @param apiReqBody + */ + private void setReqOrderInfo(JsApiReqBody apiReqBody, PaymentBO paymentBO) { + String orgTrace = StrBuilder.create(TRACE_PREFIX) + .append(DateUtils.parseDateToStr(FormatsType.YYYYMMDDHHMMSS, new Date())) + .append(RandomUtil.randomString(4)) + .toString(); + JsapiReqOrderInfo jsapiReqOrderInfo = JsapiReqOrderInfo.builder() + .orgTrace(orgTrace) + .transAmount(paymentBO.getTransAmount()) + .backUrl(easypayConfig.getBackUrl()) + .timeout(String.valueOf(TRACE_TIMEOUT)) + .orderSub(paymentBO.getOrderSub()) + .orderDes(paymentBO.getOrderDes()) + .build(); + apiReqBody.setReqOrderInfo(jsapiReqOrderInfo); + } + + /** + * 生成易生接口请求头参数 + * + * @return + */ + private EasyPayRequestHeader generateEasyPayRequestHeader() { + return EasyPayRequestHeader.builder() + .reqType(REQ_TYPE) + .reqId(easypayConfig.getReqId()) + .transTime(DateUtils.parseDateToStr(FormatsType.YYYYMMDDHHMMSS, new Date())) + .build(); + } + + @Override + public EasypayAccountVO getEasypayAccount(Long memberId) { + return EasypayAccountVO.builder().balance(new BigDecimal(1000)).build(); + } + + + @Override + public void handlerTradeCallback(EasyPayRequest request) { + + } + + @Override + public boolean withdraw(Long memberId, BigDecimal money) { + return true; + } + + @Override + public boolean syncMemberAccount() { + return true; + } + + @Override + public void openMerchant(Long memberId) { + + } + + @Override + public EasypayAccountVO getMerchantAccount(Long memberId) { + return null; + } + + @Override + public boolean split(Long memberId, BigDecimal money) { + return false; + } + + @Override + public boolean merchantAuditNotice(String request) { + return false; + } + + +} diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/impl/WithdrawServiceImpl.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/impl/WithdrawServiceImpl.java index cfec072b6..5c7bdf6d7 100644 --- a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/impl/WithdrawServiceImpl.java +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/impl/WithdrawServiceImpl.java @@ -9,12 +9,11 @@ import com.wzj.soopin.member.enums.AccountBillChangeTypeEnum; import com.wzj.soopin.member.enums.AccountBillSourceEnum; import com.wzj.soopin.transaction.enums.WithdrawAuditStatus; import com.wzj.soopin.transaction.enums.WithdrawStatus; -import com.wzj.soopin.transaction.enums.WithdrawType; import com.wzj.soopin.transaction.mapper.WithdrawMapper; import com.wzj.soopin.member.service.*; -import com.wzj.soopin.transaction.domain.vo.YishengAccountVO; +import com.wzj.soopin.transaction.domain.vo.EasypayAccountVO; import com.wzj.soopin.transaction.service.IWithdrawService; -import com.wzj.soopin.transaction.service.IYishengService; +import com.wzj.soopin.transaction.service.IEasypayService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.dromara.common.satoken.utils.LoginHelper; @@ -44,7 +43,7 @@ public class WithdrawServiceImpl extends ServiceImpl i /** * 易生账户充值服务 */ - private final IYishengService yishengService; + private final IEasypayService easypayService; private final IAccountBillService accountBillService; @@ -58,7 +57,7 @@ public class WithdrawServiceImpl extends ServiceImpl i throw new RuntimeException("提现申请已处理"); } //发起提现 - boolean chargeSuccess = yishengService.withdraw(withdraw.getMemberId(), withdraw.getMoney()); + boolean chargeSuccess = easypayService.withdraw(withdraw.getMemberId(), withdraw.getMoney()); if (!chargeSuccess) { throw new RuntimeException("提现失败"); } @@ -80,14 +79,14 @@ public class WithdrawServiceImpl extends ServiceImpl i //从易生取,别用自己计算的 //// TODO: 2025/6/21 测试的时候用计算的 测试完用易生的 BigDecimal finalBalance = balance.subtract(withdraw.getMoney()); - YishengAccountVO yishengAccountVO = yishengService.getYishengAccount(withdraw.getMemberId()); + EasypayAccountVO easypayAccountVO = easypayService.getEasypayAccount(withdraw.getMemberId()); memberAccountService.updateById(memberAccount.toBuilder().wallet(finalBalance).build()); //生成账户变动记录bh AccountBill memberAccountChangeRecord = AccountBill.builder() .accountId(withdraw.getMemberId()) .moneyBalance(finalBalance) .beforeBalance(balance) - .afterBalance(yishengAccountVO.getBalance()) + .afterBalance(easypayAccountVO.getBalance()) .changeType(AccountBillChangeTypeEnum.OUT.getCode()) .changeDesc("提现") .source(AccountBillSourceEnum.WITHDRAW.getCode()).build(); @@ -110,9 +109,9 @@ public class WithdrawServiceImpl extends ServiceImpl i throw new RuntimeException("用户不存在"); } BigDecimal balance = tenantAccount.getRevenue(); - YishengAccountVO yishengAccountVO = yishengService.getYishengAccount(withdraw.getMemberId()); + EasypayAccountVO easypayAccountVO = easypayService.getEasypayAccount(withdraw.getMemberId()); - if (yishengAccountVO == null) { + if (easypayAccountVO == null) { throw new RuntimeException("账户余额获取失败"); } //生成费用 diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/impl/WxAuthService.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/impl/WxAuthService.java index eda54a02f..fccff7c0b 100644 --- a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/impl/WxAuthService.java +++ b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/impl/WxAuthService.java @@ -1,6 +1,8 @@ package com.wzj.soopin.transaction.service.impl; +import cn.hutool.core.text.StrBuilder; import com.wzj.soopin.transaction.domain.entity.WxAuthResponse; +import com.wzj.soopin.transaction.config.WechatMiniProgramConfig; import com.wzj.soopin.transaction.wechat.WechatPayConfig; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -14,10 +16,14 @@ public class WxAuthService { @Autowired private WechatPayConfig wechatPayConfig; + @Autowired + private WechatMiniProgramConfig wechatMiniProgramConfig; + private RestTemplate restTemplate = new RestTemplate(); /** - * 通过code获取openid + * 网页-通过code获取openid + * * @param code 授权码 * @return 包含openid的响应对象 */ @@ -34,5 +40,27 @@ public class WxAuthService { return response; } + /** + * 小程序登录-通过code获取openid + * + * @param code 授权码 + * @return 包含openid的响应对象 + */ + public WxAuthResponse getOpenIdByMiniProgramCode(String code) { + String url = StrBuilder.create("https://api.weixin.qq.com/sns/jscode2session?appid=") + .append(wechatMiniProgramConfig.getAppId()) + .append("&secret=") + .append(wechatMiniProgramConfig.getSecret()) + .append("&js_code=") + .append(code) + .append("&grant_type=authorization_code") + .toString(); + WxAuthResponse response = restTemplate.getForObject(url, WxAuthResponse.class); + if (response == null || response.getOpenid() == null) { + throw new RuntimeException("Failed to get openid from WeChat"); + } + return response; + } + } diff --git a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/impl/YishengServiceImpl.java b/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/impl/YishengServiceImpl.java deleted file mode 100644 index a4a7af28a..000000000 --- a/ruoyi-modules/ruoyi-transaction/src/main/java/com/wzj/soopin/transaction/service/impl/YishengServiceImpl.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.wzj.soopin.transaction.service.impl; - - -import com.wzj.soopin.transaction.domain.vo.YishengAccountVO; -import com.wzj.soopin.transaction.service.IYishengService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import java.math.BigDecimal; - -/** - * 会员封禁 - * - * @author zcc - */ -@Service -@RequiredArgsConstructor -@Slf4j -public class YishengServiceImpl implements IYishengService { - - - @Override - public YishengAccountVO getYishengAccount(Long memberId) { - return YishengAccountVO.builder().balance(new BigDecimal(1000)).build(); - } - - @Override - public boolean withdraw(Long memberId, BigDecimal money) { - return true; - } - - @Override - public boolean syncMemberAccount() { - return true; - } - - @Override - public void openMerchant(Long memberId) { - - } - - @Override - public YishengAccountVO getMerchantAccount(Long memberId) { - return null; - } - - @Override - public boolean split(Long memberId, BigDecimal money) { - return false; - } - - @Override - public boolean merchantAuditNotice(String request) { - return false; - } -}