Merge remote-tracking branch 'origin/wzj-main' into wzj-main

# Conflicts:
#	ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/VlogService.java
#	ruoyi-modules/ruoyi-content/src/main/java/com/wzj/soopin/content/service/impl/VlogServiceImpl.java
#	ruoyi-modules/ruoyi-member/src/main/java/com/wzj/soopin/member/service/IMemberService.java
This commit is contained in:
haolj 2025-08-29 19:38:49 +08:00
commit 1aebe07488
125 changed files with 6536 additions and 1271 deletions

View File

@ -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<Page<CommentVO>> queryVlogComments(
@ -66,7 +70,7 @@ public class AppCommentController {
commentService.createComment(bo);
// 2) 短视频评论总数 +1Redis 优先
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())) {

View File

@ -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<Page<IndexVlogVO>> 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<IndexVlogVO> pages = vlogService.getIndexVlogList(bo, page);
Page<IndexVlogVO> pages = pullService.page(page);
return R.ok(pages);
}
@GetMapping("/detail/{vlogId}")
public R<Object> detail( @PathVariable String vlogId) {
public R<Object> 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<IndexVlogVO> pages = vlogService.getMyFollowVlogList( page);
Page<IndexVlogVO> 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<IndexVlogVO> pages = vlogService.getMyFriendVlogList( page);
Page<IndexVlogVO> pages = vlogService.getMyFriendVlogList(page);
return R.ok(pages);
}
@PostMapping("publish")
public R<Void> 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<Page<IndexVlogVO>> 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<Void> 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<String, Object> 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<Void> unlike(@RequestBody Map<String, String> params) {
@PostMapping("/unlike")
public R<Void> 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<Void> 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<Void> 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<Object> 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<Map<String, Object>> resultList = new ArrayList<>();
if (StringUtils.isNotBlank(cachedData)) {
// 解析JSON数据
ObjectMapper objectMapper = new ObjectMapper();
List<Map<String, Object>> vlogList = objectMapper.readValue(cachedData, new com.fasterxml.jackson.core.type.TypeReference<List<Map<String, Object>>>() {});
// 计算分页
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<Map<String, Object>> 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());
}
}
}

View File

@ -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

View File

@ -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:

View File

@ -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";
}

View File

@ -48,6 +48,10 @@
<version>2.15.2</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>5.3.0</version>
</dependency>
</dependencies>
</project>

View File

@ -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;
}
}

View File

@ -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<String, LitePullConsumer> 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 {
}
}
}

View File

@ -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<String, UserConsumerContext> 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<MessageExt> pullUserMessages(String userId , int batchSize)
throws MQClientException, InterruptedException, MQBrokerException, RemotingException {
validateUserId(userId);
UserConsumerContext context = getUserConsumerContext(userId);
// 更新最后活动时间
context.lastActiveTime = System.currentTimeMillis();
// 获取用户的消息队列
Set<MessageQueue> mqs = context.consumer.fetchSubscribeMessageQueues(context.topic);
List<MessageExt> 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<String> 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; // 初始活动时间=创建时间
}
}
}

View File

@ -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 <T> List<T> 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<String> 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<String> 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;
}
}

View File

@ -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;
}
}

View File

@ -65,11 +65,16 @@ public class LoginHelper {
*/
@SuppressWarnings("unchecked cast")
public static <T extends LoginUser> 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);
}
/**

View File

@ -27,7 +27,7 @@ import java.util.List;
*/
@Tag(name = "文章分类管理")
@RestController
@RequestMapping("/content/article/category")
@RequestMapping("/cms/article/category")
@RequiredArgsConstructor
public class ArticleCategoryController {

View File

@ -27,7 +27,7 @@ import java.util.List;
*/
@Tag(name = "内容管理")
@RestController
@RequestMapping("/content/article")
@RequestMapping("/cms/article")
@RequiredArgsConstructor
public class ArticleController {

View File

@ -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<IPage<IndexVlogVO>> page(@RequestBody VlogBO bo, @RequestBody Page page) {
Page<IndexVlogVO> pages = vlogService.getIndexVlogList(bo ,page);
return R.ok(pages);
}
@PostMapping("/vodCallBack")
public R<Void> vodCallBack(@RequestBody Map<String, Object> callbackData) {
try {
// 解析回调事件类型
@ -160,7 +168,7 @@ public class VlogController extends BaseInfoProperties {
@PostMapping("changeVlogStatus")
@PostMapping("/changeVlogStatus")
public R<Void> 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<Void> 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<Void> 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<Integer> 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));
}
}

View File

@ -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 {

View File

@ -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<IPage<Map<String, Object>>> 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<Map<String, Object>> page = new Page<>(vlogBO.getCurrent(), vlogBO.getSize());
// 调用服务层方法进行查询
IPage<Map<String, Object>> 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<String, Object> 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<String, String> fileIdToVlogIdMap = new HashMap<>();
List<String> allVlogIds = new ArrayList<>();
if (resp.getMediaInfoSet() != null) {
for (MediaInfo mediaInfo : resp.getMediaInfoSet()) {
String fileId = mediaInfo.getFileId();
Map<String, Object> statistics = vlogService.getVlogStatistics(fileId);
String vlogId = (String) statistics.get("vlogId");
if (StringUtils.isNotBlank(vlogId)) {
fileIdToVlogIdMap.put(fileId, vlogId);
allVlogIds.add(vlogId);
}
}
}
// 批量获取所有点赞数据
Map<String, Integer> vlogLikeCounts = new HashMap<>();
Map<String, Integer> vlogCommentCounts = new HashMap<>();
long totalLikeCounts = 0;
long totalCommentCounts = 0;
if (!allVlogIds.isEmpty()) {
// 批量获取所有点赞键
String likePattern = REDIS_USER_LIKE_VLOG + ":*";
Collection<String> allLikeKeys = RedisUtils.keys(likePattern);
if (allLikeKeys != null && !allLikeKeys.isEmpty()) {
// 逐个获取点赞值由于RedisUtils没有批量方法
Map<String, String> 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<Map<String, Object>> mediaList = new ArrayList<>();
if (resp.getMediaInfoSet() != null) {
for (MediaInfo mediaInfo : resp.getMediaInfoSet()) {
Map<String, Object> 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<String, Object> 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<String, String> 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<Map<String, Object>> 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());
}
}
/**
* 视频审核接口
*/

View File

@ -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;

View File

@ -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<String> blockVd;
private List<String> blockUser;
private List<String> ids;
@Override
public LambdaQueryWrapper<Vlog> toWrapper() {
LambdaQueryWrapper<Vlog> wrapper = new LambdaQueryWrapper<>();

View File

@ -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());
}
}

View File

@ -168,4 +168,8 @@ public interface VlogMapper extends BaseMapper<Vlog> {
"ORDER BY RAND() " +
"LIMIT #{limit}")
List<Map<String, Object>> selectRandomVlogs(@Param("limit") int limit);
IPage<String> getVlogForUser(Page<IndexVlogVO> page, @Param("memberId") Long memberId);
int readVlog(@Param("memberId") Long memberId, @Param("vlogId") String vlogId);
}

View File

@ -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<Vlog, VlogerVO> {
List<IndexVlogVO> getIndexVlogList(@Param("paramMap") Map<String, Object> map);
List<IndexVlogVO> getIndexVlogList(@Param("bo") VlogBO bo);
Page<IndexVlogVO> getIndexVlogList(@Param("paramMap") Map<String, Object> map, Page<IndexVlogVO> page);
Page<IndexVlogVO> getIndexVlogList(@Param("bo") VlogBO bo, Page<IndexVlogVO> page);
List<IndexVlogVO> getVlogDetailById(@Param("paramMap") Map<String, Object> map);

View File

@ -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<IndexVlogVO> page(Page<IndexVlogVO> page);
void pullVlog(VlogBO vlogBO);
}

View File

@ -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);
}

View File

@ -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<Vlog> {
/**
* 修改视频首帧图
*/
@ -115,10 +116,6 @@ public interface VlogService {
*/
List<Map<String, Object>> getLikedUsers(String vlogId);
/**
* 获取视频点赞数
*/
int getLikeCounts(String vlogId);
/**
* 获取视频上传者信息
@ -135,11 +132,6 @@ public interface VlogService {
*/
IPage<Map<String, Object>> getVlogListByMobile(Page<Map<String, Object>> page, VlogBO vlogBO);
/**
* 查询点赞最多的视频列表并存储到Redis
* @param limit 查询数量限制
*/
void cacheTopLikedVlogs(int limit);
/**
* 随机查询视频列表
@ -148,8 +140,15 @@ public interface VlogService {
*/
List<Map<String, Object>> getRandomVlogs(int limit);
IPage<String> getVlogForUser(Page<IndexVlogVO> page, Long memberId);
int readVlog(Long memberId, String vlogId);
List<IndexVlogVO> getIndexVlogList(VlogBO bo);
Page<IndexVlogVO> getIndexVlogList(VlogBO bo,Page page);
/**
* 首页搜索/搜索的vlog列表
*/
Page<IndexVlogVO> getIndexSearchVlogList(SearchBO bo, Page page);
}

View File

@ -26,13 +26,6 @@ public interface VlogUploadService {
// */
// Map<String, Object> getTaskStatus(String taskId) throws Exception;
/**
* 搜索媒体文件
*
* @param req 搜索请求
* @return 搜索响应
*/
SearchMediaResponse searchMedia(SearchMediaRequest req);
// 新增视频审核
void auditVlog(String vlogId, Integer status, String reason);

View File

@ -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 {

View File

@ -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<IndexVlogVO> page(Page<IndexVlogVO> page) {
List<MessageExt> 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<String> 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<IndexVlogVO> 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<IndexVlogVO> 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<Map<String, Object>> page = new Page<>(vlogBO.getCurrent(), vlogBO.getSize());
// 调用服务层方法进行查询
IPage<Map<String, Object>> 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<String, Object> 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<String, String> fileIdToVlogIdMap = new HashMap<>();
List<String> allVlogIds = new ArrayList<>();
if (resp.getMediaInfoSet() != null) {
for (MediaInfo mediaInfo : resp.getMediaInfoSet()) {
String fileId = mediaInfo.getFileId();
Map<String, Object> statistics = vlogService.getVlogStatistics(fileId);
String vlogId = (String) statistics.get("vlogId");
if (StringUtils.isNotBlank(vlogId)) {
fileIdToVlogIdMap.put(fileId, vlogId);
allVlogIds.add(vlogId);
}
}
}
// 批量获取所有点赞数据
Map<String, Integer> vlogLikeCounts = new HashMap<>();
Map<String, Integer> vlogCommentCounts = new HashMap<>();
long totalLikeCounts = 0;
long totalCommentCounts = 0;
if (!allVlogIds.isEmpty()) {
// 批量获取所有点赞键
String likePattern = REDIS_USER_LIKE_VLOG + ":*";
Collection<String> allLikeKeys = RedisUtils.keys(likePattern);
if (allLikeKeys != null && !allLikeKeys.isEmpty()) {
// 逐个获取点赞值由于RedisUtils没有批量方法
Map<String, String> 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<Map<String, Object>> mediaList = new ArrayList<>();
if (resp.getMediaInfoSet() != null) {
for (MediaInfo mediaInfo : resp.getMediaInfoSet()) {
Map<String, Object> 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<String, Object> 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<String, String> 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");
// }
}
}

View File

@ -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<String> rankSet = redisCache.zSetRange(REDIS_VLOG_BE_LIKED_COUNTS, 0, count);
if (rankSet == null ||rankSet.isEmpty()) {
return false;
}
if(rankSet!=null&&rankSet.size()<count){
//从数据库中获取差量数据
Page<Vlog> vlogPage = new Page<>(1, count-rankSet.size());
vlogPage = vlogService.page(vlogPage, new LambdaQueryWrapper<Vlog>()
.orderByDesc(Vlog::getLikeCounts));
Set<String> vlogIds = vlogPage.getRecords().stream().map(vlog -> vlog.getId()).collect(Collectors.toSet());
rankSet.addAll(vlogIds);
}
// 3. Set 转换为 List 以便打乱顺序
List<String> 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<String> 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;
}
}

View File

@ -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<VlogMapper, Vlog> 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<IndexVlogVO> 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<Vlog> pageParam = new Page<>(current, size);
LambdaQueryWrapper<Vlog> 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<String> blockVdList = new ArrayList<>();
ObjectMapper objectMapper = new ObjectMapper();
List<String> reports = redis.lrange(redisKey, 0, -1);
for (String report : reports) {
try {
Map<String, Object> reportMap = objectMapper.readValue(report, new TypeReference<Map<String, Object>>() {});
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<String> blockUserList = new ArrayList<>();
ObjectMapper objectMapper = new ObjectMapper();
List<String> reports = redis.lrange(redisKey, 0, -1);
for (String report : reports) {
try {
Map<String, Object> reportMap = objectMapper.readValue(report, new TypeReference<Map<String, Object>>() {});
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<String> readVlogList = new ArrayList<>();
ObjectMapper objectMapper = new ObjectMapper();
List<String> reports = redis.lrange(redisKey, 0, -1);
for (String report : reports) {
try {
Map<String, Object> reportMap = objectMapper.readValue(report, new TypeReference<Map<String, Object>>() {});
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<Vlog> vlogPage = vlogMapper.selectPage(pageParam, queryWrapper);
List<Vlog> vlogList = vlogPage.getRecords();
List<IndexVlogVO> 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<IndexVlogVO> gridResult = new Page<IndexVlogVO>();
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<Vlog> 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<Vlog> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Vlog::getId, vlogId)
@ -332,17 +221,17 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService {
Page<Vlog> pageParam = new Page<>(current, size);
LambdaQueryWrapper<Vlog> 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<Vlog> vlogPage = vlogMapper.selectPage(pageParam, queryWrapper);
List<Vlog> 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<String, Object> 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<MyLikedVlog> 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<IndexVlogVO> getMyLikedVlogList( Page page) {
public Page<IndexVlogVO> getMyLikedVlogList(Page page) {
LoginUser user= LoginHelper.getLoginUser();
if(user==null){
LoginUser user = LoginHelper.getLoginUser();
if (user == null) {
throw new ServiceException("用户未登录");
}
Map<String, Object> map = new HashMap<>();
map.put("myId", user.getUserId());
Page<IndexVlogVO> likedPage = vlogMapperCustom.getMyLikedVlogList(map,page);
Page<IndexVlogVO> 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<IndexVlogVO> getMyFollowVlogList(Page page) {
LoginUser user= LoginHelper.getLoginUser();
if(user==null){
LoginUser user = LoginHelper.getLoginUser();
if (user == null) {
throw new ServiceException("用户未登录");
}
Map<String, Object> map = new HashMap<>();
map.put("myId", user.getUserId());
Page<IndexVlogVO> voPage = vlogMapperCustom.getMyFollowVlogList(map,page);
Page<IndexVlogVO> voPage = vlogMapperCustom.getMyFollowVlogList(map, page);
return voPage;
}
@Override
public Page<IndexVlogVO> getMyFriendVlogList( Page page) {
LoginUser user= LoginHelper.getLoginUser();
if(user==null){
public Page<IndexVlogVO> getMyFriendVlogList(Page page) {
LoginUser user = LoginHelper.getLoginUser();
if (user == null) {
throw new ServiceException("用户未登录");
}
Map<String, Object> map = new HashMap<>();
map.put("myId", user.getUserId());
Page<IndexVlogVO> voPage = vlogMapperCustom.getMyFriendVlogList(map,page);
Page<IndexVlogVO> 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<Map<String, Object>> likedUsers = myLikedVlogMapper.selectLikedUsersByVlogId(vlog.getId());
result.put("likedUsers", likedUsers);
result.put("vlog",vo);
result.put("vlog", vo);
// 添加粉丝列表
// Page<FansVO> 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<String, String> 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<Map<String, Object>> allVlogs = vlogMapper.selectAllPublicVlogs();
if (allVlogs != null && !allVlogs.isEmpty()) {
// 从Redis获取每个视频的点赞数量
List<Map<String, Object>> vlogsWithLikeCounts = new ArrayList<>();
for (Map<String, Object> 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<Map<String, Object>> 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("成功缓存{}条点赞最多的视频到Rediskey: {}", topLikedVlogs.size(), redisKey);
} else {
log.warn("未查询到点赞最多的视频数据");
}
} else {
log.warn("未查询到公开的视频数据");
}
} catch (Exception e) {
log.error("缓存点赞最多视频到Redis失败", e);
}
}
@Override
public List<Map<String, Object>> getRandomVlogs(int limit) {
@ -784,6 +594,49 @@ public class VlogServiceImpl extends BaseInfoProperties implements VlogService {
}
}
@Override
public IPage<String> getVlogForUser(Page<IndexVlogVO> 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<IndexVlogVO> getIndexVlogList(VlogBO bo) {
List<IndexVlogVO> indexVlogVOPage = vlogMapperCustom.getIndexVlogList(bo);
fillRedisColumn(indexVlogVOPage );
return indexVlogVOPage;
}
@Override
public Page<IndexVlogVO> getIndexVlogList(VlogBO bo, Page page) {
Page<IndexVlogVO> indexVlogVOPage = vlogMapperCustom.getIndexVlogList(bo,page);
fillRedisColumn(indexVlogVOPage.getRecords());
return indexVlogVOPage;
}
private void fillRedisColumn(List<IndexVlogVO> 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<IndexVlogVO> getIndexSearchVlogList(SearchBO bo, Page page) {
String userId = bo.getUserId();

View File

@ -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<com.wzj.soopin.content.domain.vo.CommentVO> commentList = new ArrayList<>();

View File

@ -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);

View File

@ -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<String> 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<String> mget(List<String> keys) {
return redisTemplate.opsForValue().multiGet(keys);
}
/**
* 批量查询管道pipeline
* @param keys
* @return
*/
public List<Object> batchGet(List<String> keys) {
// nginx -> keepalive
// redis -> pipeline
List<Object> result = redisTemplate.executePipelined(new RedisCallback<String>() {
@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<Object, Object> 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<String> 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<String> 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<String> mget(List<String> keys) {
// return redisTemplate.opsForValue().multiGet(keys);
// }
//
// /**
// * 批量查询管道pipeline
// * @param keys
// * @return
// */
// public List<Object> batchGet(List<String> keys) {
//
//// nginx -> keepalive
//// redis -> pipeline
//
// List<Object> result = redisTemplate.executePipelined(new RedisCallback<String>() {
// @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<Object, Object> 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<String> 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);
// }
}

View File

@ -282,5 +282,27 @@
</choose>
</select>
<select id="getVlogForUser" resultType="java.lang.String">
SELECT
v.id
FROM
cont_vlog v
WHERE
NOT EXISTS (
SELECT 1 FROM cms_vlog_member m
WHERE m.vlog_id = v.id AND m.member_id = ${memberId}
)
AND NOT EXISTS (
SELECT 1 FROM ums_block b
WHERE b.block_member_id = v.id AND b.member_id = ${memberId}
)
ORDER BY
v.create_time DESC
</select>
<update id="readVlog">
insert into cms_vlog_member (vlog_id, member_id,status) values (#{vlogId}, #{memberId},1)
</update>
</mapper>

View File

@ -32,32 +32,38 @@
WHERE
v.is_private = 0
<choose>
<when test="paramMap.status != null and paramMap.status!=''">
AND v.status = #{paramMap.status}
<when test="bo.status != null and bo.status!=''">
AND v.status = #{bo.status}
</when>
<otherwise>
AND v.status = 1
</otherwise>
</choose>
AND v.first_frame_img IS NOT NULL
<if test="paramMap.cityCode != null and paramMap.cityCode != ''">
AND v.city_code = #{paramMap.cityCode}
<if test="bo.cityCode != null and bo.cityCode != ''">
AND v.city_code = #{bo.cityCode}
</if>
<if test="paramMap.search != null and paramMap.search != ''">
AND v.title like '%${paramMap.search}%'
<if test="bo.title != null and bo.title != ''">
AND v.title like '%${bo.title}%'
</if>
<if test="paramMap.blockVd != null and paramMap.blockVd.size() > 0">
<if test="bo.blockVd != null and bo.blockVd.size() > 0">
AND v.id NOT IN
<foreach collection="paramMap.blockVd" item="vlogId" open="(" separator="," close=")">
<foreach collection="bo.blockVd" item="vlogId" open="(" separator="," close=")">
#{vlogId}
</foreach>
</if>
<if test="paramMap.blockUser != null and paramMap.blockUser.size() > 0">
<if test="bo.blockUser != null and bo.blockUser.size() > 0">
AND v.member_id NOT IN
<foreach collection="paramMap.blockUser" item="memberId" open="(" separator="," close=")">
<foreach collection="bo.blockUser" item="memberId" open="(" separator="," close=")">
#{memberId}
</foreach>
</if>
<if test="bo.ids != null and bo.ids.size() > 0">
AND v.id IN
<foreach collection="bo.ids" item="vlogId" open="(" separator="," close=")">
#{vlogId}
</foreach>
</if>
ORDER BY
v.create_time
DESC

View File

@ -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;

View File

@ -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;

View File

@ -73,6 +73,9 @@ public interface IMemberService extends IService<Member> {
String updateWechat(MemberBO bo);
/**
* 不带租户的获取会员信息
*

View File

@ -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<OrderItemMapper, OrderItem> implements OrderItemService {
@Autowired
private RedisOperator redis;
private RedisCache redisCache;
@Override
public List<OrderItem> findByOrderId(Long orderId) {
@ -54,7 +57,7 @@ public class OrderItemServiceImpl extends ServiceImpl<OrderItemMapper, OrderItem
// 存储到Redis设置24小时过期时间
String redisKey = "top_trading_products:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
redis.set(redisKey, jsonData, 24 * 60 * 60); // 24小时过期
redisCache.setCacheObject(redisKey, jsonData, 24 * 60 * 60, TimeUnit.SECONDS); // 24小时过期
log.info("成功缓存{}个交易量最多的商品到Rediskey: {}", topTradingProducts.size(), redisKey);
} else {

View File

@ -0,0 +1,47 @@
package com.wzj.soopin.transaction.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "easypay")
public class EasypayConfig {
/**
* 易生接口请求地址前缀
*/
private String apiPathPrefix;
/**
* 易生分配的机构号
*/
private String reqId;
/**
* 易生分配的商户号
*/
private String mchtCode;
/**
* 微信子商户号
*/
private String wechatMchid;
/**
* 易生公钥
*/
private String easypayPublicKey;
/**
* 商户私钥
*/
private String merRsaPrivateKey;
/**
* 易生异步回调通知地址
*/
private String backUrl;
}

View File

@ -0,0 +1,16 @@
package com.wzj.soopin.transaction.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "wechat.mini-program")
public class WechatMiniProgramConfig {
private String appId;
private String secret;
}

View File

@ -0,0 +1,67 @@
package com.wzj.soopin.transaction.constans;
/**
* 易生接口涉及常量
*/
public interface EasypayConstants {
/**
* 请求方类型.1商户2拓展机构 默认是1
* 1商户模式下reqId即为商户号mchtCode
* 2机构模式下reqId即为拓展机构编号
*/
public static final String REQ_TYPE = "2";
/**
* 生成订单前缀
*/
public static final String TRACE_PREFIX = "wzj";
/**
* 支付订单超时时长,单位:
*/
public static final Long TRACE_TIMEOUT = 180L;
/**
* 通讯返回码-成功
*/
public static final String RSP_HEADER_OK = "000000";
/**
* 业务返回码-成功
*/
public static final String RSP_BODY_RESP_OK = "000000";
/**
* 交易状态返回码-交易成功
*/
public static final String RSP_BODY_TRANS_OK = "0";
/**
* 交易状态返回码-交易部分成功
*/
public static final String RSP_BODY_TRANS_PARTIALLY_OK = "1";
/**
* 交易状态返回码-支付成功待发货
*/
public static final String RSP_BODY_TRANS_OK_WETCAT = "2";
/**
* 交易状态返回码-支付中,需查询或接收通知获取最终状态
*/
public static final String RSP_BODY_TRANS_PROCESSING = "9";
/**
* 交易状态返回码-交易失败
*/
public static final String RSP_BODY_TRANS_FAIL = "X";
/**
* 交易状态返回码-已退款
*/
public static final String RSP_BODY_REFUND_OK = "R";
}

View File

@ -0,0 +1,61 @@
package com.wzj.soopin.transaction.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import com.wzj.soopin.transaction.domain.bo.PaymentBO;
import com.wzj.soopin.transaction.domain.bo.easypay.EasyPayRequest;
import com.wzj.soopin.transaction.domain.vo.EasypayPrePayVO;
import com.wzj.soopin.transaction.service.IEasypayService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.rmi.ServerException;
import java.util.HashMap;
import java.util.Map;
/**
* 易生支付结果通知回调
*/
@RestController
@Slf4j
@RequestMapping("/easypay")
@RequiredArgsConstructor
public class EasypayCallbackController {
private final IEasypayService easypayService;
/**
* 处理易生支付结果通知回调
* @param request
* @return
*/
@SaIgnore
@Log(title = "易生支付结果通知回调", businessType = BusinessType.UPDATE)
@PostMapping("/trade/callback")
public Map tradeCallback(@RequestBody EasyPayRequest request) {
easypayService.handlerTradeCallback(request);
HashMap<Object, Object> 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<EasypayPrePayVO> testTrade(@RequestBody PaymentBO paymentBO) throws ServerException {
EasypayPrePayVO easypayPrePayVO = easypayService.payment(paymentBO);
return R.ok(easypayPrePayVO);
}
}

View File

@ -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<WxAuthResponse> getOpenIdByMiniProgramCode(@RequestParam String code) {
WxAuthResponse response = wxAuthService.getOpenIdByMiniProgramCode(code);
return R.ok(response);
}
}

View File

@ -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为国家代码境内业务统一为1564-7位为地区码采用银联卡跨行业务地区代码标准境外商户受理的二维码业务地区代码用全0 填充 点击
* [地区码](https://www.yuque.com/pandans/ws1g9s/loa46fu6isbvoksz#n3L1v)
* <p>
* 上海市上海市浦东新区 则传 1562904
*/
private String areaInfo;
/**
* 支付有效时间允许对一个订单进行支付的最长相对时间单位为秒
*/
private String paymentValidTime;
/**
* 二维码payType=UnionPayJsapiUnionPayJsMini时必填
* 二维码表示的数据内容为特定格式的URL或TLV其中境内二维码采用URL格式境外二维码采用TLV格式见附录F21.
*/
private String qrCode;
/**
* 二维码类型payType=UnionPayJsapiUnionPayJsMini时必填
*/
private String qrCodeType;
/**
* 支付方式payType=UnionPayJsMini时必填
*/
private String payChannel;
/**
* 微信分配的公众账号IDpayType=UnionPayJsMini时必填上送调起云闪付微信小程序的微信APPID
*/
private String appid;
/**
* 银联支付标识payType=UnionPayJsMini时必填固定上送MicroMessenger
*/
private String appUpIdentifier;
// ============银联业务参数 end ==============
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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> aliGoodsDetail;
/**
* 商户机具终端信息
*/
private Map<String, Map<String, Object>> 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<RoyaltyDetailInfo> 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;
}
}

View File

@ -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<MarketingOrder> marketingOrders;
/**
* toB营销信息1一笔交易请求中仅可上送一个toB营销活动
* 2如上送toB营销活动则不再命中后台配置的手续费营销活动
* 3活动业务说明见2.7
*/
private @Nullable List<Promo> promos;
/**
* 清算信息三个标识互斥,三个标识互斥最多仅能其中一个送是非0
* 合单支付时主单清算标识本字段不起作用以各子单的清算标识为准
*/
private SettleParamInfo settleParamInfo;
/**
* 分账信息
*/
private @Nullable SeparateInfo separateInfo;
/**
* 合单信息
*/
private @Nullable List<MergeOrder> mergeOrders;
/**
* 合规信息
*/
private RiskData riskData;
/**
* 其他信息
*/
private @Nullable OtherParamInfo otherParamInfo;
}

View File

@ -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;
}

View File

@ -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<SeparateInfo.SeparateOrderDetailList> separateOrderDetailList;
/**
* 商户终端号
*/
private String termCode;
/**
* 同批次分账金额合计单位分取值范围1-10000000000
*/
private long transSumAmt;
/**
* 同批次分账笔数取值范围1-10000000000
*/
private long transSumCount;
}

View File

@ -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<String, Object> marketingExtInfo;
/**
* 营销金额单位分
*/
private double marketingTransAmount;
/**
* 商户号与reqInfo中保持一致
*/
private String mchtCode;
/**
* 营销报名ID
*/
private Double registrationId;
/**
* 商户终端号非必填如上送需与reqInfo中保持一致
*/
private String termCode;
}

View File

@ -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;
}

View File

@ -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<Specialfee> otherFee;
/**
* 所属项目
*/
private String projectId;
/**
* 小商户号
*/
private String smallMchtCode;
}

View File

@ -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;
}

View File

@ -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;
/**
* 微信分配的公众账号IDpayType=UnionPayJsMini时必填上送调起云闪付微信小程序的微信APPID
*/
private String appid;
/**
* 银联支付标识payType=UnionPayJsMini时必填固定上送MicroMessenger
*/
private String appUpIdentifier;
/**
* 地区信息7位1-3为国家代码境内业务统一为1564-7位为地区码采用银联卡跨行业务地区代码标准境外商户受理的二维码业务地区代码用全0 填充 点击
* [地区码](https://www.yuque.com/pandans/ws1g9s/loa46fu6isbvoksz#n3L1v)
* <p>
* 上海市上海市浦东新区 则传 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格式参数值示例如下
* <p>
* {"pnrOrderId":"1220250321034502483875104", "pidSct" :"lj8352@da099%ldang", "tradeScene"
* :"1"}
*/
private String pidInfo;
/**
* 二维码payType=UnionPayJsapiUnionPayJsMini时必填
* 二维码表示的数据内容为特定格式的URL或TLV其中境内二维码采用URL格式境外二维码采用TLV格式见附录F21.
*/
private String qrCode;
/**
* 二维码类型payType=UnionPayJsapiUnionPayJsMini时必填
*/
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<GoodsInfo> 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 {
/**
* 附件信息
* <p>
* 如需京东白条支付可在此参数下上送白条相应参数详细参数见附录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;
}
}

View File

@ -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;
/**
* ICCIDSIM卡卡号
*/
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;
}
}

View File

@ -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> 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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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.商户侧一张小票订单可能被分多次支付订单原价用于记录整张小票的交易金额<br />2.当订单原价与支付金额不相等则不享受优惠<br
* />3.该字段主要用于防止同一张小票分多次支付以享受多次优惠的情况正常支付订单不必上传此参数
*/
private Long costPrice;
/**
* 单品列表单品信息使用Json数组格式提交
*/
private List<GoodsDetail> 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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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> goodsDetail;
/**
* 商户的操作员编号
*/
private String operatorId;
/**
* 查询选项
*/
private List<String> 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;
}
}

View File

@ -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;
}

View File

@ -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;
/**
* 原订单outTraceoriOrgTrace与oriOutTrace二选一必填
*/
private String oriOutTrace;
/**
* 原业务日期原交易业务日期格式yyyyMMdd
*/
private String oriTransDate;
/**
* 退款金额单位分 separate
*/
private long refundAmount;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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> promoList;
/**
* 分账总单流水号分账批次号自定义要唯一
*/
private String separateBatchTrace;
/**
* 分账子单明细可送多条
*/
private List<SeparateInfo.SeparateOrderDetailList> separateOrderDetailList;
/**
* 分账订单总金额单位分
*/
private long transSumAmt;
/**
* 分账订单总笔数
*/
private long transSumCount;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class PromoList {
/**
* 营销ID
*/
private String promoCode;
/**
* 营销手续费金额
*/
private long promoFeeAmount;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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> separateRespInfoList;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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> 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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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> 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;
}
}

View File

@ -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;
}

View File

@ -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<MarketingParamInfo> marketingRespParamInfoList;
/**
* toB营销信息
*/
private List<Promo> promos;
/**
* 银联业务参数
*/
private QrRespParamInfoVO qrRespParamInfo;
/**
* 基础订单信息
*/
private TradeQueryRespOrderInfo respOrderInfo;
/**
* 返回码信息规则详见F.1
*/
private RespStateInfo respStateInfo;
/**
* 风险控制信息
*/
private RiskInfo riskParamInfo;
/**
* 清算信息
*/
private SettleRespParamInfo settleRespParamInfo;
/**
* wx业务参数
*/
private WxQueryRespParamInfoVO wxRespParamInfo;
}

View File

@ -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;
/**
* 原订单outTraceoriOrgTrace与oriOutTrace二选一必填
*/
private String oriOutTrace;
/**
* 原商户交易日期yyyyMMdd
*/
private String oriTransDate;
}
}

View File

@ -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<FundBillVO> 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<AliDiscountsDetailVO> 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<OtherContribute> 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;
}
}

View File

@ -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<String, Map<String, Object>> respMarketingExtInfo;
/**
* 营销金额
*/
private Double transAmount;
private String upOrderNo;
}

View File

@ -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;
}

View File

@ -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<QrDiscountsDetailVO> 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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<MarketingParamInfo> marketingRespParamInfoList;
/**
* 子单返回信息
*/
private MergeOrderSubRespList mergeOrderSubRespList;
/**
* toB营销信息
*/
private List<Promo> promos;
/**
* 银联业务参数
*/
private QrRespParamInfoVO qrRespParamInfo;
/**
* 基础订单信息
*/
private TradeQueryRespOrderInfo respOrderInfo;
/**
* 返回码信息规则详见F.1
*/
private RespStateInfo respStateInfo;
/**
* 风险控制信息
*/
private RiskInfo riskParamInfo;
/**
* 清算信息
*/
private SettleRespParamInfo settleRespParamInfo;
/**
* wx业务参数
*/
private WxQueryRespParamInfoVO wxRespParamInfo;
}

View File

@ -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;
}

Some files were not shown because too many files have changed in this diff Show More