Explorar o código

Merge branch 'package' of GameCenter/game-center into dev

zhimo hai 1 ano
pai
achega
395664d6f1
Modificáronse 21 ficheiros con 524 adicións e 52 borrados
  1. 11 0
      game-data/game-data-serve/src/main/java/com/zanxiang/game/data/serve/controller/RoleManageController.java
  2. 57 0
      game-data/game-data-serve/src/main/java/com/zanxiang/game/data/serve/pojo/dto/SendMsgTaskDto.java
  3. 73 0
      game-data/game-data-serve/src/main/java/com/zanxiang/game/data/serve/pojo/enums/TaskStrategyEnum.java
  4. 74 0
      game-data/game-data-serve/src/main/java/com/zanxiang/game/data/serve/pojo/vo/SendMsgTaskVo.java
  5. 6 0
      game-data/game-data-serve/src/main/java/com/zanxiang/game/data/serve/service/IRoleManageService.java
  6. 86 12
      game-data/game-data-serve/src/main/java/com/zanxiang/game/data/serve/service/impl/GameDataServiceImpl.java
  7. 11 0
      game-data/game-data-serve/src/main/java/com/zanxiang/game/data/serve/service/impl/RoleManageServiceImpl.java
  8. 7 0
      game-module/game-module-manage/src/main/java/com/zanxiang/game/module/manage/service/IGameAuthRoleService.java
  9. 12 0
      game-module/game-module-manage/src/main/java/com/zanxiang/game/module/manage/service/IKfSystemReplyService.java
  10. 8 0
      game-module/game-module-manage/src/main/java/com/zanxiang/game/module/manage/service/impl/GameAuthRoleServiceImpl.java
  11. 30 1
      game-module/game-module-manage/src/main/java/com/zanxiang/game/module/manage/service/impl/KfAppletMsgServiceImpl.java
  12. 18 0
      game-module/game-module-manage/src/main/java/com/zanxiang/game/module/manage/service/impl/KfSystemReplyServiceImpl.java
  13. 17 10
      game-module/game-module-manage/src/main/java/com/zanxiang/game/module/manage/websocket/KfMsgWebsocketHandler.java
  14. 58 0
      game-module/game-module-mybatis/src/main/java/com/zanxiang/game/module/mybatis/entity/KfSystemReply.java
  15. 12 0
      game-module/game-module-mybatis/src/main/java/com/zanxiang/game/module/mybatis/mapper/KfSystemReplyMapper.java
  16. 1 1
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/SDKApplication.java
  17. 7 2
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/constant/RedisKeyConstant.java
  18. 9 11
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/GameUserRoleServiceImpl.java
  19. 3 5
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/LoginServiceImpl.java
  20. 1 1
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/OrderPayServiceImpl.java
  21. 23 9
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/UserTokenServiceImpl.java

+ 11 - 0
game-data/game-data-serve/src/main/java/com/zanxiang/game/data/serve/controller/RoleManageController.java

@@ -3,6 +3,7 @@ package com.zanxiang.game.data.serve.controller;
 import com.zanxiang.erp.security.annotation.PreAuthorize;
 import com.zanxiang.game.data.serve.pojo.dto.RoleCombatRankingDTO;
 import com.zanxiang.game.data.serve.pojo.dto.RoleRechargeRankingDTO;
+import com.zanxiang.game.data.serve.pojo.dto.SendMsgTaskDto;
 import com.zanxiang.game.data.serve.service.IRoleManageService;
 import com.zanxiang.game.data.serve.utils.Page;
 import com.zanxiang.module.util.pojo.ResultVO;
@@ -10,6 +11,7 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -45,5 +47,14 @@ public class RoleManageController {
         return ResultVO.ok(roleManageService.getRoleCombatRanking(dto));
     }
 
+    @ApiOperation(value = "创建发送消息任务")
+    @PreAuthorize(permissionKey = "roleManage:sendMsgTask:create")
+    @PostMapping("/sendMsgTask")
+    public ResultVO rcreateSendMsgTask(@RequestBody SendMsgTaskDto dto) {
+        roleManageService.createSendMsgTask(dto);
+        return ResultVO.ok();
+    }
+
+
 
 }

+ 57 - 0
game-data/game-data-serve/src/main/java/com/zanxiang/game/data/serve/pojo/dto/SendMsgTaskDto.java

@@ -0,0 +1,57 @@
+package com.zanxiang.game.data.serve.pojo.dto;
+
+
+import com.zanxiang.game.data.serve.pojo.enums.TaskStrategyEnum;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDate;
+
+/**
+ * @author ZhangXianyu
+ * @time 2024-03-11 11:24:16
+ * @Description 发送消息任务参数
+ **/
+@Data
+public class SendMsgTaskDto implements Serializable {
+
+    /**
+     * 任务名称
+     */
+    private String taskName;
+
+    /**
+     * 任务调度策略
+     * {@link TaskStrategyEnum}
+     */
+    private Integer taskStrategy;
+
+    /**
+     * 定时发送时间
+     */
+    private LocalDate sendOnTime;
+
+    /**
+     * 定时发送开始时间
+     */
+    private LocalDate everyDayBeginTime;
+
+    /**
+     * 定时发送结束时间(默认2099表示永久)
+     */
+    private LocalDate everyDayEndTime;
+
+    /**
+     * 发送文本内容
+     */
+    private String sendContent;
+    /**
+     * 发送对象条件(筛选条件放入JSON)
+     * {@link RoleRechargeRankingDTO}
+     */
+    private String sendConditionJson;
+
+
+
+
+}

+ 73 - 0
game-data/game-data-serve/src/main/java/com/zanxiang/game/data/serve/pojo/enums/TaskStrategyEnum.java

@@ -0,0 +1,73 @@
+package com.zanxiang.game.data.serve.pojo.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Objects;
+
+/**
+ * @author : ZhangXianyu
+ * @time : 2024-03-11 11:36:57
+ * @description : 任务策略类型枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum TaskStrategyEnum {
+
+    /**
+     * 立即发送
+     */
+    SEND_NOW(1, "立即发送"),
+
+    /**
+     * 定时发送
+     */
+    SEND_ON_TIME(2, "定时发送"),
+
+    /**
+     * 每日循环发送
+     */
+    SEND_ON_EVERYDAY(3, "每日循环发送"),
+
+    /**
+     * 触发条件发送
+     */
+    APPLET(4, "触发条件发送");
+
+    /**
+     * 标识ID
+     */
+    private Integer id;
+
+    /**
+     * 类型名
+     */
+    private String name;
+
+    /**
+     * 获取任务类型名字
+     *
+     * @param deviceTypeId id
+     * @return {@link String}
+     */
+    public static String getName(Integer deviceTypeId) {
+        TaskStrategyEnum deviceType = TaskStrategyEnum.getDeviceType(deviceTypeId);
+        return deviceType == null ? null : deviceType.getName();
+    }
+
+    /**
+     * 获取客户端类型枚举
+     *
+     * @param deviceTypeId id
+     * @return DeviceTypeEnum
+     */
+    public static TaskStrategyEnum getDeviceType(Integer deviceTypeId) {
+        for (TaskStrategyEnum deviceTypeEnum : TaskStrategyEnum.values()) {
+            if (Objects.equals(deviceTypeEnum.getId(), deviceTypeId)) {
+                return deviceTypeEnum;
+            }
+        }
+        return null;
+    }
+
+}

+ 74 - 0
game-data/game-data-serve/src/main/java/com/zanxiang/game/data/serve/pojo/vo/SendMsgTaskVo.java

@@ -0,0 +1,74 @@
+package com.zanxiang.game.data.serve.pojo.vo;
+
+import lombok.Data;
+
+import java.time.LocalDate;
+
+/**
+ * @author ZhangXianyu
+ * @time 2024-03-11 11:24:16
+ * @Description 发送消息任务参数
+ **/
+@Data
+public class SendMsgTaskVo {
+
+    private Long taskId;
+
+    private String taskName;
+
+    private String taskCondition;
+
+    private String gameName;
+
+    private LocalDate createTime;
+
+    private String createBy;
+
+    private LocalDate updateTime;
+
+    private String updateBy;
+
+    private String sendContent;
+
+    /**
+     * 任务调度策略类型
+     * {@link com.zanxiang.game.data.serve.pojo.enums.TaskStrategyEnum}
+     */
+    private String taskStrategy;
+
+    /**
+     * 定时发送时间
+     */
+    private LocalDate sendOnTime;
+
+    /**
+     * 发送角色总数
+     */
+    private Integer sendCount;
+
+    /**
+     * 发送角色成功总数
+     */
+    private Integer sendSuccessCount;
+
+    /**
+     * 发送角色失败总数
+     */
+    private Integer sendFailCount;
+
+    /**
+     * 发送状态(任务实例状态 是否执行过任务)
+     */
+    private Integer sendStatus;
+
+    /**
+     * 任务状态(执行  停止)
+     */
+    private Integer taskStatus;
+
+
+
+
+
+
+}

+ 6 - 0
game-data/game-data-serve/src/main/java/com/zanxiang/game/data/serve/service/IRoleManageService.java

@@ -2,6 +2,7 @@ package com.zanxiang.game.data.serve.service;
 
 import com.zanxiang.game.data.serve.pojo.dto.RoleCombatRankingDTO;
 import com.zanxiang.game.data.serve.pojo.dto.RoleRechargeRankingDTO;
+import com.zanxiang.game.data.serve.pojo.dto.SendMsgTaskDto;
 import com.zanxiang.game.data.serve.utils.Page;
 
 import java.util.Map;
@@ -12,4 +13,9 @@ public interface IRoleManageService {
 
     Page<Map> getRoleCombatRanking(RoleCombatRankingDTO dto);
 
+    /**
+     * 创建发送消息任务
+     * @param dto
+     */
+    void createSendMsgTask(SendMsgTaskDto dto);
 }

+ 86 - 12
game-data/game-data-serve/src/main/java/com/zanxiang/game/data/serve/service/impl/GameDataServiceImpl.java

@@ -5229,18 +5229,38 @@ public class GameDataServiceImpl implements IGameDataService {
     @Override
     public Page<FlowMonitorVO> getFlowMonitor(FlowMonitorDTO dto) {
         LocalDate today = LocalDate.now();
-        //如果消耗时间和订单时间都为空,那么就给默认值
+        //如果消耗时间为空,那么就给默认值
         if(dto.getCostBeginDate()==null && dto.getCostEndDate()==null){
             LocalDate oneMonthAgo = today.minusMonths(1);
             //消耗时间如果为空,那么就给默认值当天前一个月
             dto.setCostBeginDate(oneMonthAgo);
             dto.setCostEndDate(today);
         }
+
+        //校验订单时间,必须包含在消耗时间内
+        if(dto.getOrderBeginDate()!=null && dto.getOrderEndDate()!=null){
+            //如果订单选择范围开始时间在消耗之前,那么就将订单时间设置为消耗开始时间
+            if(dto.getOrderBeginDate().isBefore(dto.getCostBeginDate())){
+                dto.setOrderBeginDate(dto.getCostBeginDate());
+            }
+            //如果订单选择范围结束时间在消耗之后,那么就将订单时间设置为消耗结束时间
+            if(dto.getOrderEndDate().isAfter(dto.getCostEndDate())){
+                dto.setOrderEndDate(dto.getCostEndDate());
+            }
+        }
+
+        //如果订单时间为空,那么就最多查询消耗结束时间当天和前一天的数据
         if(dto.getOrderBeginDate()==null && dto.getOrderEndDate()==null){
-            LocalDate twoDaysAgo = today.minusDays(1);
-            //订单时间如果为空,那么就给默认值当天和前一天
-            dto.setOrderBeginDate(twoDaysAgo);
-            dto.setOrderEndDate(today);
+            //订单结束时间默认为消耗结束时间
+            dto.setOrderEndDate(dto.getCostEndDate());
+            //获取前一天
+            LocalDate twoDaysAgo = dto.getCostEndDate().minusDays(1);
+            //如果前一天在消耗开始之后或等于,那么就将订单开始时间设置为前一天
+            if(twoDaysAgo.isEqual(dto.getCostBeginDate())||twoDaysAgo.isAfter(dto.getCostBeginDate())){
+                dto.setOrderBeginDate(twoDaysAgo);
+            }else {
+                dto.setOrderBeginDate(dto.getCostEndDate());
+            }
         }
         //分页条件
         Pager pager = dao.createPager(dto.getPageNum(), dto.getPageSize());
@@ -5248,14 +5268,17 @@ public class GameDataServiceImpl implements IGameDataService {
         //订单表查询条件
         Criteria cri = getOrderQueryStr(dto);
 
+        //渠道单独的查询条件
+        Criteria agentCri = getAgentQueryStr(dto);
+
         //查询总记录数
-        Sql countSql = Sqls.create(getCountNumSql(cri));
+        Sql countSql = Sqls.create(getCountNumSql(cri,agentCri));
         countSql.setCallback(Sqls.callback.integer());
         dao.execute(countSql);
         pager.setRecordCount(countSql.getInt());
 
         //查询sql
-        Sql sql = Sqls.create(flowMonitorSql(cri));
+        Sql sql = Sqls.create(flowMonitorSql(cri,agentCri));
         //设置自定义回传类型
         sql.setCallback(Sqls.callback.entities());
         sql.setEntity(dao.getEntity(FlowMonitorVO.class));
@@ -5325,7 +5348,7 @@ public class GameDataServiceImpl implements IGameDataService {
         return sql;
     }
 
-    private String getCountNumSql(Criteria cri){
+    private String getCountNumSql(Criteria cri,Criteria agentCri){
         String sql = """
                 
                 SELECT COUNT(*) FROM (
@@ -5539,7 +5562,7 @@ public class GameDataServiceImpl implements IGameDataService {
                                 ) f
                                                group by day, source_system
                                      ) ab  
-                                     """+ cri +""" 
+                                     """+ agentCri +""" 
                                      order by day desc
                 
                 
@@ -5578,7 +5601,7 @@ public class GameDataServiceImpl implements IGameDataService {
             BigDecimal costBigDecimal = new BigDecimal(costCount);
             BigDecimal amountBigDecimal = new BigDecimal(amountCount);
             BigDecimal bd = amountBigDecimal.divide(costBigDecimal, 2, RoundingMode.HALF_UP);
-            vo.setRecoveryCount(bd.doubleValue());
+            vo.setRecoveryCount(bd.doubleValue()*100);
         }
         return vo;
     }
@@ -5623,7 +5646,7 @@ public class GameDataServiceImpl implements IGameDataService {
      * @param cri 条件
      * @return sql
      */
-    public String flowMonitorSql(Criteria cri){
+    public String flowMonitorSql(Criteria cri,Criteria agentCri){
 
         String sql  = """
                 select day as order_date,
@@ -5837,7 +5860,7 @@ public class GameDataServiceImpl implements IGameDataService {
                 ) f
                                group by day, source_system
                      ) ab  
-                     """+ cri +"""
+                     """+ agentCri +"""
                      order by day desc
                     
                     """;
@@ -5846,6 +5869,52 @@ public class GameDataServiceImpl implements IGameDataService {
     }
 
 
+    /**
+     * 拼接查询条件
+     *
+     * @param dto
+     * @return
+     */
+    public Criteria getAgentQueryStr(FlowMonitorDTO dto) {
+        Criteria cri = Cnd.cri();
+
+        //SDK类型
+        if (StringUtils.isNotBlank(dto.getSourceSystem())) {
+            cri.where().andEquals("source_system", dto.getSourceSystem());
+        }
+
+        String gameColumn = "game_id";
+        if (dto.getGameDimension() != 1) {
+            gameColumn = "parent_game_id";
+        }
+
+        //拼接游戏ID
+        if (CollectionUtils.isNotEmpty(dto.getGameId())) {
+            cri.where().andInList(gameColumn, dto.getGameId());
+        }
+
+        //拼接查询时间
+        if (dto.getOrderBeginDate() != null && dto.getOrderEndDate() != null) {
+            cri.where().andBetween("day", dto.getOrderBeginDate(), dto.getOrderEndDate());
+        }
+
+
+        //拼接投手ID
+        if (CollectionUtils.isNotEmpty(dto.getPitcherId())) {
+            cri.where().andInList("pitcher_id", dto.getPitcherId());
+        }
+        //拼接渠道ID
+        if (CollectionUtils.isNotEmpty(dto.getAgentId())) {
+            cri.where().andInList("agent_id", dto.getAgentId());
+        }
+        //拼接广告账号id
+        if (CollectionUtils.isNotEmpty(dto.getAccountId())) {
+            cri.where().andInList("account_id", dto.getAccountId());
+        }
+
+        return cri;
+    }
+
     /**
      * 拼接查询条件
      *
@@ -5875,6 +5944,10 @@ public class GameDataServiceImpl implements IGameDataService {
             cri.where().andBetween("day", dto.getOrderBeginDate(), dto.getOrderEndDate());
         }
 
+        if(dto.getCostBeginDate()!=null && dto.getCostEndDate()!=null){
+            cri.where().andBetween("to_date(reg_time)", dto.getCostBeginDate(), dto.getCostEndDate());
+        }
+
         //拼接投手ID
         if (CollectionUtils.isNotEmpty(dto.getPitcherId())) {
             cri.where().andInList("pitcher_id", dto.getPitcherId());
@@ -5891,6 +5964,7 @@ public class GameDataServiceImpl implements IGameDataService {
         return cri;
     }
 
+
     /**
      * 查询出每个游戏的注册查询时间内,注册当天且充值的所有用户id
      *

+ 11 - 0
game-data/game-data-serve/src/main/java/com/zanxiang/game/data/serve/service/impl/RoleManageServiceImpl.java

@@ -4,6 +4,7 @@ import com.zanxiang.erp.base.ErpServer;
 import com.zanxiang.erp.base.rpc.ISysUserRpc;
 import com.zanxiang.game.data.serve.pojo.dto.RoleCombatRankingDTO;
 import com.zanxiang.game.data.serve.pojo.dto.RoleRechargeRankingDTO;
+import com.zanxiang.game.data.serve.pojo.dto.SendMsgTaskDto;
 import com.zanxiang.game.data.serve.pojo.enums.OrderByEnum;
 import com.zanxiang.game.data.serve.service.IRoleManageService;
 import com.zanxiang.game.data.serve.utils.Page;
@@ -316,6 +317,16 @@ public class RoleManageServiceImpl implements IRoleManageService {
         }
     }
 
+    /**
+     * 创建发送消息任务
+     * @param dto
+     */
+    @Override
+    public void createSendMsgTask(SendMsgTaskDto dto) {
+
+
+    }
+
     /**
      * 通过id获取 GS、运营、客服人员、投手名字
      * @param dataMap dataMap

+ 7 - 0
game-module/game-module-manage/src/main/java/com/zanxiang/game/module/manage/service/IGameAuthRoleService.java

@@ -17,6 +17,13 @@ import java.util.Map;
  */
 public interface IGameAuthRoleService extends IService<GameAuthRole> {
 
+    /**
+     * 是否是客服
+     *
+     * @return boolean
+     */
+    boolean isCustomer();
+
     /**
      * 获取用户身份验证类型
      *

+ 12 - 0
game-module/game-module-manage/src/main/java/com/zanxiang/game/module/manage/service/IKfSystemReplyService.java

@@ -0,0 +1,12 @@
+package com.zanxiang.game.module.manage.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zanxiang.game.module.mybatis.entity.KfSystemReply;
+
+/**
+ * @author : lingfeng
+ * @time : 2024-03-11
+ * @description : 客服自动回复
+ */
+public interface IKfSystemReplyService extends IService<KfSystemReply> {
+}

+ 8 - 0
game-module/game-module-manage/src/main/java/com/zanxiang/game/module/manage/service/impl/GameAuthRoleServiceImpl.java

@@ -47,6 +47,14 @@ public class GameAuthRoleServiceImpl extends ServiceImpl<GameAuthRoleMapper, Gam
     @Autowired
     private IGameAuthService gameAuthService;
 
+    @Override
+    public boolean isCustomer() {
+        return super.count(new LambdaQueryWrapper<GameAuthRole>()
+                .eq(GameAuthRole::getAuthType, GameAuthEnum.CUSTOMER.getValue())
+                .eq(GameAuthRole::getUserId, SecurityUtil.getUserId())
+        ) > 0;
+    }
+
     @Override
     public Map<Long, String> getUserByAuthType(String authType) {
         List<GameAuthRole> authRoleList = super.list(new LambdaQueryWrapper<GameAuthRole>()

+ 30 - 1
game-module/game-module-manage/src/main/java/com/zanxiang/game/module/manage/service/impl/KfAppletMsgServiceImpl.java

@@ -87,12 +87,14 @@ public class KfAppletMsgServiceImpl implements IKfAppletMsgService {
     @Autowired
     private IKfRoomMsgService kfRoomMsgService;
 
+    @Autowired
+    private IKfSystemReplyService kfSystemReplyService;
+
     @Autowired
     private RedisUtil<String> redisUtil;
 
     @Override
     public void appletMsg(String postData) {
-        log.error("接收到SDK转发的小程序消息, postData : {}", postData);
         KfAppletMsgDTO kfAppletMsgDTO = JsonUtil.toObj(postData, KfAppletMsgDTO.class);
         GameApplet gameApplet = gameAppletService.getOne(new LambdaQueryWrapper<GameApplet>()
                 .eq(GameApplet::getGhId, kfAppletMsgDTO.getToUserName()));
@@ -123,6 +125,8 @@ public class KfAppletMsgServiceImpl implements IKfAppletMsgService {
         if (Strings.isNotBlank(orderId) && this.orderHandle(orderId, gameApplet.getGameId(), kfAppletMsgDTO, kfRoom, kfRoomMsg)) {
             return;
         }
+        //客服休息时间, 发送自动回复
+        this.systemReplyHandle(gameApplet.getGameId(), kfAppletMsgDTO.getFromUserName(), kfRoom);
         //保存房间消息
         kfRoomMsgService.save(kfRoomMsg);
         //玩家状态更新为待接入状态
@@ -137,6 +141,31 @@ public class KfAppletMsgServiceImpl implements IKfAppletMsgService {
         this.pushMessage(this.transform(kfRoom, gameApplet.getGameId(), kfRoomMsg, msgContent));
     }
 
+    private void systemReplyHandle(Long gameId, String openId, KfRoom kfRoom) {
+        //获取自动回复配置
+        KfSystemReply kfSystemReply = kfSystemReplyService.getById(gameId);
+        if (kfSystemReply == null) {
+            return;
+        }
+        //判断当前时间是否在时间内
+        LocalTime nowTime = LocalTime.now();
+        LocalTime startTime = LocalTime.parse(kfSystemReply.getStartTime());
+        LocalTime endTime = LocalTime.parse(kfSystemReply.getEndTime());
+        if (nowTime.isAfter(startTime) && nowTime.isBefore(endTime)) {
+            return;
+        }
+        //休息时间, 发送指定消息
+        Map<String, Object> textMap = new HashMap<>(1);
+        textMap.put("content", kfSystemReply.getSysReply());
+        Map<String, Object> msgParamMap = new HashMap<>(3);
+        msgParamMap.put("touser", openId);
+        msgParamMap.put("msgtype", KfRoomMsgTypeEnum.KF_MSG_TYPE_TEXT.getValue());
+        msgParamMap.put("text", textMap);
+        kfWxApiService.sendCustomMessageApi(gameId, msgParamMap);
+        //保存消息
+        kfRoomMsgService.save(this.transform(openId, gameId, kfRoom, JsonUtil.toString(msgParamMap)));
+    }
+
     private boolean orderHandle(String orderId, Long gameId, KfAppletMsgDTO kfAppletMsgDTO, KfRoom kfRoom, KfRoomMsg kfRoomMsg) {
         //判断收到的消息是否为充值消息或者小程序消息
         if (!KfAppletMsgDTO.MSG_CONTENT_PAY.contains(kfAppletMsgDTO.getContent())

+ 18 - 0
game-module/game-module-manage/src/main/java/com/zanxiang/game/module/manage/service/impl/KfSystemReplyServiceImpl.java

@@ -0,0 +1,18 @@
+package com.zanxiang.game.module.manage.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zanxiang.game.module.manage.service.IKfSystemReplyService;
+import com.zanxiang.game.module.mybatis.entity.KfSystemReply;
+import com.zanxiang.game.module.mybatis.mapper.KfSystemReplyMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author : lingfeng
+ * @time : 2024-03-11
+ * @description : 客服自动回复
+ */
+@Slf4j
+@Service
+public class KfSystemReplyServiceImpl extends ServiceImpl<KfSystemReplyMapper, KfSystemReply> implements IKfSystemReplyService {
+}

+ 17 - 10
game-module/game-module-manage/src/main/java/com/zanxiang/game/module/manage/websocket/KfMsgWebsocketHandler.java

@@ -10,10 +10,7 @@ import com.zanxiang.game.module.manage.enums.KfRoomMsgTypeEnum;
 import com.zanxiang.game.module.manage.enums.KfWebSocketMsgEnum;
 import com.zanxiang.game.module.manage.pojo.dto.KfWebSocketMsgDTO;
 import com.zanxiang.game.module.manage.pojo.params.KfWebSocketMsgParam;
-import com.zanxiang.game.module.manage.service.IKfQuickReplyService;
-import com.zanxiang.game.module.manage.service.IKfRoomMsgService;
-import com.zanxiang.game.module.manage.service.IKfRoomService;
-import com.zanxiang.game.module.manage.service.IKfSessionUserService;
+import com.zanxiang.game.module.manage.service.*;
 import com.zanxiang.game.module.manage.service.api.KfWxApiService;
 import com.zanxiang.game.module.manage.utils.FileUtil;
 import com.zanxiang.game.module.mybatis.entity.KfRoom;
@@ -66,6 +63,9 @@ public class KfMsgWebsocketHandler implements WebSocketHandler {
     @Autowired
     private KfWxApiService wxApiService;
 
+    @Autowired
+    private IGameAuthRoleService gameAuthRoleService;
+
     @Autowired
     private IKfQuickReplyService kfQuickReplyService;
 
@@ -327,6 +327,8 @@ public class KfMsgWebsocketHandler implements WebSocketHandler {
                 .gameId(gameId)
                 .roomList(onlineRoomList)
                 .build());
+        //释放锁
+        distributedLockComponent.unlock(RedisKeyConstant.KF_MSG_USER_CONNECT_JOIN + kfRoom.getOpenId());
     }
 
     private void finishRoomList(WebSocketSession session, KfWebSocketMsgParam param) {
@@ -368,15 +370,16 @@ public class KfMsgWebsocketHandler implements WebSocketHandler {
     }
 
     private void userConnectJoin(WebSocketSession session, KfWebSocketMsgParam param) {
+        //参数判断
         if (Strings.isBlank(param.getOpenId())) {
             this.sendMessage(session, KfWebSocketMsgDTO.fail(param.getWebSocketMsgType(),
                     "接入玩家参数错误, openId不可为空, param : " + JsonUtil.toString(param)));
             return;
         }
-        //触发玩家接入线程锁
-        if (!distributedLockComponent.doLock(RedisKeyConstant.KF_MSG_USER_CONNECT_JOIN + param.getOpenId(),
-                0L, 5L, TimeUnit.MINUTES)) {
-            this.sendMessage(session, KfWebSocketMsgDTO.fail(param.getWebSocketMsgType(), "玩家已被其他客服接入"));
+        //判断是否为客服
+        if (!gameAuthRoleService.isCustomer()) {
+            this.sendMessage(session, KfWebSocketMsgDTO.fail(param.getWebSocketMsgType(),
+                    "非客服人员, 不可接待玩家, param : " + JsonUtil.toString(param)));
             return;
         }
         //玩家信息, 判断玩家是否已经被接入
@@ -385,6 +388,12 @@ public class KfMsgWebsocketHandler implements WebSocketHandler {
             this.sendMessage(session, KfWebSocketMsgDTO.fail(param.getWebSocketMsgType(), "玩家已被其他客服接入"));
             return;
         }
+        //触发玩家接入线程锁
+        if (!distributedLockComponent.doLock(RedisKeyConstant.KF_MSG_USER_CONNECT_JOIN + param.getOpenId(),
+                0L, 5L, TimeUnit.MINUTES)) {
+            this.sendMessage(session, KfWebSocketMsgDTO.fail(param.getWebSocketMsgType(), "玩家已被其他客服接入"));
+            return;
+        }
         //玩家更新为已接入
         kfSessionUserService.update(new LambdaUpdateWrapper<KfSessionUser>()
                 .set(KfSessionUser::getIsWait, Boolean.FALSE)
@@ -417,8 +426,6 @@ public class KfMsgWebsocketHandler implements WebSocketHandler {
                 .gameId(param.getGameId())
                 .waitUserList(waitUserList)
                 .build());
-        //释放锁
-        distributedLockComponent.unlock(RedisKeyConstant.KF_MSG_USER_CONNECT_JOIN + param.getOpenId());
     }
 
     private void kfCreateConnect(WebSocketSession session, KfWebSocketMsgEnum msgTypeEnum, Long gameId) {

+ 58 - 0
game-module/game-module-mybatis/src/main/java/com/zanxiang/game/module/mybatis/entity/KfSystemReply.java

@@ -0,0 +1,58 @@
+package com.zanxiang.game.module.mybatis.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author : lingfeng
+ * @time : 2024-03-11
+ * @description : 客服系统回复
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@ToString
+@Builder
+@TableName("t_kf_system_reply")
+public class KfSystemReply {
+
+    /**
+     * 游戏
+     */
+    @TableId(value = "game_id", type = IdType.INPUT)
+    private Long gameId;
+
+    /**
+     * APPID
+     */
+    private String appId;
+
+    /**
+     * 回复
+     */
+    private String sysReply;
+
+    /**
+     * 开始时间
+     */
+    private String startTime;
+
+    /**
+     * 结束时间
+     */
+    private String endTime;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+}

+ 12 - 0
game-module/game-module-mybatis/src/main/java/com/zanxiang/game/module/mybatis/mapper/KfSystemReplyMapper.java

@@ -0,0 +1,12 @@
+package com.zanxiang.game.module.mybatis.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zanxiang.game.module.mybatis.entity.KfSystemReply;
+
+/**
+ * @author : lingfeng
+ * @time : 2024-03-11
+ * @description : ${description}
+ */
+public interface KfSystemReplyMapper extends BaseMapper<KfSystemReply> {
+}

+ 1 - 1
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/SDKApplication.java

@@ -23,7 +23,7 @@ public class SDKApplication {
 
     public static void main(String[] args) {
         SpringApplication.run(SDKApplication.class, args);
-        System.out.println("赞象SDK服务启动成功 <解决线上BUg01> ( ´・・)ノ(._.`) \n" +
+        System.out.println("赞象SDK服务启动成功 <解决SDK线上并发问题> ( ´・・)ノ(._.`) \n" +
                 " ___________ _   __\n" +
                 "/  ___|  _  \\ | / /\n" +
                 "\\ `--.| | | | |/ / \n" +

+ 7 - 2
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/constant/RedisKeyConstant.java

@@ -30,7 +30,7 @@ public class RedisKeyConstant {
     /**
      * 角色信息更新
      */
-    public static final String ROLE_UPDATE_KEY = RedisKeyConstant.REDIS_PREFIX + "role_update";
+    public static final String ROLE_CREATE_LOCK = RedisKeyConstant.REDIS_PREFIX + "role_create_lock_";
 
     /**
      * 角色信息更新
@@ -55,7 +55,7 @@ public class RedisKeyConstant {
     /**
      * 用户注册
      */
-    public static final String USER_CREATE = RedisKeyConstant.REDIS_PREFIX + "user_create";
+    public static final String USER_CREATE = RedisKeyConstant.REDIS_PREFIX + "user_create_";
 
     /**
      * 客服支付订单标记
@@ -67,4 +67,9 @@ public class RedisKeyConstant {
      */
     public static final String GET_PAY_PARAM_LOCK = RedisKeyConstant.REDIS_PREFIX + "get_pay_param_lock_";
 
+    /**
+     * 用户token创建锁
+     */
+    public static final String TOKEN_CREATE_LOCK = RedisKeyConstant.REDIS_PREFIX + "create_token_lock_";
+
 }

+ 9 - 11
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/GameUserRoleServiceImpl.java

@@ -140,23 +140,23 @@ public class GameUserRoleServiceImpl extends ServiceImpl<GameUserRoleMapper, Gam
     }
 
     private void gameRoleCreate(GameUserRoleUpdateParam param, UserData userData) {
-        //上锁
-        if (!distributedLockComponent.doLock(RedisKeyConstant.ROLE_UPDATE_KEY + "_" + userData.getUserId(), 0L, 1L, TimeUnit.MINUTES)) {
-            return;
-        }
-        //查询玩家信息
-        GameUser gameUser = gameUserService.getOne(new LambdaQueryWrapper<GameUser>().eq(GameUser::getGameId, userData.getGameId())
-                .eq(GameUser::getUserId, userData.getUserId()));
         //查询玩家角色信息
         GameUserRole userRole = this.getOne(new LambdaQueryWrapper<GameUserRole>()
                 .eq(GameUserRole::getGameId, userData.getGameId())
                 .eq(GameUserRole::getRoleId, param.getRoleId()));
         if (userRole != null) {
-            //释放锁
-            distributedLockComponent.unlock(RedisKeyConstant.ROLE_UPDATE_KEY + "_" + userData.getUserId());
             return;
         }
+        //上锁
+        if (!distributedLockComponent.doLock(RedisKeyConstant.ROLE_CREATE_LOCK + param.getRoleId(), 0L, 3L, TimeUnit.MINUTES)) {
+            return;
+        }
+        //查询玩家信息
         User user = userService.getById(userData.getUserId());
+        GameUser gameUser = gameUserService.getOne(new LambdaQueryWrapper<GameUser>()
+                .eq(GameUser::getGameId, userData.getGameId())
+                .eq(GameUser::getUserId, userData.getUserId()));
+        //创建角色
         userRole = this.transform(param, userData, gameUser, user);
         super.save(userRole);
         //更新玩家创角数
@@ -173,8 +173,6 @@ public class GameUserRoleServiceImpl extends ServiceImpl<GameUserRoleMapper, Gam
         callBackService.roleCallBack(userRole, userData);
         //用户创角埋点数据发送到卡夫卡
         kafkaService.eventTrack(KafkaEventTrackEnum.KAFKA_EVENT_TRACK_ROLE_CREATE, JsonUtil.toString(userRole));
-        //释放锁
-        distributedLockComponent.unlock(RedisKeyConstant.ROLE_UPDATE_KEY + "_" + userData.getUserId());
     }
 
     private GameUserRole transform(GameUserRoleUpdateParam param, UserData userData, GameUser gameUser, User user) {

+ 3 - 5
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/LoginServiceImpl.java

@@ -277,10 +277,10 @@ public class LoginServiceImpl implements IRegisterLoginService {
 
     private User userCreateSave(UserData userData, String userName, String password, String mobile, String openId, String sessionKey) {
         //锁Key
-        String lockKey = RedisKeyConstant.USER_CREATE + "_" + userData.getGameId() + "_" + userName;
+        String lockKey = RedisKeyConstant.USER_CREATE + userData.getGameId() + "_" + userName;
         //上锁
-        if (!distributedLockComponent.doLock(lockKey, 3L, 10L, TimeUnit.SECONDS)) {
-            throw new BaseException("用户信息正在注册中, 请稍后重试");
+        if (!distributedLockComponent.doLock(lockKey, 0L, 3L, TimeUnit.MINUTES)) {
+            throw new BaseException("用户信息正在注册中, 请勿重复请求!");
         }
         //渠道id, 链接参数, 分享人id
         Tuple3<Long, Map<String, String>, String> tuple3 = agentService.getUserAgentId(userData);
@@ -312,8 +312,6 @@ public class LoginServiceImpl implements IRegisterLoginService {
                 .build());
         //注册信息埋点数据发送到卡夫卡
         kafkaService.eventTrack(KafkaEventTrackEnum.KAFKA_EVENT_TRACK_REG, JsonUtil.toString(user));
-        //释放锁
-        distributedLockComponent.unlock(lockKey);
         return user;
     }
 

+ 1 - 1
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/OrderPayServiceImpl.java

@@ -94,7 +94,7 @@ public class OrderPayServiceImpl implements IOrderPayService {
                 .eq(OrderPayParam::getOrderId, order.getOrderId()));
         if (orderPayParam == null) {
             log.error("支付参数获取失败, orderId : {}", orderId);
-            throw new BaseException("支付参数获取失败");
+            throw new BaseException("支付参数正在创建中, 请勿重复请求!");
         }
         //返回支付参数
         return BeanUtil.copy(orderPayParam, PayParamVO.class);

+ 23 - 9
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/UserTokenServiceImpl.java

@@ -19,6 +19,7 @@ import com.zanxiang.game.module.sdk.pojo.vo.CpTokenCheckVO;
 import com.zanxiang.game.module.sdk.service.*;
 import com.zanxiang.game.module.sdk.util.RedisUtil;
 import com.zanxiang.game.module.sdk.util.SignUtil;
+import com.zanxiang.module.redis.service.IDistributedLockComponent;
 import com.zanxiang.module.util.DateUtil;
 import com.zanxiang.module.util.JsonUtil;
 import com.zanxiang.module.util.bean.BeanUtil;
@@ -35,6 +36,7 @@ import reactor.util.function.Tuples;
 import java.time.LocalDateTime;
 import java.util.Objects;
 import java.util.UUID;
+import java.util.concurrent.TimeUnit;
 
 /**
  * @author : lingfeng
@@ -57,6 +59,9 @@ public class UserTokenServiceImpl extends ServiceImpl<UserTokenMapper, UserToken
     @Autowired
     private IUserService userService;
 
+    @Autowired
+    private IDistributedLockComponent distributedLockComponent;
+
     @Autowired
     private IUserLoginLogService userLoginLogService;
 
@@ -248,15 +253,7 @@ public class UserTokenServiceImpl extends ServiceImpl<UserTokenMapper, UserToken
                 .eq(UserToken::getDeviceType, deviceType));
         //不存在token数据, 直接创建
         if (userToken == null) {
-            userToken = UserToken.builder()
-                    .userId(userId)
-                    .token(token)
-                    .deviceType(deviceType)
-                    .createTime(LocalDateTime.now())
-                    .updateTime(LocalDateTime.now())
-                    .expireTime(DateUtils.localDateTimeToSecond(LocalDateTime.now()) + ExpireTimeEnum.ONE_WEEK.getTime())
-                    .build();
-            super.save(userToken);
+            userToken = this.tokenSave(userId, token, deviceType);
         } else {
             //更新数据库
             userToken.setToken(token);
@@ -274,6 +271,23 @@ public class UserTokenServiceImpl extends ServiceImpl<UserTokenMapper, UserToken
         return token;
     }
 
+    private UserToken tokenSave(Long userId, String token, Integer deviceType) {
+        //上锁
+        if (!distributedLockComponent.doLock(RedisKeyConstant.TOKEN_CREATE_LOCK + userId + "_" + deviceType, 0L, 3L, TimeUnit.MINUTES)) {
+            throw new BaseException("token创建中, 请勿重复请求!");
+        }
+        UserToken userToken = UserToken.builder()
+                .userId(userId)
+                .token(token)
+                .deviceType(deviceType)
+                .createTime(LocalDateTime.now())
+                .updateTime(LocalDateTime.now())
+                .expireTime(DateUtils.localDateTimeToSecond(LocalDateTime.now()) + ExpireTimeEnum.ONE_WEEK.getTime())
+                .build();
+        super.save(userToken);
+        return userToken;
+    }
+
     @Override
     public void deleteUserToken(Long userId, Integer deviceType) {
         UserToken userToken = super.getOne(new LambdaQueryWrapper<UserToken>()