Browse Source

Merge remote-tracking branch 'origin/package' into package

zhangxianyu 3 tháng trước cách đây
mục cha
commit
304dd2d374
56 tập tin đã thay đổi với 1550 bổ sung156 xóa
  1. 1 1
      game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/GameBackApplication.java
  2. 14 3
      game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/controller/api/OceanengineCallbackApi.java
  3. 21 3
      game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/controller/api/TencentCallbackApi.java
  4. 7 0
      game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/dao/mapper/GameOceanengineCallbackMapper.java
  5. 7 0
      game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/dao/mapper/GameTencentMiniGameCallbackMapper.java
  6. 216 0
      game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/pojo/dto/GameOceanengineCallbackDTO.java
  7. 181 0
      game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/pojo/dto/GameTencentCallbackDTO.java
  8. 240 0
      game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/pojo/entity/GameOceanengineCallback.java
  9. 205 0
      game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/pojo/entity/GameTencentMiniGameCallback.java
  10. 10 0
      game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/service/IGameOceanengineCallbackService.java
  11. 10 0
      game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/service/IGameTencentMiniGameCallbackService.java
  12. 61 0
      game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/service/impl/GameOceanengineCallbackServiceImpl.java
  13. 61 0
      game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/service/impl/GameTencentMiniGameCallbackServiceImpl.java
  14. 19 5
      game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/service/impl/GameTencentMiniGameOrderServiceImpl.java
  15. 31 3
      game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/task/CallbackClearTask.java
  16. 4 0
      game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/task/TencentMiniOrderSplitBackTask.java
  17. 2 2
      game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/BanStatusEnum.java
  18. 2 2
      game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/CpStatusEnum.java
  19. 2 2
      game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/DeleteEnum.java
  20. 2 2
      game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/GameAuthEnum.java
  21. 3 3
      game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/GameCategoryEnum.java
  22. 2 2
      game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/H5SignShowEnum.java
  23. 2 2
      game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/H5WeChatControlEnum.java
  24. 3 3
      game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/OsEnum.java
  25. 2 2
      game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/PayApplicationTypeEnum.java
  26. 3 3
      game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/PayDeviceEnum.java
  27. 3 3
      game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/PayWayEnum.java
  28. 2 2
      game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/ShellControlEnum.java
  29. 2 2
      game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/StatusEnum.java
  30. 5 0
      game-module/game-module-manage/src/main/java/com/zanxiang/game/module/manage/constant/RedisKeyConstant.java
  31. 5 0
      game-module/game-module-mybatis/src/main/java/com/zanxiang/game/module/mybatis/entity/Game.java
  32. 10 0
      game-module/game-module-mybatis/src/main/java/com/zanxiang/game/module/mybatis/entity/User.java
  33. 5 0
      game-module/game-module-mybatis/src/main/java/com/zanxiang/game/module/mybatis/entity/UserLoginLog.java
  34. 10 0
      game-module/game-module-sdk/pom.xml
  35. 3 1
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/SDKApplication.java
  36. 5 0
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/constant/RedisKeyConstant.java
  37. 5 6
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/controller/CallBackController.java
  38. 28 7
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/controller/SmsController.java
  39. 2 2
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/enums/CallBackEnum.java
  40. 1 1
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/enums/CpPushDataEnum.java
  41. 2 2
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/enums/DataTypeEnum.java
  42. 2 2
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/enums/DeviceTypeEnum.java
  43. 1 1
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/enums/KafkaEventTrackEnum.java
  44. 2 2
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/enums/LoginTypeEnum.java
  45. 2 2
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/enums/ShellSwitchEnum.java
  46. 2 2
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/enums/SmsTypeEnum.java
  47. 2 2
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/enums/TokenCheckEnum.java
  48. 29 0
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/ISmsService.java
  49. 55 0
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/api/CpApiService.java
  50. 44 12
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/GameBackLogMediaSdkServiceImpl.java
  51. 33 37
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/GameServiceImpl.java
  52. 6 0
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/LoginServiceImpl.java
  53. 4 1
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/OrderServiceImpl.java
  54. 54 26
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/PerformOrderServiceImpl.java
  55. 95 0
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/SmsServiceImpl.java
  56. 20 5
      game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/UserLoginLogServiceImpl.java

+ 1 - 1
game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/GameBackApplication.java

@@ -18,7 +18,7 @@ public class GameBackApplication {
 
     public static void main(String[] args) {
         SpringApplication.run(GameBackApplication.class, args);
-        System.out.println("腾讯小游戏上报新增类型 (腾讯小游戏回传新增回传类型´・・)ノ(._.`)  \n" +
+        System.out.println("腾讯小游戏上报新增类型 (腾讯小游戏投放监测链接调试完成´・・)ノ(._.`)  \n" +
                 " ______  __     __     \n" +
                 "/_____/\\/__/\\ /__/\\    \n" +
                 "\\:::__\\/\\ \\::\\\\:.\\ \\   \n" +

+ 14 - 3
game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/controller/api/OceanengineCallbackApi.java

@@ -1,7 +1,9 @@
 package com.zanxiang.game.back.serve.controller.api;
 
 import com.zanxiang.game.back.serve.pojo.dto.GameOceanengineAppCallbackDTO;
+import com.zanxiang.game.back.serve.pojo.dto.GameOceanengineCallbackDTO;
 import com.zanxiang.game.back.serve.service.IGameOceanengineAppCallbackService;
+import com.zanxiang.game.back.serve.service.IGameOceanengineCallbackService;
 import com.zanxiang.module.util.JsonUtil;
 import com.zanxiang.module.util.pojo.ResultVO;
 import lombok.extern.slf4j.Slf4j;
@@ -18,6 +20,9 @@ import org.springframework.web.bind.annotation.RestController;
 @RequestMapping("/api/oceanengineCallback")
 public class OceanengineCallbackApi {
 
+    @Autowired
+    private IGameOceanengineCallbackService gameOceanengineCallbackService;
+
     @Autowired
     private IGameOceanengineAppCallbackService gameOceanengineAppCallbackService;
 
@@ -25,9 +30,15 @@ public class OceanengineCallbackApi {
     // &aid=__AID__&cid=__CID__&campaignId=__CAMPAIGN_ID__&ctype=__CTYPE__&advertiserId=__ADVERTISER_ID__&csite=__CSITE__&convertId=__CONVERT_ID__&requestId=__REQUEST_ID__&trackId=__TRACK_ID__&sl=__SL__
     // &imei=__imei__&idfa=__IDFA__&idfaMd5=__IDFA_MD5__&androidid=__ANDROIDID__&oaid=__OAID__&oaidMd5=__OAID_MD5__&os=__OS__&mac=__MAC__&mac1=MAC1&ipv4=__IPV4__&ipv6=__ipv6__&ip=__IP__
     // &ua=__UA__&geo=__GEO__&ts=__TS__&callbackParam=__CALLBACK_PARAM__&callbackUrl=__CALLBACK_URL__&model=__MODEL__&unionSite=__UNION_SITE__&caid=__CAID__&productid=__PRODUCTID__&outerid=__OUTERID__
-    @GetMapping("/callback")
-    public ResultVO<Boolean> callback(GameOceanengineAppCallbackDTO dto) {
-        log.info("头条监测链接:{}", JsonUtil.toString(dto));
+    @GetMapping("/app/callBack")
+    public ResultVO<Boolean> appCallback(GameOceanengineAppCallbackDTO dto) {
+        log.info("头条APP监测链接:{}", JsonUtil.toString(dto));
         return ResultVO.ok(gameOceanengineAppCallbackService.callback(dto));
     }
+
+    @GetMapping("/miniGame/callback")
+    public ResultVO<Boolean> callback(GameOceanengineCallbackDTO dto) {
+        log.info("头条微信小游戏监测链接:{}", JsonUtil.toString(dto));
+        return ResultVO.ok(gameOceanengineCallbackService.callback(dto));
+    }
 }

+ 21 - 3
game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/controller/api/TencentCallbackApi.java

@@ -1,7 +1,9 @@
 package com.zanxiang.game.back.serve.controller.api;
 
 import com.zanxiang.game.back.serve.pojo.dto.GameTencentAppCallbackDTO;
+import com.zanxiang.game.back.serve.pojo.dto.GameTencentCallbackDTO;
 import com.zanxiang.game.back.serve.service.IGameTencentAppCallbackService;
+import com.zanxiang.game.back.serve.service.IGameTencentMiniGameCallbackService;
 import com.zanxiang.module.util.JsonUtil;
 import com.zanxiang.module.util.pojo.ResultVO;
 import lombok.extern.slf4j.Slf4j;
@@ -17,9 +19,13 @@ import org.springframework.web.bind.annotation.RestController;
 @RestController
 @RequestMapping("/api/tencentCallback")
 public class TencentCallbackApi {
+
     @Autowired
     private IGameTencentAppCallbackService gameTencentAppCallbackService;
 
+    @Autowired
+    private IGameTencentMiniGameCallbackService gameTencentMiniGameCallbackService;
+
     // https://api.zanxiangwl.com/gameBack/api/tencentCallback/callback
     // ?clickId=__CLICK_ID__&clickTime=__CLICK_TIME__&adgroupId=__ADGROUP_ID__&dynamicCreativeId=__DYNAMIC_CREATIVE_ID__
     // &marketingAssetId=__MARKETING_ASSET_ID__&materialPackageId=__MATERIAL_PACKAGE_ID__&adPlatformType=__AD_PLATFORM_TYPE__
@@ -27,9 +33,21 @@ public class TencentCallbackApi {
     // &processTime=__PROCESS_TIME__&requestId=__REQUEST_ID__&impressionId=__IMPRESSION_ID__&muid=__MUID__
     // &hashAndroidId=__HASH_ANDROID_ID__&ip=__IP__&userAgent=__USER_AGENT__&callback=__CALLBACK__
     // &encryptedPositionId=__ENCRYPTED_POSITION_ID__&ipv6=__IPV6__&hashOaid=__HASH_OAID__&&caid=__QAID_CAA__&ipMd5=__IP_MD5__&ipv6Md5=__IPV6_MD5__
-    @GetMapping("/callback")
-    public ResultVO<Boolean> callback(GameTencentAppCallbackDTO dto) {
-        log.info("腾讯监测链接:{}", JsonUtil.toString(dto));
+    @GetMapping("/app/callback")
+    public ResultVO<Boolean> appCallback(GameTencentAppCallbackDTO dto) {
+        log.info("腾讯APP监测链接:{}", JsonUtil.toString(dto));
         return ResultVO.ok(gameTencentAppCallbackService.callback(dto));
     }
+
+    // https://api.zanxiangwl.com/gameBack/api/tencentCallback/miniGame/callback
+    // ?clickId=__CLICK_ID__&clickTime=__CLICK_TIME__&adgroupId=__ADGROUP_ID__&dynamicCreativeId=__DYNAMIC_CREATIVE_ID__
+    // &marketingAssetId=__MARKETING_ASSET_ID__&materialPackageId=__MATERIAL_PACKAGE_ID__&adPlatformType=__AD_PLATFORM_TYPE__
+    // &accountId=__ACCOUNT_ID__&agencyId=__AGENCY_ID__&clickSkuId=__CLICK_SKU_ID__&deviceOsType=__DEVICE_OS_TYPE__
+    // &processTime=__PROCESS_TIME__&requestId=__REQUEST_ID__&impressionId=__IMPRESSION_ID__&ip=__IP__&userAgent=__USER_AGENT__
+    // &callback=__CALLBACK__&encryptedPositionId=__ENCRYPTED_POSITION_ID__&ipv6=__IPV6__&ipMd5=__IP_MD5__&ipv6Md5=__IPV6_MD5__
+    @GetMapping("/miniGame/callback")
+    public ResultVO<Boolean> miniGameCallback(GameTencentCallbackDTO dto) {
+        log.info("腾讯小游戏监测链接:{}", JsonUtil.toString(dto));
+        return ResultVO.ok(gameTencentMiniGameCallbackService.callback(dto));
+    }
 }

+ 7 - 0
game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/dao/mapper/GameOceanengineCallbackMapper.java

@@ -0,0 +1,7 @@
+package com.zanxiang.game.back.serve.dao.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zanxiang.game.back.serve.pojo.entity.GameOceanengineCallback;
+
+public interface GameOceanengineCallbackMapper extends BaseMapper<GameOceanengineCallback> {
+}

+ 7 - 0
game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/dao/mapper/GameTencentMiniGameCallbackMapper.java

@@ -0,0 +1,7 @@
+package com.zanxiang.game.back.serve.dao.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zanxiang.game.back.serve.pojo.entity.GameTencentMiniGameCallback;
+
+public interface GameTencentMiniGameCallbackMapper extends BaseMapper<GameTencentMiniGameCallback> {
+}

+ 216 - 0
game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/pojo/dto/GameOceanengineCallbackDTO.java

@@ -0,0 +1,216 @@
+package com.zanxiang.game.back.serve.pojo.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author : lingfeng
+ * @time : 2025-01-10
+ * @description : 头条游戏 微信小游戏 监测链接回传数据(文档:https://open.oceanengine.com/labels/7/docs/1696710655781900)
+ */
+@Data
+public class GameOceanengineCallbackDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 巨量广告体验版中特有的宏参,代表巨量广告体验版的广告ID, ex: ID: 7074140945750507528, 宏: PROMOTION_ID
+     */
+    private Long promotionId;
+
+    /**
+     * 巨量广告体验版中特有的宏参,代表巨量广告体验版的项目ID, ex: ID: 7074140945750507528, 宏: PROJECT_ID
+     */
+    private Long projectId;
+
+    /**
+     * 针对巨量广告体验版,图片素材宏参数(下发原始素材id), 宏: MID1
+     */
+    private String mid1;
+
+    /**
+     * 针对巨量广告体验版,标题素材宏参数(下发原始素材id), 宏: MID2
+     */
+    private String mid2;
+
+    /**
+     * 针对巨量广告体验版,视频素材宏参数(下发原始素材id), 宏: MID3
+     */
+    private String mid3;
+
+    /**
+     * 针对巨量广告体验版,搭配试玩素材宏参数(下发原始素材id), 宏: MID4
+     */
+    private String mid4;
+
+    /**
+     * 针对巨量广告体验版,落地页素材宏参数(下发原始素材id), 宏: MID5
+     */
+    private String mid5;
+
+    /**
+     * 针对巨量广告体验版,安卓下载详情页素材宏参数(下发原始素材id), 宏: MID6
+     */
+    private String mid6;
+
+    /**
+     * 广告计划id, ex: 1645988237525045, 宏: AID
+     */
+    private Long aid;
+
+    /**
+     * 广告创意 id, ex: 1650703686054530, 宏: CID
+     */
+    private Long cid;
+
+    /**
+     * 广告组 id, ex: 1651688272934434, 宏: CAMPAIGN_ID
+     */
+    private Long campaignId;
+
+    /**
+     * 创意样式, 2=小图模式、3=大图模式、4=组图模式、5=视频, 宏: CTYPE
+     */
+    private Integer ctype;
+
+    /**
+     * 广告主id, ex: 1631857582073864, 宏: ADVERTISER_ID
+     */
+    private Long advertiserId;
+
+    /**
+     * 广告投放位置, 今日头条:1-10000,80000-110001、西瓜视频:10001-10099、火山小视频:30001-30099、抖音:40001-40099、番茄小说:26001-26099、穿山甲开屏广告:800000000、穿山甲网盟非开屏广告:900000000、通投广告位:33013、搜索:38016, 宏: CSITE
+     */
+    private Integer csite;
+
+    /**
+     * 转化id, ex: 1681681272671401, 宏: CONVERT_ID
+     */
+    private Long convertId;
+
+    /**
+     * 请求下发的id, ex: 20191202180607010026077068090BA60A, 宏: REQUEST_ID
+     */
+    private String requestId;
+
+    /**
+     * 请求下发的id&创意id的md5,16位, ex: b2f97be5b363764d, 宏: TRACK_ID
+     */
+    private String trackId;
+
+    /**
+     * 这次请求的语言, ex: zh, 宏: SL
+     */
+    private String sl;
+
+    /**
+     * 安卓的设备 ID 的 md5 摘要,32位, 假设原始的 imei 是 868823031739689我们最终发送的是 imei 的 md5 摘要:0c2bd03c39f19845bf54ea0abafae70e,会存在部分无法获取imei的情况,则imei会回传空字符串或者0的md5摘要, 宏: IMEI
+     */
+    private String imei;
+
+    /**
+     * IOS 6+的设备id字段,32位, ex: 4FCFEFA1-096D-4176-B352-1870ED6DB777注意,一些例外00000000-0000-0000-0000-000000000000, 如果用户关闭里读取idfa的权限,会导致idfa全部是0, 宏: IDFA
+     */
+    private String idfa;
+
+    /**
+     * IOS 6+的设备id的md5摘要,32位, ex: 09c593c62a6074ae5f859e97a222c0e8,注意,用户关闭读取idfa权限,0值也会进行MD5加密, 宏: IDFA_MD5
+     */
+    private String idfaMd5;
+
+    /**
+     * 安卓id原值的md5,32位, ex: androidId的原值是: 7b5ca2d57178d2f1我们进行md5摘要,最终结果是:873541edf36da9170af47d5b69e82193, 宏: ANDROIDID
+     */
+    private String androidid;
+
+    /**
+     * Android Q及更高版本的设备号,32位, ex: 97e7ef3f-e5f2-d0b8-ccfc-f79bbeaf4841注意,一些例外如果无法获取oaid,oaid会传空字符串, 宏: OAID
+     */
+    private String oaid;
+
+    /**
+     * Android Q及更高版本的设备号的md5摘要,32位, ex: 87f8274c36eb73fabcbf143a10eca6a4,会存在部分无法获取oaid的情况,则该字段会回传空字符串, 宏: OAID_MD5
+     */
+    private String oaidMd5;
+
+    /**
+     * 操作系统平台, 安卓:0、IOS:1、其它:3, 宏: OS
+     */
+    private Integer os;
+
+    /**
+     * 移动设备mac地址,转换成大写字母,去掉“:”,并且取md5摘要后的结果, 假设原始的mac地址是34:d7:12:9b:3a:89去掉“:”后变成 34d7129b3a89变成大写字母34D7129B3A89取MD5摘要,最后变成如下结果:df97bc5021e14256e141b2f036df5a3c, 宏: MAC
+     */
+    private String mac;
+
+    /**
+     * 移动设备 mac 地址,转换成大写字母,并且取md5摘要后的结果,32位, 假设原始的mac地址是 34:d7:12:9b:3a:89变成大写字母: 34:D7:12:9B:3A:89进行MD5摘要,最后变成如下结果:4d0433eb614f5d5ad409a65395426cbe, 宏: MAC1
+     */
+    private String mac1;
+
+    /**
+     * 优先使用上报请求的对端 IP 地址。如果该IP为 IPv6, 则使用客户端获取的 client_ipv4 地址, ex: 61.168.146.195或为空, 宏: IPV4
+     */
+    private String ipv4;
+
+    /**
+     * 优先使用上报请求的对端 IP 地址。如果该IP为 IPv4, 则使用客户端获取的 client_ipv6 地址, ex: 240e:498:1c90:9d00:5513:b9c9:650a:d9d2或为空, 宏: IPV6
+     */
+    private String ipv6;
+
+    /**
+     * [下发逻辑修改 - 0727生效]正常情况下,全量下发IPv4地址。极少数情况如IPV4无法取数,则下发 IPv6, ex: 61.158.146.195或240e:398:1c90:9d00:5513:b9c9:650a:d9d2, 宏: IP
+     */
+    private String ip;
+
+    /**
+     * 用户代理(User Agent),一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。经过url encode, ex: News+7.4.5+rv%3A7.4.5.23%5C%28iPhone%3B+iOS+12.4.1%3B+zh_CN%5C%29Cronet, 宏: UA
+     */
+    private String ua;
+
+    /**
+     * 位置信息,包含三部分:latitude(纬度),longitude(经度)以及precise(确切信息,精度)十进制保留1位小数,西经南纬保留负数,用字母 x 分 割纬度与精度 (先纬后经,最后精度), ex: 35.7x122.4x100.0, 宏: GEO
+     */
+    private String geo;
+
+    /**
+     * 客户端发生广告点击事件的时间,以毫秒为单位时间戳, ex: 1575194434000, 宏: TS
+     */
+    private Long ts;
+
+    /**
+     * 一些跟广告信息相关的回调参数,内容是一个加密字符串,在调用事件回传接口的时候会用到, ex: EJiw267wvfQCGKf2g74ZIPD89-vIATAMOAFCIjIwMTkxMTI3MTQxMTEzMDEwMDI2MDc3MjE1MTUwNTczNTBIAQ==, 宏: CALLBACK_PARAM
+     */
+    private String callbackParam;
+
+    /**
+     * 直接把调用事件回传接口的url生成出来,广告主可以直接使用, ex: https://ad.toutiao.com/track/activate/?callback=EJiw267wvfQCGKf2g74ZIPD89-vIATAMOAFCIjIwMTkxMTI3MTQxMTEzMDEwMDI2MDc3MjE1MTUwNTczNTBIAQ==&os=0&muid=db94e6a60a9c6661e3e03a7d301c25b6, 宏: CALLBACK_URL
+     */
+    private String callbackUrl;
+
+    /**
+     * 手机型号, 包含多种格式, ex: iPhone12,2 (urlencode之后为iPhone12%2c2),iPhone X (urlencode之后为iPhone+X),SM-A750GN (urlencode之后为SM-A750GN), 宏: MODEL
+     */
+    private String model;
+
+    /**
+     * 对外广告位编码, ex: 2045891290, 宏: UNION_SITE
+     */
+    private String unionSite;
+
+    /**
+     * 中国广告协会互联网广告标识,包含最新两个版本的CAID和版本号,url encode之后的json字符串。(【CAID】和【CAID1、CAID2】的信息一致,使用一种即可;建议使用【CAID】,参数中包含多个信息,后续维护成本低), ex: %5B%7B%22version%22%3A%2220220111%22%2C%22caid%22%3A%22912ec803b2ce49e4a541068d495ab570%22%7D%2C%7B%22version%22%3A%2220211207%22%2C%22caid%22%3A%22e332a76c29654fcb7f6e6b31ced090c7%22%7D%5D, 宏: CAID
+     */
+    private String caid;
+
+    /**
+     * 商品id,仅支持站内(不支持穿山甲), 宏: PRODUCTID
+     */
+    private String productid;
+
+    /**
+     * 商品id,同时支持站内和穿山甲, 宏: OUTERID
+     */
+    private String outerid;
+}

+ 181 - 0
game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/pojo/dto/GameTencentCallbackDTO.java

@@ -0,0 +1,181 @@
+package com.zanxiang.game.back.serve.pojo.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author : lingfeng
+ * @time : 2025-01-10
+ * @description : 腾讯游戏 微信小游戏 监测链接回传数据(文档:https://datanexus.qq.com/doc/develop/guider/interface/conversion/ad_track_click#5-%E5%AD%97%E6%AE%B5%E5%88%97%E8%A1%A8)
+ */
+@Data
+public class GameTencentCallbackDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 点击id, ex: 24oi6xq2aaakvagnqu7a, 宏: __CLICK_ID__
+     */
+    private String clickId;
+
+    /**
+     * 点击时间, ex: 1586437362, 宏: __CLICK_TIME__
+     */
+    private Long clickTime;
+
+    /**
+     * 曝光时间, ex: 1586437361, ex: , 宏: __IMPRESSION_TIME__
+     */
+    private Long impressionTime;
+
+    /**
+     * 广告组id(实际为广告id), ex: 228691429, ex: , 宏: __ADGROUP_ID__
+     */
+    private Long adgroupId;
+
+    /**
+     * 创意 ID, ex: 654321, 宏: __DYNAMIC_CREATIVE_ID__
+     */
+    private Long dynamicCreativeId;
+
+    /**
+     * 营销资产ID, ex: 12345, 宏: __MARKETING_ASSET_ID__
+     */
+    private Long marketingAssetId;
+
+    /**
+     * 素材标签ID, 宏: __MATERIAL_PACKAGE_ID__
+     */
+    private String materialPackageId;
+
+    /**
+     * 广告投放平台, 1:GDT entrance、3:京东直投、5:经 wechat mp 投放的广告、6:京东运营、8:QQ 公众账号平台、9:移动联盟 SSP、10:58 运营、11:58 商户、12:易车运营、13:易车商户、14:融 360 运营、15:融 360 商户、16:点评运营、17:点评商户、18:来自 OMG 的广告主、19:京东外单, 宏: __AD_PLATFORM_TYPE__
+     */
+    private Integer adPlatformType;
+
+    /**
+     * 广告主id, ex: 9471147, 宏: __ACCOUNT_ID__
+     */
+    private Long accountId;
+
+    /**
+     * 代理商id, ex: 1050262, 宏: __AGENCY_ID__
+     */
+    private Long agencyId;
+
+    /**
+     * 点击sku, ex: 478c4a93a054f7c9087b4ecb1f03f8a1, 宏: __CLICK_SKU_ID__
+     */
+    private String clickSkuId;
+
+    /**
+     * 设备类型, ex: ios、android, 宏: __DEVICE_OS_TYPE__
+     */
+    private String deviceOsType;
+
+    /**
+     * 请求时间, ex: 1586437335, 宏: __PROCESS_TIME__
+     */
+    private Long processTime;
+
+    /**
+     * 应用id, ex: 1101072624、wx69618ae091cf2c76, 宏: __PROMOTED_OBJECT_ID__
+     */
+    private String promotedObjectId;
+
+    /**
+     * 请求id, ex: vqp7xdombqonw, 宏: __REQUEST_ID__
+     */
+    private String requestId;
+
+    /**
+     * 曝光id, ex: xkrx5et47h7g401, 宏: __IMPRESSION_ID__
+     */
+    private String impressionId;
+
+    /**
+     * 设备id(imei或idfa的加密值),对 IMEI 设备号转成小写,再进行md5编码,再小写,32位、对 IDFA 设备号保持大写,再进行 md5 编码,再小写,32位, 宏: __MUID__
+     */
+    private String muid;
+
+    /**
+     * 安卓id做md5加密后小写, ex: 797745b011e3286de9e1a1c59ba72c97, 宏: __HASH_ANDROID_ID__
+     */
+    private String hashAndroidId;
+
+    /**
+     * 媒体投放系统获取的用户终端的公共IPV4地址, ex: 183.226.102.120, 宏: __IP__
+     */
+    private String ip;
+
+    /**
+     * 用户代理(user_agent), ex: Dalvik%2F2.1.0+%28Linux%3B+U%3B+Android+8.0.0%3B+PIC-AL00+Build%2FHUAWEIPIC-AL00%29, 宏: __USER_AGENT__
+     */
+    private String userAgent;
+
+    /**
+     * 情况1:使用 DataNexus 配置,并与广告直接绑定(ex: 空值)、情况2:新版转化归因中的监测链接信息(使用 DataNexus 或直接填写监测链接)直接提供上报信息回传接口的 url,示例为url encode 编码原值,广告主需要 decode 作为 post 请求url回传至腾讯广告(ex: http%3A%2F%2Ftracking.e.qq.com%2Fconv%3Fcb%3DxXx%252BxXx%253D%26conv_id%3D123)、情况3:使用投放管理平台 - 工具(ex: http://tracking.e.qq.com/conv?cb=%s&&%s&&%s&&%s 其中%s代表的参数分别为 productId、productType、advertiserId、clickId), 宏: __CALLBACK__
+     */
+    private String callback;
+
+    /**
+     * 联盟广告位id, ex: 8144201, 宏: __ENCRYPTED_POSITION_ID__
+     */
+    private String encryptedPositionId;
+
+    /**
+     * 媒体投放系统获取的用户终端的公共IPV6地址, ex: 2409%3A8a55%3A4cc0%3A4050%3A2507%3A4922%3Abbe0%3A524b, 宏: __IPV6__
+     */
+    private String ipv6;
+
+    /**
+     * Android Q 及更高版本的设备号,64位及以下,取原值后做md5加密, ex: 9d271e4d04de7e4b0b4f1df20e79ce64, 宏: __HASH_OAID__
+     */
+    private String hashOaid;
+
+    /**
+     * URL Encode后的JSON数组;其中qaid为中广协ID(即CAID),hash_qaid为CAID原值MD5加密后的结果, version为腾讯版本号,支持两个版本同时下发(即最新版和上一版),腾讯版本号与中广协版本对应关系为:腾讯 1001 = 中广协 20200901;腾讯 1003 = 中广协 20201230; 腾讯1004 = 中广协 20211207;腾讯1005=中广协 20220111;腾讯1006=中广协 20230330, 宏: __QAID_CAA__
+     */
+    private String caid;
+
+    /**
+     * 机型, ex:  PCKM00、Redmi 7、iPhone 7..., 宏: __MODEL__
+     */
+    private String model;
+
+    /**
+     * 专用于网页类小程序转化规则的点击监测下发,其它类型不支持该字段下发。每个用户针对小程序应用会产生一个安全的OpenID,只针对当前的小程序有效, ex: ozWH25VK0aodxYMZrX0Lqj9HHhrg, 宏: __WECHAT_OPEN_ID__
+     */
+    private String wechatOpenid;
+
+    /**
+     * 媒体投放系统获取的用户终端的公共IPV4地址MD5加密后转小写,仅在新版转化里支持配置, 宏: __IP_MD5__
+     */
+    private String ipMd5;
+
+    /**
+     * 媒体投放系统获取的用户终端的公共IPV6地址MD5加密后转小写,仅在新版转化里支持配置, 宏: __IPV6_MD5__
+     */
+    private String ipv6Md5;
+
+    /**
+     * 渠道包id, 只 for Android 设备生效, 宏: __CHANNEL_PACKAGE_ID__
+     */
+    private String channelPackageId;
+
+    /**
+     * 操作系统版本, 只 for iOS 和 Android 设备生效, 宏: __DEVICE_OS_VERSION__
+     */
+    private String deviceOsVersion;
+
+    /**
+     * 行为类型, LANDING_PAGE_CLICK:点击跳转按钮,RESERVATION:表单预约, 宏: __ACT_TYPE__
+     */
+    private String actType;
+
+    /**
+     * 行为时间, ex: 1586437361, 宏: __ACT_TIME__
+     */
+    private Long actTime;
+}

+ 240 - 0
game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/pojo/entity/GameOceanengineCallback.java

@@ -0,0 +1,240 @@
+package com.zanxiang.game.back.serve.pojo.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.time.LocalDate;
+
+/**
+ * @author : lingfeng
+ * @time : 2025-01-10
+ * @description : 头条游戏 小游戏 监测链接回传数据(文档:https://open.oceanengine.com/labels/7/docs/1696710655781900)
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@TableName(GameOceanengineCallback.TABLE_NAME)
+public class GameOceanengineCallback implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+    public static final String TABLE_NAME = "t_game_oceanengine_callback";
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    private String agentKey;
+
+    private Long gameId;
+
+    /**
+     * 时间发生日期
+     */
+    private LocalDate day;
+
+    /**
+     * 巨量广告体验版中特有的宏参,代表巨量广告体验版的广告ID, ex: ID: 7074140945750507528, 宏: PROMOTION_ID
+     */
+    private Long promotionId;
+
+    /**
+     * 巨量广告体验版中特有的宏参,代表巨量广告体验版的项目ID, ex: ID: 7074140945750507528, 宏: PROJECT_ID
+     */
+    private Long projectId;
+
+    /**
+     * 针对巨量广告体验版,图片素材宏参数(下发原始素材id), 宏: MID1
+     */
+    private String mid1;
+
+    /**
+     * 针对巨量广告体验版,标题素材宏参数(下发原始素材id), 宏: MID2
+     */
+    private String mid2;
+
+    /**
+     * 针对巨量广告体验版,视频素材宏参数(下发原始素材id), 宏: MID3
+     */
+    private String mid3;
+
+    /**
+     * 针对巨量广告体验版,搭配试玩素材宏参数(下发原始素材id), 宏: MID4
+     */
+    private String mid4;
+
+    /**
+     * 针对巨量广告体验版,落地页素材宏参数(下发原始素材id), 宏: MID5
+     */
+    private String mid5;
+
+    /**
+     * 针对巨量广告体验版,安卓下载详情页素材宏参数(下发原始素材id), 宏: MID6
+     */
+    private String mid6;
+
+    /**
+     * 广告计划id, ex: 1645988237525045, 宏: AID
+     */
+    private Long aid;
+
+    /**
+     * 广告创意 id, ex: 1650703686054530, 宏: CID
+     */
+    private Long cid;
+
+    /**
+     * 广告组 id, ex: 1651688272934434, 宏: CAMPAIGN_ID
+     */
+    private Long campaignId;
+
+    /**
+     * 创意样式, 2=小图模式、3=大图模式、4=组图模式、5=视频, 宏: CTYPE
+     */
+    private Integer ctype;
+
+    /**
+     * 广告主id, ex: 1631857582073864, 宏: ADVERTISER_ID
+     */
+    private Long advertiserId;
+
+    /**
+     * 广告投放位置, 今日头条:1-10000,80000-110001、西瓜视频:10001-10099、火山小视频:30001-30099、抖音:40001-40099、番茄小说:26001-26099、穿山甲开屏广告:800000000、穿山甲网盟非开屏广告:900000000、通投广告位:33013、搜索:38016, 宏: CSITE
+     */
+    private Integer csite;
+
+    /**
+     * 转化id, ex: 1681681272671401, 宏: CONVERT_ID
+     */
+    private Long convertId;
+
+    /**
+     * 请求下发的id, ex: 20191202180607010026077068090BA60A, 宏: REQUEST_ID
+     */
+    private String requestId;
+
+    /**
+     * 请求下发的id&创意id的md5,16位, ex: b2f97be5b363764d, 宏: TRACK_ID
+     */
+    private String trackId;
+
+    /**
+     * 这次请求的语言, ex: zh, 宏: SL
+     */
+    private String sl;
+
+    /**
+     * 安卓的设备 ID 的 md5 摘要,32位, 假设原始的 imei 是 868823031739689我们最终发送的是 imei 的 md5 摘要:0c2bd03c39f19845bf54ea0abafae70e,会存在部分无法获取imei的情况,则imei会回传空字符串或者0的md5摘要, 宏: IMEI
+     */
+    private String imei;
+
+    /**
+     * IOS 6+的设备id字段,32位, ex: 4FCFEFA1-096D-4176-B352-1870ED6DB777注意,一些例外00000000-0000-0000-0000-000000000000, 如果用户关闭里读取idfa的权限,会导致idfa全部是0, 宏: IDFA
+     */
+    private String idfa;
+
+    /**
+     * IOS 6+的设备id的md5摘要,32位, ex: 09c593c62a6074ae5f859e97a222c0e8,注意,用户关闭读取idfa权限,0值也会进行MD5加密, 宏: IDFA_MD5
+     */
+    private String idfaMd5;
+
+    /**
+     * 安卓id原值的md5,32位, ex: androidId的原值是: 7b5ca2d57178d2f1我们进行md5摘要,最终结果是:873541edf36da9170af47d5b69e82193, 宏: ANDROIDID
+     */
+    private String androidid;
+
+    /**
+     * Android Q及更高版本的设备号,32位, ex: 97e7ef3f-e5f2-d0b8-ccfc-f79bbeaf4841注意,一些例外如果无法获取oaid,oaid会传空字符串, 宏: OAID
+     */
+    private String oaid;
+
+    /**
+     * Android Q及更高版本的设备号的md5摘要,32位, ex: 87f8274c36eb73fabcbf143a10eca6a4,会存在部分无法获取oaid的情况,则该字段会回传空字符串, 宏: OAID_MD5
+     */
+    private String oaidMd5;
+
+    /**
+     * 操作系统平台, 安卓:0、IOS:1、其它:3, 宏: OS
+     */
+    private Integer os;
+
+    /**
+     * 移动设备mac地址,转换成大写字母,去掉“:”,并且取md5摘要后的结果, 假设原始的mac地址是34:d7:12:9b:3a:89去掉“:”后变成 34d7129b3a89变成大写字母34D7129B3A89取MD5摘要,最后变成如下结果:df97bc5021e14256e141b2f036df5a3c, 宏: MAC
+     */
+    private String mac;
+
+    /**
+     * 移动设备 mac 地址,转换成大写字母,并且取md5摘要后的结果,32位, 假设原始的mac地址是 34:d7:12:9b:3a:89变成大写字母: 34:D7:12:9B:3A:89进行MD5摘要,最后变成如下结果:4d0433eb614f5d5ad409a65395426cbe, 宏: MAC1
+     */
+    private String mac1;
+
+    /**
+     * 优先使用上报请求的对端 IP 地址。如果该IP为 IPv6, 则使用客户端获取的 client_ipv4 地址, ex: 61.168.146.195或为空, 宏: IPV4
+     */
+    private String ipv4;
+
+    /**
+     * 优先使用上报请求的对端 IP 地址。如果该IP为 IPv4, 则使用客户端获取的 client_ipv6 地址, ex: 240e:498:1c90:9d00:5513:b9c9:650a:d9d2或为空, 宏: IPV6
+     */
+    private String ipv6;
+
+    /**
+     * [下发逻辑修改 - 0727生效]正常情况下,全量下发IPv4地址。极少数情况如IPV4无法取数,则下发 IPv6, ex: 61.158.146.195或240e:398:1c90:9d00:5513:b9c9:650a:d9d2, 宏: IP
+     */
+    private String ip;
+
+    /**
+     * 用户代理(User Agent),一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。经过url encode, ex: News+7.4.5+rv%3A7.4.5.23%5C%28iPhone%3B+iOS+12.4.1%3B+zh_CN%5C%29Cronet, 宏: UA
+     */
+    private String ua;
+
+    /**
+     * 位置信息,包含三部分:latitude(纬度),longitude(经度)以及precise(确切信息,精度)十进制保留1位小数,西经南纬保留负数,用字母 x 分 割纬度与精度 (先纬后经,最后精度), ex: 35.7x122.4x100.0, 宏: GEO
+     */
+    private String geo;
+
+    /**
+     * 客户端发生广告点击事件的时间,以毫秒为单位时间戳, ex: 1575194434000, 宏: TS
+     */
+    private Long ts;
+
+    /**
+     * 一些跟广告信息相关的回调参数,内容是一个加密字符串,在调用事件回传接口的时候会用到, ex: EJiw267wvfQCGKf2g74ZIPD89-vIATAMOAFCIjIwMTkxMTI3MTQxMTEzMDEwMDI2MDc3MjE1MTUwNTczNTBIAQ==, 宏: CALLBACK_PARAM
+     */
+    private String callbackParam;
+
+    /**
+     * 直接把调用事件回传接口的url生成出来,广告主可以直接使用, ex: https://ad.toutiao.com/track/activate/?callback=EJiw267wvfQCGKf2g74ZIPD89-vIATAMOAFCIjIwMTkxMTI3MTQxMTEzMDEwMDI2MDc3MjE1MTUwNTczNTBIAQ==&os=0&muid=db94e6a60a9c6661e3e03a7d301c25b6, 宏: CALLBACK_URL
+     */
+    private String callbackUrl;
+
+    /**
+     * 手机型号, 包含多种格式, ex: iPhone12,2 (urlencode之后为iPhone12%2c2),iPhone X (urlencode之后为iPhone+X),SM-A750GN (urlencode之后为SM-A750GN), 宏: MODEL
+     */
+    private String model;
+
+    /**
+     * 对外广告位编码, ex: 2045891290, 宏: UNION_SITE
+     */
+    private String unionSite;
+
+    /**
+     * 中国广告协会互联网广告标识,包含最新两个版本的CAID和版本号,url encode之后的json字符串。(【CAID】和【CAID1、CAID2】的信息一致,使用一种即可;建议使用【CAID】,参数中包含多个信息,后续维护成本低), ex: %5B%7B%22version%22%3A%2220220111%22%2C%22caid%22%3A%22912ec803b2ce49e4a541068d495ab570%22%7D%2C%7B%22version%22%3A%2220211207%22%2C%22caid%22%3A%22e332a76c29654fcb7f6e6b31ced090c7%22%7D%5D, 宏: CAID
+     */
+    private String caid;
+
+    /**
+     * 商品id,仅支持站内(不支持穿山甲), 宏: PRODUCTID
+     */
+    private String productid;
+
+    /**
+     * 商品id,同时支持站内和穿山甲, 宏: OUTERID
+     */
+    private String outerid;
+}

+ 205 - 0
game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/pojo/entity/GameTencentMiniGameCallback.java

@@ -0,0 +1,205 @@
+package com.zanxiang.game.back.serve.pojo.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.time.LocalDate;
+
+/**
+ * @author : lingfeng
+ * @time : 2025-01-10
+ * @description : 腾讯游戏 微信小游戏 监测链接回传数据(文档:https://datanexus.qq.com/doc/develop/guider/interface/conversion/ad_track_click#5-%E5%AD%97%E6%AE%B5%E5%88%97%E8%A1%A8)
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@TableName(GameTencentMiniGameCallback.TABLE_NAME)
+public class GameTencentMiniGameCallback implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+    public static final String TABLE_NAME = "t_game_tencent_mini_game_callback";
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    private String agentKey;
+
+    private Long gameId;
+
+    /**
+     * 时间发生日期
+     */
+    private LocalDate day;
+
+    /**
+     * 点击id, ex: 24oi6xq2aaakvagnqu7a, 宏: __CLICK_ID__
+     */
+    private String clickId;
+
+    /**
+     * 点击时间, ex: 1586437362, 宏: __CLICK_TIME__
+     */
+    private Long clickTime;
+
+    /**
+     * 曝光时间, ex: 1586437361, ex: , 宏: __IMPRESSION_TIME__
+     */
+    private Long impressionTime;
+
+    /**
+     * 广告组id(实际为广告id), ex: 228691429, ex: , 宏: __ADGROUP_ID__
+     */
+    private Long adgroupId;
+
+    /**
+     * 创意 ID, ex: 654321, 宏: __DYNAMIC_CREATIVE_ID__
+     */
+    private Long dynamicCreativeId;
+
+    /**
+     * 营销资产ID, ex: 12345, 宏: __MARKETING_ASSET_ID__
+     */
+    private Long marketingAssetId;
+
+    /**
+     * 素材标签ID, 宏: __MATERIAL_PACKAGE_ID__
+     */
+    private String materialPackageId;
+
+    /**
+     * 广告投放平台, 1:GDT entrance、3:京东直投、5:经 wechat mp 投放的广告、6:京东运营、8:QQ 公众账号平台、9:移动联盟 SSP、10:58 运营、11:58 商户、12:易车运营、13:易车商户、14:融 360 运营、15:融 360 商户、16:点评运营、17:点评商户、18:来自 OMG 的广告主、19:京东外单, 宏: __AD_PLATFORM_TYPE__
+     */
+    private Integer adPlatformType;
+
+    /**
+     * 广告主id, ex: 9471147, 宏: __ACCOUNT_ID__
+     */
+    private Long accountId;
+
+    /**
+     * 代理商id, ex: 1050262, 宏: __AGENCY_ID__
+     */
+    private Long agencyId;
+
+    /**
+     * 点击sku, ex: 478c4a93a054f7c9087b4ecb1f03f8a1, 宏: __CLICK_SKU_ID__
+     */
+    private String clickSkuId;
+
+    /**
+     * 设备类型, ex: ios、android, 宏: __DEVICE_OS_TYPE__
+     */
+    private String deviceOsType;
+
+    /**
+     * 请求时间, ex: 1586437335, 宏: __PROCESS_TIME__
+     */
+    private Long processTime;
+
+    /**
+     * 应用id, ex: 1101072624、wx69618ae091cf2c76, 宏: __PROMOTED_OBJECT_ID__
+     */
+    private String promotedObjectId;
+
+    /**
+     * 请求id, ex: vqp7xdombqonw, 宏: __REQUEST_ID__
+     */
+    private String requestId;
+
+    /**
+     * 曝光id, ex: xkrx5et47h7g401, 宏: __IMPRESSION_ID__
+     */
+    private String impressionId;
+
+    /**
+     * 设备id(imei或idfa的加密值),对 IMEI 设备号转成小写,再进行md5编码,再小写,32位、对 IDFA 设备号保持大写,再进行 md5 编码,再小写,32位, 宏: __MUID__
+     */
+    private String muid;
+
+    /**
+     * 安卓id做md5加密后小写, ex: 797745b011e3286de9e1a1c59ba72c97, 宏: __HASH_ANDROID_ID__
+     */
+    private String hashAndroidId;
+
+    /**
+     * 媒体投放系统获取的用户终端的公共IPV4地址, ex: 183.226.102.120, 宏: __IP__
+     */
+    private String ip;
+
+    /**
+     * 用户代理(user_agent), ex: Dalvik%2F2.1.0+%28Linux%3B+U%3B+Android+8.0.0%3B+PIC-AL00+Build%2FHUAWEIPIC-AL00%29, 宏: __USER_AGENT__
+     */
+    private String userAgent;
+
+    /**
+     * 情况1:使用 DataNexus 配置,并与广告直接绑定(ex: 空值)、情况2:新版转化归因中的监测链接信息(使用 DataNexus 或直接填写监测链接)直接提供上报信息回传接口的 url,示例为url encode 编码原值,广告主需要 decode 作为 post 请求url回传至腾讯广告(ex: http%3A%2F%2Ftracking.e.qq.com%2Fconv%3Fcb%3DxXx%252BxXx%253D%26conv_id%3D123)、情况3:使用投放管理平台 - 工具(ex: http://tracking.e.qq.com/conv?cb=%s&&%s&&%s&&%s 其中%s代表的参数分别为 productId、productType、advertiserId、clickId), 宏: __CALLBACK__
+     */
+    private String callback;
+
+    /**
+     * 联盟广告位id, ex: 8144201, 宏: __ENCRYPTED_POSITION_ID__
+     */
+    private String encryptedPositionId;
+
+    /**
+     * 媒体投放系统获取的用户终端的公共IPV6地址, ex: 2409%3A8a55%3A4cc0%3A4050%3A2507%3A4922%3Abbe0%3A524b, 宏: __IPV6__
+     */
+    private String ipv6;
+
+    /**
+     * Android Q 及更高版本的设备号,64位及以下,取原值后做md5加密, ex: 9d271e4d04de7e4b0b4f1df20e79ce64, 宏: __HASH_OAID__
+     */
+    private String hashOaid;
+
+    /**
+     * URL Encode后的JSON数组;其中qaid为中广协ID(即CAID),hash_qaid为CAID原值MD5加密后的结果, version为腾讯版本号,支持两个版本同时下发(即最新版和上一版),腾讯版本号与中广协版本对应关系为:腾讯 1001 = 中广协 20200901;腾讯 1003 = 中广协 20201230; 腾讯1004 = 中广协 20211207;腾讯1005=中广协 20220111;腾讯1006=中广协 20230330, 宏: __QAID_CAA__
+     */
+    private String caid;
+
+    /**
+     * 机型, ex:  PCKM00、Redmi 7、iPhone 7..., 宏: __MODEL__
+     */
+    private String model;
+
+    /**
+     * 专用于网页类小程序转化规则的点击监测下发,其它类型不支持该字段下发。每个用户针对小程序应用会产生一个安全的OpenID,只针对当前的小程序有效, ex: ozWH25VK0aodxYMZrX0Lqj9HHhrg, 宏: __WECHAT_OPEN_ID__
+     */
+    private String wechatOpenid;
+
+    /**
+     * 媒体投放系统获取的用户终端的公共IPV4地址MD5加密后转小写,仅在新版转化里支持配置, 宏: __IP_MD5__
+     */
+    private String ipMd5;
+
+    /**
+     * 媒体投放系统获取的用户终端的公共IPV6地址MD5加密后转小写,仅在新版转化里支持配置, 宏: __IPV6_MD5__
+     */
+    private String ipv6Md5;
+
+    /**
+     * 渠道包id, 只 for Android 设备生效, 宏: __CHANNEL_PACKAGE_ID__
+     */
+    private String channelPackageId;
+
+    /**
+     * 操作系统版本, 只 for iOS 和 Android 设备生效, 宏: __DEVICE_OS_VERSION__
+     */
+    private String deviceOsVersion;
+
+    /**
+     * 行为类型, LANDING_PAGE_CLICK:点击跳转按钮,RESERVATION:表单预约, 宏: __ACT_TYPE__
+     */
+    private String actType;
+
+    /**
+     * 行为时间, ex: 1586437361, 宏: __ACT_TIME__
+     */
+    private Long actTime;
+}

+ 10 - 0
game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/service/IGameOceanengineCallbackService.java

@@ -0,0 +1,10 @@
+package com.zanxiang.game.back.serve.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zanxiang.game.back.serve.pojo.dto.GameOceanengineCallbackDTO;
+import com.zanxiang.game.back.serve.pojo.entity.GameOceanengineCallback;
+
+public interface IGameOceanengineCallbackService extends IService<GameOceanengineCallback> {
+
+    boolean callback(GameOceanengineCallbackDTO dto);
+}

+ 10 - 0
game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/service/IGameTencentMiniGameCallbackService.java

@@ -0,0 +1,10 @@
+package com.zanxiang.game.back.serve.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zanxiang.game.back.serve.pojo.dto.GameTencentCallbackDTO;
+import com.zanxiang.game.back.serve.pojo.entity.GameTencentMiniGameCallback;
+
+public interface IGameTencentMiniGameCallbackService extends IService<GameTencentMiniGameCallback> {
+
+    boolean callback(GameTencentCallbackDTO dto);
+}

+ 61 - 0
game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/service/impl/GameOceanengineCallbackServiceImpl.java

@@ -0,0 +1,61 @@
+package com.zanxiang.game.back.serve.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zanxiang.game.back.serve.dao.mapper.GameOceanengineCallbackMapper;
+import com.zanxiang.game.back.serve.pojo.dto.GameOceanengineCallbackDTO;
+import com.zanxiang.game.back.serve.pojo.entity.GameOceanengineCallback;
+import com.zanxiang.game.back.serve.service.IGameOceanengineCallbackService;
+import com.zanxiang.game.module.base.ServerInfo;
+import com.zanxiang.game.module.base.pojo.vo.AgentRpcVO;
+import com.zanxiang.game.module.base.rpc.IAgentRpc;
+import com.zanxiang.module.util.DateUtil;
+import com.zanxiang.module.util.JsonUtil;
+import com.zanxiang.module.util.bean.BeanUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.apache.kafka.clients.producer.KafkaProducer;
+import org.apache.kafka.clients.producer.ProducerRecord;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+
+@Slf4j
+@Service
+public class GameOceanengineCallbackServiceImpl extends ServiceImpl<GameOceanengineCallbackMapper, GameOceanengineCallback>
+        implements IGameOceanengineCallbackService {
+
+    @Value("${spring.kafka.oceanengineCallbackTopic}")
+    private String oceanengineCallbackTopic;
+
+    @DubboReference(providedBy = ServerInfo.SERVER_DUBBO_NAME)
+    private IAgentRpc agentRpc;
+
+    @Autowired
+    private KafkaProducer<String, String> kafkaProducer;
+
+    @Override
+    public boolean callback(GameOceanengineCallbackDTO dto) {
+        AgentRpcVO agent = agentRpc.getByByteAccountId(dto.getAdvertiserId()).getData();
+        if (agent == null) {
+            log.error("头条-微信小游戏检测链接数据找不到渠道:{}", JsonUtil.toString(dto));
+        }
+        GameOceanengineCallback callback = BeanUtil.copy(dto, GameOceanengineCallback.class);
+        if (agent != null) {
+            callback.setAgentKey(agent.getAgentKey());
+            callback.setGameId(agent.getGameId());
+        } else {
+            callback.setAgentKey("-");
+            callback.setGameId(-1L);
+        }
+        callback.setDay(dto.getTs() == null || dto.getTs() < 1000 ? LocalDate.now() : DateUtil.milliToLocalDateTime(dto.getTs()).toLocalDate());
+        save(callback);
+        try {
+            kafkaProducer.send(new ProducerRecord<>(oceanengineCallbackTopic, dto.getAdvertiserId().toString(), JsonUtil.toString(callback)));
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return true;
+    }
+}

+ 61 - 0
game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/service/impl/GameTencentMiniGameCallbackServiceImpl.java

@@ -0,0 +1,61 @@
+package com.zanxiang.game.back.serve.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zanxiang.game.back.serve.dao.mapper.GameTencentMiniGameCallbackMapper;
+import com.zanxiang.game.back.serve.pojo.dto.GameTencentCallbackDTO;
+import com.zanxiang.game.back.serve.pojo.entity.GameTencentMiniGameCallback;
+import com.zanxiang.game.back.serve.service.IGameTencentMiniGameCallbackService;
+import com.zanxiang.game.module.base.ServerInfo;
+import com.zanxiang.game.module.base.pojo.vo.AgentRpcVO;
+import com.zanxiang.game.module.base.rpc.IAgentRpc;
+import com.zanxiang.module.util.DateUtil;
+import com.zanxiang.module.util.JsonUtil;
+import com.zanxiang.module.util.bean.BeanUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.apache.kafka.clients.producer.KafkaProducer;
+import org.apache.kafka.clients.producer.ProducerRecord;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+
+@Slf4j
+@Service
+public class GameTencentMiniGameCallbackServiceImpl extends ServiceImpl<GameTencentMiniGameCallbackMapper, GameTencentMiniGameCallback>
+        implements IGameTencentMiniGameCallbackService {
+
+    @Value("${spring.kafka.tencentCallbackTopic}")
+    private String tencentCallbackTopic;
+
+    @DubboReference(providedBy = ServerInfo.SERVER_DUBBO_NAME)
+    private IAgentRpc agentRpc;
+
+    @Autowired
+    private KafkaProducer<String, String> kafkaProducer;
+
+    @Override
+    public boolean callback(GameTencentCallbackDTO dto) {
+        AgentRpcVO agent = agentRpc.getByTencentAccountId(dto.getAccountId()).getData();
+        if (agent == null) {
+            log.error("腾讯检测链接数据找不到渠道:{}", JsonUtil.toString(dto));
+        }
+        GameTencentMiniGameCallback callback = BeanUtil.copy(dto, GameTencentMiniGameCallback.class);
+        if (agent != null) {
+            callback.setAgentKey(agent.getAgentKey());
+            callback.setGameId(agent.getGameId());
+        } else {
+            callback.setAgentKey("-");
+            callback.setGameId(-1L);
+        }
+        callback.setDay(dto.getClickTime() == null || dto.getClickTime() < 1000 ? LocalDate.now() : DateUtil.secondToLocalDate(dto.getClickTime()));
+        save(callback);
+        try {
+            kafkaProducer.send(new ProducerRecord<>(tencentCallbackTopic, dto.getAccountId().toString(), JsonUtil.toString(callback)));
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return true;
+    }
+}

+ 19 - 5
game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/service/impl/GameTencentMiniGameOrderServiceImpl.java

@@ -112,7 +112,7 @@ public class GameTencentMiniGameOrderServiceImpl extends ServiceImpl<GameTencent
                 // 此处使用用户最近一次的重新染色时间
                 userLog.getCreateTime(),
                 orderLog.getWechatOpenid(),
-                new TencentMiniGameOrderBackPolicyCheck(this, gameBackPolicy, userLog, orderLog));
+                new TencentMiniGameOrderBackPolicyCheck(this, gameBackPolicy, userLog, orderLog, BackTypeEnum.BACK_API));
         boolean doBack = backInfo.first;
         Long backMoney = backInfo.second;
         String backMsg = backInfo.third;
@@ -189,7 +189,7 @@ public class GameTencentMiniGameOrderServiceImpl extends ServiceImpl<GameTencent
                 // 此处使用用户最近一次的重新染色时间
                 userLog.getCreateTime(),
                 orderLog.getWechatOpenid(),
-                new TencentMiniGameOrderBackPolicyCheck(this, gameBackPolicy, userLog, orderLog));
+                new TencentMiniGameOrderBackPolicyCheck(this, gameBackPolicy, userLog, orderLog, BackTypeEnum.BACK_MEDIA_SDK));
         boolean doBack = backInfo.first;
         Long backMoney = backInfo.second;
         String backMsg = backInfo.third;
@@ -412,15 +412,16 @@ public class GameTencentMiniGameOrderServiceImpl extends ServiceImpl<GameTencent
         private final GameBackPolicy gameBackPolicy;
         private final GameTencentMiniGameUser userLog;
         private final GameTencentMiniGameOrder orderLog;
+        private final BackTypeEnum backTypeEnum;
 
         public TencentMiniGameOrderBackPolicyCheck(IGameTencentMiniGameOrderService gameTencentMiniGameOrderService,
-                                                   GameBackPolicy gameBackPolicy,
-                                                   GameTencentMiniGameUser userLog,
-                                                   GameTencentMiniGameOrder orderLog) {
+                                                   GameBackPolicy gameBackPolicy, GameTencentMiniGameUser userLog,
+                                                   GameTencentMiniGameOrder orderLog, BackTypeEnum backTypeEnum) {
             this.gameTencentMiniGameOrderService = gameTencentMiniGameOrderService;
             this.gameBackPolicy = gameBackPolicy;
             this.orderLog = orderLog;
             this.userLog = userLog;
+            this.backTypeEnum = backTypeEnum;
         }
 
         @Override
@@ -435,6 +436,7 @@ public class GameTencentMiniGameOrderServiceImpl extends ServiceImpl<GameTencent
                         .eq(GameTencentMiniGameOrder::getOrderStatus, OrderStatusEnum.SUCCESS_PAY.getValue())
                         .eq(GameTencentMiniGameOrder::getIsFirstOrder, firstPolicy)
                         .ne(GameTencentMiniGameOrder::getOrderId, orderLog.getOrderId())
+                        .eq(GameTencentMiniGameOrder::getBackType, backTypeEnum.getBackType())
                         .orderByDesc(GameTencentMiniGameOrder::getCreateTime)
                         .last("limit " + numberOfRound)
                 ).stream().filter(log -> log.getBackStatus().equals(BackStatusEnum.SUCCESS.getBackStatus())).count();
@@ -448,6 +450,7 @@ public class GameTencentMiniGameOrderServiceImpl extends ServiceImpl<GameTencent
                         .apply(firstPolicy, "date(recharge_time) = date(pay_time)")
                         .apply(!firstPolicy, "date(recharge_time) != date(pay_time)")
                         .ne(GameTencentMiniGameOrder::getOrderId, orderLog.getOrderId())
+                        .eq(GameTencentMiniGameOrder::getBackType, backTypeEnum.getBackType())
                         .orderByDesc(GameTencentMiniGameOrder::getCreateTime)
                         .last("limit " + numberOfRound)
                 ).stream().filter(log -> log.getBackStatus().equals(BackStatusEnum.SUCCESS.getBackStatus())).count();
@@ -461,6 +464,7 @@ public class GameTencentMiniGameOrderServiceImpl extends ServiceImpl<GameTencent
                         .apply(firstPolicy, "TIMESTAMPDIFF(HOUR, recharge_time, pay_time) < 24")
                         .apply(!firstPolicy, "TIMESTAMPDIFF(HOUR, recharge_time, pay_time) >= 24")
                         .ne(GameTencentMiniGameOrder::getOrderId, orderLog.getOrderId())
+                        .eq(GameTencentMiniGameOrder::getBackType, backTypeEnum.getBackType())
                         .orderByDesc(GameTencentMiniGameOrder::getCreateTime)
                         .last("limit " + numberOfRound)
                 ).stream().filter(log -> log.getBackStatus().equals(BackStatusEnum.SUCCESS.getBackStatus())).count();
@@ -474,6 +478,7 @@ public class GameTencentMiniGameOrderServiceImpl extends ServiceImpl<GameTencent
                         .apply(firstPolicy, "TIMESTAMPDIFF(HOUR, recharge_time, pay_time) < 48")
                         .apply(!firstPolicy, "TIMESTAMPDIFF(HOUR, recharge_time, pay_time) >= 48")
                         .ne(GameTencentMiniGameOrder::getOrderId, orderLog.getOrderId())
+                        .eq(GameTencentMiniGameOrder::getBackType, backTypeEnum.getBackType())
                         .orderByDesc(GameTencentMiniGameOrder::getCreateTime)
                         .last("limit " + numberOfRound)
                 ).stream().filter(log -> log.getBackStatus().equals(BackStatusEnum.SUCCESS.getBackStatus())).count();
@@ -485,6 +490,7 @@ public class GameTencentMiniGameOrderServiceImpl extends ServiceImpl<GameTencent
                         .eq(GameTencentMiniGameOrder::getBackPolicyId, gameBackPolicy.getId())
                         .eq(GameTencentMiniGameOrder::getOrderStatus, OrderStatusEnum.SUCCESS_PAY.getValue())
                         .ne(GameTencentMiniGameOrder::getOrderId, orderLog.getOrderId())
+                        .eq(GameTencentMiniGameOrder::getBackType, backTypeEnum.getBackType())
                         .orderByDesc(GameTencentMiniGameOrder::getCreateTime)
                         .last("limit " + numberOfRound)
                 ).stream().filter(log -> log.getBackStatus().equals(BackStatusEnum.SUCCESS.getBackStatus())).count();
@@ -510,6 +516,7 @@ public class GameTencentMiniGameOrderServiceImpl extends ServiceImpl<GameTencent
                         .eq(GameTencentMiniGameOrder::getWechatOpenid, userId)
                         .eq(GameTencentMiniGameOrder::getIsFirstOrder, firstPolicy)
                         .ne(GameTencentMiniGameOrder::getOrderId, orderLog.getOrderId())
+                        .eq(GameTencentMiniGameOrder::getBackType, backTypeEnum.getBackType())
                 );
             } else if (backUnit == BackUnitEnum.UNIT_DAY) {
                 return gameTencentMiniGameOrderService.count(new LambdaQueryWrapper<GameTencentMiniGameOrder>()
@@ -523,6 +530,7 @@ public class GameTencentMiniGameOrderServiceImpl extends ServiceImpl<GameTencent
                         .apply(firstPolicy, "date(recharge_time) = date(pay_time)")
                         .apply(!firstPolicy, "date(recharge_time) != date(pay_time)")
                         .ne(GameTencentMiniGameOrder::getOrderId, orderLog.getOrderId())
+                        .eq(GameTencentMiniGameOrder::getBackType, backTypeEnum.getBackType())
                 );
             } else if (backUnit == BackUnitEnum.UNIT_TIME_DAY) {
                 return gameTencentMiniGameOrderService.count(new LambdaQueryWrapper<GameTencentMiniGameOrder>()
@@ -536,6 +544,7 @@ public class GameTencentMiniGameOrderServiceImpl extends ServiceImpl<GameTencent
                         .apply(firstPolicy, "TIMESTAMPDIFF(HOUR, recharge_time, pay_time) < 24")
                         .apply(!firstPolicy, "TIMESTAMPDIFF(HOUR, recharge_time, pay_time) >= 24")
                         .ne(GameTencentMiniGameOrder::getOrderId, orderLog.getOrderId())
+                        .eq(GameTencentMiniGameOrder::getBackType, backTypeEnum.getBackType())
                 );
             } else if (backUnit == BackUnitEnum.UNIT_TIME_2DAY) {
                 return gameTencentMiniGameOrderService.count(new LambdaQueryWrapper<GameTencentMiniGameOrder>()
@@ -549,6 +558,7 @@ public class GameTencentMiniGameOrderServiceImpl extends ServiceImpl<GameTencent
                         .apply(firstPolicy, "TIMESTAMPDIFF(HOUR, recharge_time, pay_time) < 48")
                         .apply(!firstPolicy, "TIMESTAMPDIFF(HOUR, recharge_time, pay_time) >= 48")
                         .ne(GameTencentMiniGameOrder::getOrderId, orderLog.getOrderId())
+                        .eq(GameTencentMiniGameOrder::getBackType, backTypeEnum.getBackType())
                 );
             } else if (backUnit == BackUnitEnum.LARGE_AMOUNT) {
                 return gameTencentMiniGameOrderService.count(new LambdaQueryWrapper<GameTencentMiniGameOrder>()
@@ -560,6 +570,7 @@ public class GameTencentMiniGameOrderServiceImpl extends ServiceImpl<GameTencent
                         .eq(GameTencentMiniGameOrder::getBackStatus, BackStatusEnum.SUCCESS.getBackStatus())
                         .eq(GameTencentMiniGameOrder::getWechatOpenid, userId)
                         .ne(GameTencentMiniGameOrder::getOrderId, orderLog.getOrderId())
+                        .eq(GameTencentMiniGameOrder::getBackType, backTypeEnum.getBackType())
                 );
             } else {
                 throw new RuntimeException("不支持的回传单位[" + backUnit.getValue() + "]");
@@ -577,6 +588,7 @@ public class GameTencentMiniGameOrderServiceImpl extends ServiceImpl<GameTencent
                     .eq(GameTencentMiniGameOrder::getOrderStatus, OrderStatusEnum.SUCCESS_PAY.getValue())
                     .apply("TIMESTAMPDIFF(MINUTE, recharge_time, pay_time) > {0}", markUpTime)
                     .ne(GameTencentMiniGameOrder::getOrderId, orderLog.getOrderId())
+                    .eq(GameTencentMiniGameOrder::getBackType, backTypeEnum.getBackType())
                     .orderByDesc(GameTencentMiniGameOrder::getCreateTime)
                     .last("limit " + numberOfRound)
             ).stream().filter(log -> log.getBackStatus().equals(BackStatusEnum.SUCCESS.getBackStatus())).count();
@@ -594,6 +606,7 @@ public class GameTencentMiniGameOrderServiceImpl extends ServiceImpl<GameTencent
                     .eq(GameTencentMiniGameOrder::getWechatOpenid, userId)
                     .apply("TIMESTAMPDIFF(MINUTE, recharge_time, pay_time) > {0}", markUpTime)
                     .ne(GameTencentMiniGameOrder::getOrderId, orderLog.getOrderId())
+                    .eq(GameTencentMiniGameOrder::getBackType, backTypeEnum.getBackType())
             );
         }
 
@@ -608,6 +621,7 @@ public class GameTencentMiniGameOrderServiceImpl extends ServiceImpl<GameTencent
                     .eq(GameTencentMiniGameOrder::getOrderStatus, OrderStatusEnum.SUCCESS_PAY.getValue())
                     .eq(GameTencentMiniGameOrder::getWechatOpenid, orderLog.getWechatOpenid())
                     .ne(GameTencentMiniGameOrder::getOrderId, orderLog.getOrderId())
+                    .eq(GameTencentMiniGameOrder::getBackType, backTypeEnum.getBackType())
             );
             return ObjectUtil.objToLong(data.get("recharge_money"), 0L);
         }

+ 31 - 3
game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/task/CallbackClearTask.java

@@ -4,10 +4,13 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.zanxiang.game.back.base.ServerInfo;
 import com.zanxiang.game.back.serve.config.NacosDynamicParamConfig;
 import com.zanxiang.game.back.serve.pojo.entity.GameOceanengineAppCallback;
+import com.zanxiang.game.back.serve.pojo.entity.GameOceanengineCallback;
 import com.zanxiang.game.back.serve.pojo.entity.GameTencentAppCallback;
+import com.zanxiang.game.back.serve.pojo.entity.GameTencentMiniGameCallback;
 import com.zanxiang.game.back.serve.service.IGameOceanengineAppCallbackService;
+import com.zanxiang.game.back.serve.service.IGameOceanengineCallbackService;
 import com.zanxiang.game.back.serve.service.IGameTencentAppCallbackService;
-import com.zanxiang.module.redis.service.IDistributedLockComponent;
+import com.zanxiang.game.back.serve.service.IGameTencentMiniGameCallbackService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Scheduled;
@@ -18,17 +21,24 @@ import java.time.LocalDate;
 @Slf4j
 @Component
 public class CallbackClearTask {
+
     private static final String CLEAR_KEY = ServerInfo.SERVER_NAME + ":CALLBACK_DATA_CLEAR";
 
     @Autowired
     private NacosDynamicParamConfig nacosDynamicParamConfig;
-    @Autowired
-    private IDistributedLockComponent distributedLockComponent;
+
     @Autowired
     private IGameOceanengineAppCallbackService gameOceanengineAppCallbackService;
+
     @Autowired
     private IGameTencentAppCallbackService gameTencentAppCallbackService;
 
+    @Autowired
+    private IGameTencentMiniGameCallbackService gameTencentMiniGameCallbackService;
+
+    @Autowired
+    private IGameOceanengineCallbackService gameOceanengineCallbackService;
+
     /**
      * 每天凌晨 2点清理一次 15天前的监测链接数据
      */
@@ -36,6 +46,7 @@ public class CallbackClearTask {
     public void execute() {
         log.error("开始清理监测链接的日志");
         try {
+            //头条-APP
             gameOceanengineAppCallbackService.remove(new LambdaQueryWrapper<GameOceanengineAppCallback>()
                     .lt(GameOceanengineAppCallback::getDay, LocalDate.now().minusDays(nacosDynamicParamConfig.getCallbackClearDay()))
             );
@@ -43,11 +54,28 @@ public class CallbackClearTask {
             log.error(e.getMessage(), e);
         }
         try {
+            //腾讯-APP
             gameTencentAppCallbackService.remove(new LambdaQueryWrapper<GameTencentAppCallback>()
                     .lt(GameTencentAppCallback::getDay, LocalDate.now().minusDays(nacosDynamicParamConfig.getCallbackClearDay()))
             );
         } catch (Exception e) {
             log.error(e.getMessage(), e);
         }
+        try {
+            //头条-微信小游戏
+            gameOceanengineCallbackService.remove(new LambdaQueryWrapper<GameOceanengineCallback>()
+                    .lt(GameOceanengineCallback::getDay, LocalDate.now().minusDays(nacosDynamicParamConfig.getCallbackClearDay()))
+            );
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        try {
+            //腾讯-微信小游戏
+            gameTencentMiniGameCallbackService.remove(new LambdaQueryWrapper<GameTencentMiniGameCallback>()
+                    .lt(GameTencentMiniGameCallback::getDay, LocalDate.now().minusDays(nacosDynamicParamConfig.getCallbackClearDay()))
+            );
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
     }
 }

+ 4 - 0
game-back/game-back-serve/src/main/java/com/zanxiang/game/back/serve/task/TencentMiniOrderSplitBackTask.java

@@ -7,6 +7,7 @@ import com.zanxiang.game.back.base.ServerInfo;
 import com.zanxiang.game.back.serve.pojo.entity.GameTencentMiniGameOrder;
 import com.zanxiang.game.back.serve.pojo.entity.GameTencentMiniGameOrderSplitLog;
 import com.zanxiang.game.back.serve.pojo.enums.BackStatusEnum;
+import com.zanxiang.game.back.serve.pojo.enums.BackTypeEnum;
 import com.zanxiang.game.back.serve.service.IGameTencentMiniGameBackLogService;
 import com.zanxiang.game.back.serve.service.IGameTencentMiniGameOrderService;
 import com.zanxiang.game.back.serve.service.IGameTencentMiniGameOrderSplitLogService;
@@ -36,11 +37,13 @@ public class TencentMiniOrderSplitBackTask {
 
     @Autowired
     private IGameTencentMiniGameOrderService gameTencentMiniGameOrderService;
+
     @Autowired
     private IGameTencentMiniGameOrderSplitLogService gameTencentMiniGameOrderSplitLogService;
 
     @Autowired
     private IDistributedLockComponent distributedLockComponent;
+
     @Autowired
     private IGameTencentMiniGameBackLogService gameTencentMiniGameBackLogService;
 
@@ -54,6 +57,7 @@ public class TencentMiniOrderSplitBackTask {
                 .in(GameTencentMiniGameOrderSplitLog::getBackDay, Arrays.asList(LocalDate.now().minusDays(1), LocalDate.now()))
                 .lt(GameTencentMiniGameOrderSplitLog::getBackTime, now.plusMinutes(1))
                 .eq(GameTencentMiniGameOrderSplitLog::getBackStatus, BackStatusEnum.NO.getBackStatus())
+                .eq(GameTencentMiniGameOrderSplitLog::getBackType, BackTypeEnum.BACK_API)
                 .orderByAsc(GameTencentMiniGameOrderSplitLog::getOrderNo)
                 .orderByAsc(GameTencentMiniGameOrderSplitLog::getBackTime)
         );

+ 2 - 2
game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/BanStatusEnum.java

@@ -25,11 +25,11 @@ public enum BanStatusEnum {
     /**
      * 状态
      */
-    private Integer status;
+    private final Integer status;
 
     /**
      * 描述
      */
-    private String describe;
+    private final String describe;
 
 }

+ 2 - 2
game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/CpStatusEnum.java

@@ -29,10 +29,10 @@ public enum CpStatusEnum {
     /**
      * 类型
      */
-    private Integer status;
+    private final Integer status;
 
     /**
      * 描述
      */
-    private String desc;
+    private final String desc;
 }

+ 2 - 2
game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/DeleteEnum.java

@@ -24,10 +24,10 @@ public enum DeleteEnum {
     /**
      *
      */
-    private int code;
+    private final int code;
 
     /**
      * 状态
      */
-    private String name;
+    private final String name;
 }

+ 2 - 2
game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/GameAuthEnum.java

@@ -49,12 +49,12 @@ public enum GameAuthEnum {
     /**
      * 角色
      */
-    private String value;
+    private final String value;
 
     /**
      * 名称
      */
-    private String name;
+    private final String name;
 
     /**
      * 根据值获取枚举对象

+ 3 - 3
game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/GameCategoryEnum.java

@@ -37,17 +37,17 @@ public enum GameCategoryEnum {
     /**
      * 应用id
      */
-    private Long id;
+    private final Long id;
 
     /**
      * 游戏分类key
      */
-    private String categoryKey;
+    private final String categoryKey;
 
     /**
      * 描述
      */
-    private String name;
+    private final String name;
 
     /**
      * 被分类名字

+ 2 - 2
game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/H5SignShowEnum.java

@@ -35,10 +35,10 @@ public enum H5SignShowEnum {
     /**
      * 展示类型
      */
-    private String value;
+    private final String value;
 
     /**
      * 描述
      */
-    private String describe;
+    private final String describe;
 }

+ 2 - 2
game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/H5WeChatControlEnum.java

@@ -30,11 +30,11 @@ public enum H5WeChatControlEnum {
     /**
      * 展示类型
      */
-    private String value;
+    private final String value;
 
     /**
      * 描述
      */
-    private String describe;
+    private final String describe;
 
 }

+ 3 - 3
game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/OsEnum.java

@@ -52,17 +52,17 @@ public enum OsEnum {
     /**
      * 数据类型
      */
-    private Integer osType;
+    private final Integer osType;
 
     /**
      * 平台id
      */
-    private String os;
+    private final String os;
 
     /**
      * 平台名称
      */
-    private String osName;
+    private final String osName;
 
 
     /**

+ 2 - 2
game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/PayApplicationTypeEnum.java

@@ -30,12 +30,12 @@ public enum PayApplicationTypeEnum {
     /**
      * 应用类型
      */
-    private Integer type;
+    private final Integer type;
 
     /**
      * 描述
      */
-    private String describe;
+    private final String describe;
 
     public static String getDescByType(Integer type) {
         PayApplicationTypeEnum[] values = PayApplicationTypeEnum.values();

+ 3 - 3
game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/PayDeviceEnum.java

@@ -56,17 +56,17 @@ public enum PayDeviceEnum {
     /**
      * 支付方式id
      */
-    private Long payDeviceId;
+    private final Long payDeviceId;
 
     /**
      * 支付方式key
      */
-    private String payDeviceKey;
+    private final String payDeviceKey;
 
     /**
      * 支付方式名称
      */
-    private String payDeviceName;
+    private final String payDeviceName;
 
     /**
      * 根据支付方式id获取名称

+ 3 - 3
game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/PayWayEnum.java

@@ -34,17 +34,17 @@ public enum PayWayEnum {
     /**
      * 支付渠道id
      */
-    private Integer payWayId;
+    private final Integer payWayId;
 
     /**
      * 支付渠道key
      */
-    private String payWayKey;
+    private final String payWayKey;
 
     /**
      * 支付渠道名称
      */
-    private String payWayName;
+    private final String payWayName;
 
     /**
      * 根据支付类型id获取名称

+ 2 - 2
game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/ShellControlEnum.java

@@ -40,10 +40,10 @@ public enum ShellControlEnum {
     /**
      * 壳包控制类型
      */
-    private Integer shellControl;
+    private final Integer shellControl;
 
     /**
      * 描述
      */
-    private String name;
+    private final String name;
 }

+ 2 - 2
game-module/game-module-base/src/main/java/com/zanxiang/game/module/base/pojo/enums/StatusEnum.java

@@ -24,10 +24,10 @@ public enum StatusEnum {
     /**
      * 状态
      */
-    private int code;
+    private final int code;
 
     /**
      * 描述
      */
-    private String name;
+    private final String name;
 }

+ 5 - 0
game-module/game-module-manage/src/main/java/com/zanxiang/game/module/manage/constant/RedisKeyConstant.java

@@ -56,4 +56,9 @@ public class RedisKeyConstant {
      * 游戏礼包码线程锁缓存key
      */
     public static final String GAME_GIFT_PACK_LOCK = RedisKeyConstant.REDIS_PREFIX + "game_gift_pack_lock_";
+
+    /**
+     * ip解析任务队列
+     */
+    public static final String IP_DATA_ASSAY_QUEUE = "game_sdk_ip_data_assay_queue_";
 }

+ 5 - 0
game-module/game-module-mybatis/src/main/java/com/zanxiang/game/module/mybatis/entity/Game.java

@@ -168,4 +168,9 @@ public class Game implements Serializable {
      */
     private Boolean apiBackSwitch;
 
+    /**
+     * 关闭充值开关
+     */
+    private Boolean rechargeCloseSwitch;
+
 }

+ 10 - 0
game-module/game-module-mybatis/src/main/java/com/zanxiang/game/module/mybatis/entity/User.java

@@ -201,6 +201,16 @@ public class User implements Serializable {
      */
     private LocalDateTime relationCreateTime;
 
+    /**
+     * 手机号绑定时间
+     */
+    private LocalDateTime mobileBindTime;
+
+    /**
+     * ip解析数据
+     */
+    private String ipData;
+
     /**
      * 获取用户显示手机号
      *

+ 5 - 0
game-module/game-module-mybatis/src/main/java/com/zanxiang/game/module/mybatis/entity/UserLoginLog.java

@@ -73,4 +73,9 @@ public class UserLoginLog implements Serializable {
      * 创建时间
      */
     private LocalDateTime createTime;
+
+    /**
+     * ip解析数据
+     */
+    private String ipData;
 }

+ 10 - 0
game-module/game-module-sdk/pom.xml

@@ -133,6 +133,16 @@
             <artifactId>kafka-clients</artifactId>
             <version>2.2.1</version>
         </dependency>
+        <!--重试机制-->
+        <dependency>
+            <groupId>org.springframework.retry</groupId>
+            <artifactId>spring-retry</artifactId>
+            <version>1.3.3</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-aspects</artifactId>
+        </dependency>
     </dependencies>
 
     <build>

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

@@ -6,6 +6,7 @@ import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.retry.annotation.EnableRetry;
 import org.springframework.scheduling.annotation.EnableScheduling;
 
 /**
@@ -19,11 +20,12 @@ import org.springframework.scheduling.annotation.EnableScheduling;
 @Configuration
 @EnableDubbo
 @EnableScheduling
+@EnableRetry(proxyTargetClass = true)
 public class SDKApplication {
 
     public static void main(String[] args) {
         SpringApplication.run(SDKApplication.class, args);
-        System.out.println("赞象SDK服务启动成功 <腾讯媒体sdk回传优化> ( ´・・)ノ(._.`) \n" +
+        System.out.println("赞象SDK服务启动成功 <新增IP解析任务队列> ( ´・・)ノ(._.`) \n" +
                 " ___________ _   __\n" +
                 "/  ___|  _  \\ | / /\n" +
                 "\\ `--.| | | | |/ / \n" +

+ 5 - 0
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/constant/RedisKeyConstant.java

@@ -91,4 +91,9 @@ public class RedisKeyConstant {
      * 完成新手引导回传去重锁
      */
     public static final String CALL_BACK_TUTORIAL_FINISH_LOCK = RedisKeyConstant.REDIS_PREFIX + "call_back_tutorial_finish_lock_";
+
+    /**
+     * ip解析任务队列
+     */
+    public static final String IP_DATA_ASSAY_QUEUE = "game_sdk_ip_data_assay_queue_";
 }

+ 5 - 6
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/controller/CallBackController.java

@@ -40,9 +40,8 @@ public class CallBackController {
     @GetMapping("/ad/sdk/config")
     @ApiResponses(value = {@ApiResponse(code = 200, message = "成功", response = Map.class)})
     public ResultVO<Map<String, Object>> getAdSdkConfig(UserData userData) {
-        log.error("媒体初始化配置请求 userData : {}", JsonUtil.toString(userData));
         Map<String, Object> adSdkConfigMap = gameService.getAdSdkConfig(userData);
-        log.error("媒体初始化配置返回, map : {}", JsonUtil.toString(adSdkConfigMap));
+        log.error("媒体初始化配置返回, map : {}, userId : {}", JsonUtil.toString(adSdkConfigMap), userData.getUserId());
         return ResultVO.ok(adSdkConfigMap);
     }
 
@@ -51,10 +50,10 @@ public class CallBackController {
     @ApiResponses(value = {@ApiResponse(code = 200, message = "成功", response = Map.class)})
     public ResultVO<Map<String, Object>> callBackJudge(@Validated @RequestBody CallBackControlParam param,
                                                        @ValidLogin UserData userData) {
-        log.error("事件回传判断请求, param : {},  userData : {}", JsonUtil.toString(param), JsonUtil.toString(userData));
+        log.error("事件回传判断请求, param : {},  userId : {}", JsonUtil.toString(param), userData.getUserId());
         Map<String, Object> resultMap = backLogMediaSdkService.callBackJudge(param, userData);
-        log.error("事件回传判断结果, param : {},  userData : {}, resultMap : {}", JsonUtil.toString(param),
-                JsonUtil.toString(userData), JsonUtil.toString(resultMap));
+        log.error("事件回传判断结果, param : {},  userId : {}, resultMap : {}", JsonUtil.toString(param),
+                userData.getUserId(), JsonUtil.toString(resultMap));
         return ResultVO.ok(resultMap);
     }
 
@@ -63,7 +62,7 @@ public class CallBackController {
     @ApiResponses(value = {@ApiResponse(code = 200, message = "成功", response = Boolean.class)})
     public ResultVO<Boolean> addMediaSdkBackLog(@Validated @RequestBody BackLogMediaSdkParam param,
                                                 @ValidLogin UserData userData) {
-        log.error("媒体sdk执行回传日志提交, param : {},  userData : {}", JsonUtil.toString(param), JsonUtil.toString(userData));
+        log.error("媒体sdk执行回传日志提交, param : {},  userId : {}", JsonUtil.toString(param), userData.getUserId());
         return ResultVO.ok(backLogMediaSdkService.addMediaSdkBackLog(param, userData));
     }
 }

+ 28 - 7
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/controller/SmsController.java

@@ -1,9 +1,10 @@
 package com.zanxiang.game.module.sdk.controller;
 
+import com.zanxiang.game.module.sdk.annotation.ValidLogin;
+import com.zanxiang.game.module.sdk.pojo.param.BindPhoneParam;
 import com.zanxiang.game.module.sdk.pojo.param.SmsCheckParam;
 import com.zanxiang.game.module.sdk.pojo.param.SmsSendParam;
 import com.zanxiang.game.module.sdk.pojo.param.UserData;
-import com.zanxiang.game.module.sdk.pojo.vo.UserLoginVO;
 import com.zanxiang.game.module.sdk.service.ISmsService;
 import com.zanxiang.module.util.pojo.ResultVO;
 import io.swagger.annotations.Api;
@@ -12,10 +13,9 @@ import io.swagger.annotations.ApiResponse;
 import io.swagger.annotations.ApiResponses;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.validation.annotation.Validated;
-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 org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
 
 /**
  * @author : lingfeng
@@ -32,15 +32,36 @@ public class SmsController {
 
     @ApiOperation(value = "发送短信")
     @PostMapping("/send/msg")
-    @ApiResponses(value = {@ApiResponse(code = 200, message = "成功", response = UserLoginVO.class)})
+    @ApiResponses(value = {@ApiResponse(code = 200, message = "成功", response = Boolean.class)})
     public ResultVO<Boolean> smsSend(@Validated @RequestBody SmsSendParam smsSendParam, UserData userData) {
         return smsService.smsSend(smsSendParam, userData);
     }
 
     @ApiOperation(value = "验证码校验")
     @PostMapping("/check/msg")
-    @ApiResponses(value = {@ApiResponse(code = 200, message = "成功", response = UserLoginVO.class)})
+    @ApiResponses(value = {@ApiResponse(code = 200, message = "成功", response = Boolean.class)})
     public ResultVO<Boolean> smsCheck(@Validated @RequestBody SmsCheckParam smsCheckParam) {
         return smsService.smsCheck(smsCheckParam);
     }
+
+    @ApiOperation(value = "获取绑定手机号")
+    @GetMapping("/bind/phone")
+    @ApiResponses(value = {@ApiResponse(code = 200, message = "成功", response = Map.class)})
+    public ResultVO<Map<String, Object>> getBindPhone(@ValidLogin UserData userData) {
+        return ResultVO.ok(smsService.getBindPhone(userData));
+    }
+
+    @ApiOperation(value = "绑定手机号发送验证码")
+    @GetMapping("/bind/phone/send")
+    @ApiResponses(value = {@ApiResponse(code = 200, message = "成功", response = Map.class)})
+    public ResultVO<Map<String, Object>> bindPhoneSend(@RequestParam String phone, @ValidLogin UserData userData) {
+        return ResultVO.ok(smsService.bindPhoneSend(phone, userData));
+    }
+
+    @ApiOperation(value = "绑定手机号验证")
+    @PostMapping("/bind/phone/check")
+    @ApiResponses(value = {@ApiResponse(code = 200, message = "成功", response = Map.class)})
+    public ResultVO<Map<String, Object>> bindPhoneCheck(@Validated @RequestBody BindPhoneParam param, @ValidLogin UserData userData) {
+        return ResultVO.ok(smsService.bindPhoneCheck(param, userData));
+    }
 }

+ 2 - 2
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/enums/CallBackEnum.java

@@ -30,10 +30,10 @@ public enum CallBackEnum {
     /**
      * 数据类型
      */
-    private Integer callBackStatus;
+    private final Integer callBackStatus;
 
     /**
      * 描述
      */
-    private String name;
+    private final String name;
 }

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

@@ -40,5 +40,5 @@ public enum CpPushDataEnum {
     /**
      * 枚举值
      */
-    private String value;
+    private final String value;
 }

+ 2 - 2
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/enums/DataTypeEnum.java

@@ -40,10 +40,10 @@ public enum DataTypeEnum {
     /**
      * 数据类型
      */
-    private Integer dateType;
+    private final Integer dateType;
 
     /**
      * 描述
      */
-    private String name;
+    private final String name;
 }

+ 2 - 2
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/enums/DeviceTypeEnum.java

@@ -37,12 +37,12 @@ public enum DeviceTypeEnum {
     /**
      * 设备类型
      */
-    private Integer deviceType;
+    private final Integer deviceType;
 
     /**
      * 描述
      */
-    private String describe;
+    private final String describe;
 
     public static DeviceTypeEnum getByDeviceType(Integer deviceType) {
         for (DeviceTypeEnum deviceTypeEnum : DeviceTypeEnum.values()) {

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

@@ -50,5 +50,5 @@ public enum KafkaEventTrackEnum {
     /**
      * 数据埋点类型
      */
-    private String value;
+    private final String value;
 }

+ 2 - 2
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/enums/LoginTypeEnum.java

@@ -30,10 +30,10 @@ public enum LoginTypeEnum {
     /**
      * 登录类型
      */
-    private Integer loginType;
+    private final Integer loginType;
 
     /**
      * 描述
      */
-    private String name;
+    private final String name;
 }

+ 2 - 2
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/enums/ShellSwitchEnum.java

@@ -30,11 +30,11 @@ public enum ShellSwitchEnum {
     /**
      * 壳包控制
      */
-    private Integer shellSwitch;
+    private final Integer shellSwitch;
 
     /**
      * 描述
      */
-    private String name;
+    private final String name;
 
 }

+ 2 - 2
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/enums/SmsTypeEnum.java

@@ -38,12 +38,12 @@ public enum SmsTypeEnum {
     /**
      * 类型
      */
-    private Integer type;
+    private final Integer type;
 
     /**
      * 描述
      */
-    private String describe;
+    private final String describe;
 
     /**
      * 根据type获取枚举

+ 2 - 2
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/enums/TokenCheckEnum.java

@@ -30,10 +30,10 @@ public enum TokenCheckEnum {
     /**
      * 支付方式
      */
-    private Integer code;
+    private final Integer code;
 
     /**
      * 支付类前缀
      */
-    private String msg;
+    private final String msg;
 }

+ 29 - 0
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/ISmsService.java

@@ -1,11 +1,14 @@
 package com.zanxiang.game.module.sdk.service;
 
 import com.zanxiang.game.module.base.pojo.enums.HttpStatusEnum;
+import com.zanxiang.game.module.sdk.pojo.param.BindPhoneParam;
 import com.zanxiang.game.module.sdk.pojo.param.SmsCheckParam;
 import com.zanxiang.game.module.sdk.pojo.param.SmsSendParam;
 import com.zanxiang.game.module.sdk.pojo.param.UserData;
 import com.zanxiang.module.util.pojo.ResultVO;
 
+import java.util.Map;
+
 /**
  * @author : lingfeng
  * @time : 2022-06-13
@@ -13,6 +16,32 @@ import com.zanxiang.module.util.pojo.ResultVO;
  */
 public interface ISmsService {
 
+    /**
+     * 获取用户绑定的手机号
+     *
+     * @param userData : 用户信息
+     * @return : 返回是否绑定手机号
+     */
+    Map<String, Object> getBindPhone(UserData userData);
+
+    /**
+     * 绑定手机号发送验证码
+     *
+     * @param phone    : 手机号
+     * @param userData : 用户信息
+     * @return : 返回发送结果
+     */
+    Map<String, Object> bindPhoneSend(String phone, UserData userData);
+
+    /**
+     * 绑定手机号
+     *
+     * @param param    : 手机号验证码信息
+     * @param userData : 用户信息
+     * @return : 返回绑定结果
+     */
+    Map<String, Object> bindPhoneCheck(BindPhoneParam param, UserData userData);
+
     /**
      * 短信发送
      *

+ 55 - 0
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/api/CpApiService.java

@@ -0,0 +1,55 @@
+package com.zanxiang.game.module.sdk.service.api;
+
+import com.github.sd4324530.jtuple.Tuple2;
+import com.zanxiang.game.module.sdk.pojo.result.PushCpResult;
+import com.zanxiang.module.util.JsonUtil;
+import com.zanxiang.module.util.exception.BaseException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.retry.annotation.Backoff;
+import org.springframework.retry.annotation.Retryable;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Objects;
+
+/**
+ * @author : lingfeng
+ * @time : 2025-01-07
+ * @description : CP方充值接口调用
+ */
+@Slf4j
+@Service
+public class CpApiService {
+
+    @Autowired
+    private RestTemplate restTemplate;
+
+    /**
+     * 回调CP充值接口
+     *
+     * @param url : 请求链接
+     * @return : 返回充值结果以及CP返回结果
+     */
+    @Retryable(value = {BaseException.class}, maxAttempts = 4, backoff = @Backoff(delay = 5000, multiplier = 2))
+    public Tuple2<Boolean, String> callCpRecharge(String url) {
+        String resultStr;
+        try {
+            resultStr = restTemplate.getForObject(url, String.class);
+        } catch (Exception e) {
+            log.error("回调CP充值接口异常, url : {}, e : {}", url, e.getMessage());
+            throw new BaseException("回调CP充值接口异常");
+        }
+        PushCpResult pushCpResult = JsonUtil.toObj(resultStr, PushCpResult.class);
+        if (pushCpResult == null) {
+            log.error("CP返回充值结果为空, url : {}, result is null", url);
+            throw new BaseException("CP返回充值结果为空");
+        }
+        boolean pushSuccess = Objects.equals(pushCpResult.getResult(), PushCpResult.SUCCESS_CODE)
+                || Objects.equals(pushCpResult.getCode(), PushCpResult.SUCCESS_CODE);
+        if (pushSuccess) {
+            return Tuple2.with(Boolean.TRUE, resultStr);
+        }
+        throw new BaseException("CP返回充值失败");
+    }
+}

+ 44 - 12
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/GameBackLogMediaSdkServiceImpl.java

@@ -15,6 +15,7 @@ import com.zanxiang.game.back.base.rpc.ITencentMiniGameBackRpc;
 import com.zanxiang.game.back.base.rpc.ITtAppBackRpc;
 import com.zanxiang.game.module.base.pojo.enums.AccountTypeEnum;
 import com.zanxiang.game.module.base.pojo.enums.GameCategoryEnum;
+import com.zanxiang.game.module.base.pojo.enums.PayDeviceEnum;
 import com.zanxiang.game.module.mybatis.entity.*;
 import com.zanxiang.game.module.mybatis.mapper.GameBackLogMediaSdkMapper;
 import com.zanxiang.game.module.sdk.constant.RedisKeyConstant;
@@ -107,7 +108,7 @@ public class GameBackLogMediaSdkServiceImpl extends ServiceImpl<GameBackLogMedia
                 return resultMap;
             }
             //玩家渠道信息回传检测
-            Tuple2<Boolean, Agent> userAgentCheckTuple2 = this.userAgentCheck(user, resultMap);
+            Tuple2<Boolean, Agent> userAgentCheckTuple2 = this.userAgentCheck(user, resultMap, param);
             if (!userAgentCheckTuple2.first) {
                 return resultMap;
             }
@@ -257,27 +258,42 @@ public class GameBackLogMediaSdkServiceImpl extends ServiceImpl<GameBackLogMedia
         return true;
     }
 
-    private Tuple2<Boolean, Agent> userAgentCheck(User user, Map<String, Object> resultMap) {
-        //判断玩家是否为自然量或者被分享用户, 是则不执行回传
-        if (Objects.equals(user.getAgentId(), Agent.DEFAULT_AGENT) || user.getShareUserId() != null) {
+    private Tuple2<Boolean, Agent> userAgentCheck(User user, Map<String, Object> resultMap, CallBackControlParam param) {
+        //玩家渠道
+        Agent agent = agentService.getById(user.getAgentId());
+        //买量用户, 返回执行回传, 且返回渠道信息
+        if (agent != null && user.getShareUserId() == null) {
+            return Tuple2.with(Boolean.TRUE, agent);
+        }
+        //查询游戏
+        Game game = gameService.getById(user.getGameId());
+        //非买量, 安卓APP, 不回传
+        if (Objects.equals(game.getCategory(), GameCategoryEnum.CATEGORY_APP.getId())) {
             resultMap.put("backMsg", "玩家属于自然量或者被分享用户, 不回传");
             return Tuple2.with(Boolean.FALSE, null);
         }
-        //判断玩家是否存在渠道, 不存在渠道则不回传
-        Agent agent = agentService.getById(user.getAgentId());
-        if (agent == null) {
-            resultMap.put("backMsg", "根据玩家渠道id查询渠道信息为空, 无法执行回传");
+        //非买量, 新手引导无法回传
+        if (Objects.equals(param.getCallBackTypeEnum(), CallBackTypeEnum.CALL_BACK_TUTORIAL_FINISH)) {
+            resultMap.put("backMsg", "非买量, 新手引导回传无法判定, 不回传");
             return Tuple2.with(Boolean.FALSE, null);
         }
-        //返回执行回传, 且返回渠道信息
-        return Tuple2.with(Boolean.TRUE, agent);
+        //非买量, 订单回传, 不继续判断, 直接回传
+        if (Objects.equals(param.getCallBackTypeEnum(), CallBackTypeEnum.CALL_BACK_PAY_ORDER)) {
+            resultMap.put("callBack", Boolean.TRUE);
+            resultMap.put("backMsg", "微信小游戏自然量订单全量回传");
+            PlatformOrderDTO platformOrderDTO = orderService.getByOrderId(param.getOrderId());
+            resultMap.put("amount", platformOrderDTO.getAmount().longValue() * 100);
+            return Tuple2.with(Boolean.FALSE, null);
+        }
+        //其他回传, 还是走判断
+        return Tuple2.with(Boolean.TRUE, null);
     }
 
     private void checkCallBack(User user, Agent agent, Map<String, Object> resultMap, CallBackControlParam param) {
         //玩家id
         Long userId = user.getId();
         //游戏id
-        Long gameId = agent.getGameId();
+        Long gameId = user.getGameId();
         //角色id
         String roleId = param.getRoleId();
         //角色等级
@@ -484,7 +500,7 @@ public class GameBackLogMediaSdkServiceImpl extends ServiceImpl<GameBackLogMedia
             doBack = orderBackQueryRpcVO.getDoBack();
             backMsg = orderBackQueryRpcVO.getBackMsg();
         }
-        //腾讯小游戏媒体SDK回传
+        //腾讯小游戏投腾讯广告
         if (Objects.equals(agent.getAccountType(), AccountTypeEnum.TENCENT_MINI_GAME.getValue())) {
             TencentMiniGameOrderBackQueryRpcDTO orderQuery = this.transform(user.getOpenId(), orderId, agent);
             OrderBackQueryRpcVO orderBackQueryRpcVO = tencentMiniGameBackRpc.orderBackQuery(orderQuery).getData();
@@ -493,6 +509,22 @@ public class GameBackLogMediaSdkServiceImpl extends ServiceImpl<GameBackLogMedia
             }
             doBack = orderBackQueryRpcVO.getDoBack();
             backMsg = orderBackQueryRpcVO.getBackMsg();
+            //米大师支付会被监听, 漏单修正, 直接更改结果为回传
+            if (Objects.equals(orderBackQueryRpcVO.getDoBack(), Boolean.FALSE)) {
+                PlatformOrderDTO platformOrderDTO = orderService.getByOrderId(orderId);
+                if (Objects.equals(platformOrderDTO.getPayDeviceId(), PayDeviceEnum.MI_PAY.getPayDeviceId())) {
+                    doBack = Boolean.TRUE;
+                    amount = Collections.singletonList(platformOrderDTO.getAmount().longValue() * 100);
+                    backMsg = "米大师漏单, 直接修正为原订单金额回传";
+                }
+            }
+        }
+        //微信小游戏投巨量广告 (坑逼腾讯要其他渠道的订单数据)
+        if (Objects.equals(agent.getAccountType(), AccountTypeEnum.BYTE.getValue())) {
+            PlatformOrderDTO platformOrderDTO = orderService.getByOrderId(orderId);
+            doBack = Boolean.TRUE;
+            amount = Collections.singletonList(platformOrderDTO.getAmount().longValue() * 100);
+            backMsg = "微信小游戏投巨量广告, 订单回传给腾讯媒体SDK";
         }
         return Tuple3.with(doBack, amount, Strings.isBlank(backMsg) ? "未知的渠道投放类型" : backMsg);
     }

+ 33 - 37
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/GameServiceImpl.java

@@ -1,14 +1,10 @@
 package com.zanxiang.game.module.sdk.service.impl;
 
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.github.sd4324530.jtuple.Tuple2;
-import com.zanxiang.game.module.base.pojo.enums.AccountTypeEnum;
-import com.zanxiang.game.module.base.pojo.enums.GameCategoryEnum;
 import com.zanxiang.game.module.mybatis.entity.Agent;
 import com.zanxiang.game.module.mybatis.entity.Game;
 import com.zanxiang.game.module.mybatis.entity.User;
-import com.zanxiang.game.module.mybatis.entity.UserEvent;
 import com.zanxiang.game.module.mybatis.mapper.GameMapper;
 import com.zanxiang.game.module.sdk.pojo.param.UserData;
 import com.zanxiang.game.module.sdk.service.IAgentService;
@@ -23,7 +19,6 @@ import org.springframework.stereotype.Service;
 
 import java.util.Collections;
 import java.util.Map;
-import java.util.Objects;
 
 /**
  * @author : lingfeng
@@ -49,38 +44,39 @@ public class GameServiceImpl extends ServiceImpl<GameMapper, Game> implements IG
         if (Strings.isBlank(game.getAdSdkConfig())) {
             return Collections.singletonMap("adSdk", 0);
         }
-        //判断是否为APP, APP直接返回配置
-        if (Objects.equals(game.getCategory(), GameCategoryEnum.CATEGORY_APP.getId())) {
-            return JsonUtil.toMap(game.getAdSdkConfig(), Map.class, Object.class);
-        }
-        //查询用户信息以及投放渠道
-        Tuple2<User, Agent> userAndAgent = this.getUserAndAgent(userData.getUserId());
-        User user = userAndAgent.first;
-        Agent agent = userAndAgent.second;
-        //渠道判断, 目前只有投腾讯才返回
-        if (agent != null) {
-            //腾讯广告
-            if (Objects.equals(agent.getAccountType(), AccountTypeEnum.TENCENT_MINI_GAME.getValue())) {
-                return JsonUtil.toMap(game.getAdSdkConfig(), Map.class, Object.class);
-            }
-            //头条广告
-            if (Objects.equals(agent.getAccountType(), AccountTypeEnum.BYTE.getValue())) {
-                return Collections.singletonMap("adSdk", 0);
-            }
-        }
-        //用户判断, 是否调试阶段和测试用户
-        if (user != null) {
-            if (Objects.equals(game.getStatus(), 2) || userEventService.count(new LambdaQueryWrapper<UserEvent>()
-                    .eq(UserEvent::getGameId, user.getGameId())
-                    .and(qw -> qw.eq(UserEvent::getUsername, user.getUsername())
-                            .or().eq(UserEvent::getUsername, user.getOpenId())
-                            .or().eq(UserEvent::getMobile, user.getMobile()))
-            ) > 0) {
-                return JsonUtil.toMap(game.getAdSdkConfig(), Map.class, Object.class);
-            }
-        }
-        //默认返回不初始化
-        return Collections.singletonMap("adSdk", 0);
+        return JsonUtil.toMap(game.getAdSdkConfig(), Map.class, Object.class);
+//        //判断是否为APP, APP直接返回配置
+//        if (Objects.equals(game.getCategory(), GameCategoryEnum.CATEGORY_APP.getId())) {
+//            return JsonUtil.toMap(game.getAdSdkConfig(), Map.class, Object.class);
+//        }
+//        //查询用户信息以及投放渠道
+//        Tuple2<User, Agent> userAndAgent = this.getUserAndAgent(userData.getUserId());
+//        User user = userAndAgent.first;
+//        Agent agent = userAndAgent.second;
+//        //渠道判断, 目前只有投腾讯才返回
+//        if (agent != null) {
+//            //腾讯广告
+//            if (Objects.equals(agent.getAccountType(), AccountTypeEnum.TENCENT_MINI_GAME.getValue())) {
+//                return JsonUtil.toMap(game.getAdSdkConfig(), Map.class, Object.class);
+//            }
+//            //头条广告
+//            if (Objects.equals(agent.getAccountType(), AccountTypeEnum.BYTE.getValue())) {
+//                return Collections.singletonMap("adSdk", 0);
+//            }
+//        }
+//        //用户判断, 是否调试阶段和测试用户
+//        if (user != null) {
+//            if (Objects.equals(game.getStatus(), 2) || userEventService.count(new LambdaQueryWrapper<UserEvent>()
+//                    .eq(UserEvent::getGameId, user.getGameId())
+//                    .and(qw -> qw.eq(UserEvent::getUsername, user.getUsername())
+//                            .or().eq(UserEvent::getUsername, user.getOpenId())
+//                            .or().eq(UserEvent::getMobile, user.getMobile()))
+//            ) > 0) {
+//                return JsonUtil.toMap(game.getAdSdkConfig(), Map.class, Object.class);
+//            }
+//        }
+//        //默认返回不初始化
+//        return Collections.singletonMap("adSdk", 0);
     }
 
     private Tuple2<User, Agent> getUserAndAgent(Long userId) {

+ 6 - 0
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/LoginServiceImpl.java

@@ -21,6 +21,7 @@ import com.zanxiang.game.module.sdk.pojo.param.UserData;
 import com.zanxiang.game.module.sdk.pojo.vo.UserLoginVO;
 import com.zanxiang.game.module.sdk.service.*;
 import com.zanxiang.game.module.sdk.service.api.WxApiService;
+import com.zanxiang.game.module.sdk.util.RedisUtil;
 import com.zanxiang.game.module.sdk.util.RegexUtil;
 import com.zanxiang.game.module.sdk.util.RegisterUtil;
 import com.zanxiang.module.redis.service.IDistributedLockComponent;
@@ -51,6 +52,9 @@ import java.util.concurrent.TimeUnit;
 @Service
 public class LoginServiceImpl implements IRegisterLoginService {
 
+    @Autowired
+    private RedisUtil<String> redisUtil;
+
     @Autowired
     private IUserTokenService userTokenService;
 
@@ -327,6 +331,8 @@ public class LoginServiceImpl implements IRegisterLoginService {
         }
         //注册信息埋点数据发送到卡夫卡
         kafkaService.eventTrack(KafkaEventTrackEnum.KAFKA_EVENT_TRACK_REG, JsonUtil.toString(user));
+        //发送到IP解析任务队列
+        redisUtil.addToSet(RedisKeyConstant.IP_DATA_ASSAY_QUEUE, "REG:" + user.getId());
         //返回用户信息
         return user;
     }

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

@@ -63,6 +63,9 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
         Long gameId = userData.getGameId();
         //游戏信息
         Game game = gameService.getById(gameId);
+        if (Objects.equals(game.getRechargeCloseSwitch(), Boolean.TRUE)) {
+            throw new BaseException("游戏已经关闭充值, 禁止操作");
+        }
         //玩家信息
         GameUser gameUser = gameUserService.getOne(new LambdaQueryWrapper<GameUser>()
                 .eq(GameUser::getUserId, userData.getUserId()));
@@ -71,7 +74,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
                 .eq(GameUserRole::getGameId, gameId)
                 .eq(GameUserRole::getUserId, userData.getUserId())
                 .eq(GameUserRole::getRoleId, payParam.getRoleId()));
-        if (game == null || gameUser == null || gameUserRole == null) {
+        if (gameUser == null || gameUserRole == null) {
             log.error("游戏用户信息不全, game : {}, gameUser : {}, gameUserRole : {}", JsonUtil.toString(game),
                     JsonUtil.toString(gameUser), JsonUtil.toString(gameUserRole));
             throw new BaseException("参数错误, 游戏用户信息不全");

+ 54 - 26
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/PerformOrderServiceImpl.java

@@ -2,6 +2,7 @@ package com.zanxiang.game.module.sdk.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.github.sd4324530.jtuple.Tuple2;
 import com.zanxiang.game.module.base.pojo.enums.CpStatusEnum;
 import com.zanxiang.game.module.base.pojo.enums.GameCategoryEnum;
 import com.zanxiang.game.module.base.util.DateUtils;
@@ -12,6 +13,7 @@ import com.zanxiang.game.module.sdk.pojo.dto.PayMerchantDTO;
 import com.zanxiang.game.module.sdk.pojo.dto.PlatformOrderDTO;
 import com.zanxiang.game.module.sdk.pojo.result.PushCpResult;
 import com.zanxiang.game.module.sdk.service.*;
+import com.zanxiang.game.module.sdk.service.api.CpApiService;
 import com.zanxiang.game.module.sdk.util.SignUtil;
 import com.zanxiang.module.util.JsonUtil;
 import com.zanxiang.module.util.URIUtil;
@@ -73,6 +75,9 @@ public class PerformOrderServiceImpl implements IPerformOrderService {
     @Autowired
     private IGameService gameService;
 
+    @Autowired
+    private CpApiService cpApiService;
+
     @Override
     @Transactional(rollbackFor = {RuntimeException.class, Exception.class})
     public Boolean pushCp(PlatformOrderDTO orderInfo) {
@@ -90,31 +95,11 @@ public class PerformOrderServiceImpl implements IPerformOrderService {
         Game game = gameService.getById(orderInfo.getGameId());
         //用户信息
         User user = userService.getById(orderInfo.getUserId());
-        //回调CP
-        Map<String, String> map = new HashMap<>(9);
-        //用户存在关联用户id, 且不是微信小程序, 则判定为导量用户, 提交CP原始用户id
-        if (user.getRelationUserId() != null && !Objects.equals(game.getCategory(), GameCategoryEnum.CATEGORY_WX_APPLET.getId())) {
-            map.put("userId", String.valueOf(user.getRelationUserId()));
-        } else {
-            map.put("userId", String.valueOf(orderInfo.getUserId()));
-        }
-        //判断是否设置充值打折
-        if (game.getRechargeRebate() != null) {
-            BigDecimal money = orderInfo.getAmount().multiply(game.getRechargeRebate()).setScale(2, RoundingMode.HALF_UP);
-            map.put("money", money.stripTrailingZeros().toPlainString());
-        } else {
-            map.put("money", orderInfo.getAmount().stripTrailingZeros().toPlainString());
-        }
-        map.put("time", String.valueOf(System.currentTimeMillis()));
-        map.put("serverId", orderInfo.getServerId());
-        map.put("roleId", orderInfo.getRoleId());
-        map.put("roleName", orderInfo.getRoleName());
-        map.put("orderId", orderInfo.getCpOrderId());
-        map.put("ext", URIUtil.encodeURIComponent(orderInfo.getExt()));
-        map.put("sign", this.paySign(orderInfo.getGameId(), map));
-        map.put("orderId2", orderInfo.getOrderId());
-        //url
-        String url = URIUtil.fillUrlParams(cpPaybackUrl, map, false);
+        //回调参数
+        Map<String, String> param = this.payParam(game, user, orderInfo);
+        //拼接请求URI
+        String url = URIUtil.fillUrlParams(cpPaybackUrl, param, false);
+        //调CP充值回调
         String resultStr;
         try {
             resultStr = restTemplate.getForObject(url, String.class);
@@ -125,6 +110,21 @@ public class PerformOrderServiceImpl implements IPerformOrderService {
         PushCpResult pushCpResult = JsonUtil.toObj(resultStr, PushCpResult.class);
         boolean pushSuccess = Objects.equals(pushCpResult.getResult(), PushCpResult.SUCCESS_CODE)
                 || Objects.equals(pushCpResult.getCode(), PushCpResult.SUCCESS_CODE);
+
+        //CP返回充值失败, 进入重试
+        if (!pushSuccess) {
+            try {
+                Tuple2<Boolean, String> tuple2 = cpApiService.callCpRecharge(url);
+                //重试成功, 修改结果, 重试失败, 则打印日志, 将日志给CP方
+                if (tuple2.first) {
+                    pushSuccess = true;
+                    resultStr = tuple2.second;
+                }
+            } catch (Exception e) {
+                log.error("CP充值回调重试失败, url : {}, e : {}", url, e.getMessage());
+            }
+        }
+
         log.error("通知CP支付回调结果, url : {}, resultStr : {}, pushSuccess : {}", url, resultStr, pushSuccess);
         //更新订单信息
         orderService.update(new LambdaUpdateWrapper<Order>()
@@ -140,7 +140,7 @@ public class PerformOrderServiceImpl implements IPerformOrderService {
                 .status(orderInfo.getStatus())
                 .cpStatus(pushSuccess ? CpStatusEnum.SUCCESS.getStatus() : CpStatusEnum.FAIL.getStatus())
                 .cpPaybackUrl(cpPaybackUrl)
-                .params(JsonUtil.toString(map))
+                .params(JsonUtil.toString(param))
                 .ext(orderInfo.getExt())
                 .notifyCnt(orderInfo.getCpNotifyCnt() + 1)
                 .createTime(LocalDateTime.now())
@@ -150,6 +150,34 @@ public class PerformOrderServiceImpl implements IPerformOrderService {
         return pushSuccess;
     }
 
+    private Map<String, String> payParam(Game game, User user, PlatformOrderDTO orderInfo) {
+        Map<String, String> map = new HashMap<>(10);
+        //用户存在关联用户id, 且不是微信小程序, 则判定为导量用户, 提交CP原始用户id
+        if (user.getRelationUserId() != null
+                && !Objects.equals(game.getCategory(), GameCategoryEnum.CATEGORY_WX_APPLET.getId())) {
+            map.put("userId", String.valueOf(user.getRelationUserId()));
+        } else {
+            map.put("userId", String.valueOf(orderInfo.getUserId()));
+        }
+        //判断是否设置充值打折
+        if (game.getRechargeRebate() != null) {
+            BigDecimal money = orderInfo.getAmount().multiply(game.getRechargeRebate())
+                    .setScale(2, RoundingMode.HALF_UP);
+            map.put("money", money.stripTrailingZeros().toPlainString());
+        } else {
+            map.put("money", orderInfo.getAmount().stripTrailingZeros().toPlainString());
+        }
+        map.put("time", String.valueOf(System.currentTimeMillis()));
+        map.put("serverId", orderInfo.getServerId());
+        map.put("roleId", orderInfo.getRoleId());
+        map.put("roleName", orderInfo.getRoleName());
+        map.put("orderId", orderInfo.getCpOrderId());
+        map.put("ext", URIUtil.encodeURIComponent(orderInfo.getExt()));
+        map.put("sign", this.paySign(orderInfo.getGameId(), map));
+        map.put("orderId2", orderInfo.getOrderId());
+        return map;
+    }
+
     private String paySign(Long gameId, Map<String, String> map) {
         //查询游戏
         GameExt gameExt = gameExtService.getByGameId(gameId);

+ 95 - 0
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/SmsServiceImpl.java

@@ -1,26 +1,37 @@
 package com.zanxiang.game.module.sdk.service.impl;
 
 import cn.hutool.core.util.RandomUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.zanxiang.game.module.base.pojo.enums.HttpStatusEnum;
+import com.zanxiang.game.module.mybatis.entity.Game;
+import com.zanxiang.game.module.mybatis.entity.User;
 import com.zanxiang.game.module.sdk.constant.RedisKeyConstant;
 import com.zanxiang.game.module.sdk.enums.ExpireTimeEnum;
 import com.zanxiang.game.module.sdk.enums.SmsTypeEnum;
 import com.zanxiang.game.module.sdk.pojo.dto.UserDTO;
+import com.zanxiang.game.module.sdk.pojo.param.BindPhoneParam;
 import com.zanxiang.game.module.sdk.pojo.param.SmsCheckParam;
 import com.zanxiang.game.module.sdk.pojo.param.SmsSendParam;
 import com.zanxiang.game.module.sdk.pojo.param.UserData;
+import com.zanxiang.game.module.sdk.service.IGameService;
 import com.zanxiang.game.module.sdk.service.ISmsService;
 import com.zanxiang.game.module.sdk.service.IUserService;
 import com.zanxiang.game.module.sdk.util.RedisUtil;
 import com.zanxiang.game.module.sdk.util.RegexUtil;
 import com.zanxiang.module.sms.pojo.SendResult;
 import com.zanxiang.module.sms.service.impl.AliSmsService;
+import com.zanxiang.module.util.exception.BaseException;
 import com.zanxiang.module.util.pojo.ResultVO;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.logging.log4j.util.Strings;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -41,6 +52,90 @@ public class SmsServiceImpl implements ISmsService {
     @Autowired
     private AliSmsService aliSmsService;
 
+    @Autowired
+    private IGameService gameService;
+
+    @Override
+    public Map<String, Object> getBindPhone(UserData userData) {
+        User user = userService.getById(userData.getUserId());
+        if (user == null) {
+            throw new BaseException("参数错误, 用户信息不存在");
+        }
+        Map<String, Object> resultMap = new HashMap<>(3);
+        //未绑定手机号或者绑定时间超过1个月
+        if (Strings.isBlank(user.getMobile()) || user.getMobileBindTime() == null
+                || ChronoUnit.DAYS.between(user.getMobileBindTime(), LocalDateTime.now()) >= 30) {
+            resultMap.put("isBindPhone", Boolean.FALSE);
+            Game game = gameService.getById(user.getGameId());
+            //诸神-攻守CP前端要求未绑定的时候, 返回空串
+            if (Objects.equals(game.getSuperGameId(), 2L)) {
+                resultMap.put("phone", "");
+                resultMap.put("mobileBindTime", "");
+            }
+            return resultMap;
+        }
+        //已绑定手机号
+        resultMap.put("isBindPhone", Boolean.TRUE);
+        resultMap.put("phone", user.getShowPhoneNum());
+        resultMap.put("mobileBindTime", user.getMobileBindTime());
+        return resultMap;
+    }
+
+    @Override
+    public Map<String, Object> bindPhoneSend(String phone, UserData userData) {
+        //验证手机号
+        if (!RegexUtil.checkPhone(phone)) {
+            return this.buildResultMap(Boolean.FALSE, "手机号不合法, 请重新输入");
+        }
+        //校验手机号是否已经绑定了其他账号
+        if (userService.count(new LambdaQueryWrapper<User>()
+                .eq(User::getGameId, userData.getGameId())
+                .eq(User::getMobile, phone)
+        ) > 0) {
+            return this.buildResultMap(Boolean.FALSE, "手机号已被其他账号绑定, 请联系客服");
+        }
+        //验证码缓存key
+        String key = this.smsKey(phone, SmsTypeEnum.SMS_BIND.getType());
+        //判断是否已经发送
+        if (Strings.isNotBlank(redisUtil.getCache(key))) {
+            return this.buildResultMap(Boolean.FALSE, "验证码已经发送, 请勿重复操作");
+        }
+        //发送验证码
+        String randomCode = this.randomCode();
+        SendResult sendResult = aliSmsService.sendCode(randomCode, phone);
+        //发送失败
+        if (!sendResult.isSuccess()) {
+            return this.buildResultMap(Boolean.FALSE, "验证码发送失败, 请稍后重试或者联系客服");
+        }
+        //发送成功设置缓存, 时效5分钟
+        redisUtil.setCache(key, randomCode, ExpireTimeEnum.FIVE_MIN.getTime());
+        return this.buildResultMap(Boolean.TRUE, "发送成功");
+    }
+
+    private Map<String, Object> buildResultMap(Boolean sendResult, String msg) {
+        Map<String, Object> resultMap = new HashMap<>(2);
+        resultMap.put("result", sendResult);
+        resultMap.put("msg", msg);
+        return resultMap;
+    }
+
+    @Override
+    public Map<String, Object> bindPhoneCheck(BindPhoneParam param, UserData userData) {
+        //验证码验证
+        HttpStatusEnum statusEnum = this.smsCheck(SmsTypeEnum.SMS_BIND.getType(), param.getMobile(), param.getCode());
+        if (!Objects.equals(statusEnum, HttpStatusEnum.SUCCESS)) {
+            return this.buildResultMap(Boolean.FALSE, statusEnum.getMsg());
+        }
+        //更新用户手机号
+        userService.update(new LambdaUpdateWrapper<User>()
+                .set(User::getMobile, param.getMobile())
+                .set(User::getMobileBindTime, LocalDateTime.now())
+                .set(User::getUpdateTime, LocalDateTime.now())
+                .eq(User::getId, userData.getUserId()));
+        //返回结果
+        return this.buildResultMap(Boolean.TRUE, "绑定成功");
+    }
+
     @Override
     public ResultVO<Boolean> smsSend(SmsSendParam smsSendParam, UserData userData) {
         //类型

+ 20 - 5
game-module/game-module-sdk/src/main/java/com/zanxiang/game/module/sdk/service/impl/UserLoginLogServiceImpl.java

@@ -3,9 +3,12 @@ package com.zanxiang.game.module.sdk.service.impl;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.zanxiang.game.module.mybatis.entity.UserLoginLog;
 import com.zanxiang.game.module.mybatis.mapper.UserLoginLogMapper;
+import com.zanxiang.game.module.sdk.constant.RedisKeyConstant;
 import com.zanxiang.game.module.sdk.pojo.param.UserData;
 import com.zanxiang.game.module.sdk.service.IUserLoginLogService;
+import com.zanxiang.game.module.sdk.util.RedisUtil;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDateTime;
@@ -19,10 +22,12 @@ import java.time.LocalDateTime;
 @Service
 public class UserLoginLogServiceImpl extends ServiceImpl<UserLoginLogMapper, UserLoginLog> implements IUserLoginLogService {
 
+    @Autowired
+    private RedisUtil<String> redisUtil;
+
     @Override
     public Boolean createRoleLoginLog(UserData userData, String roleId, String roleName, Integer type) {
-        //创建对象
-        return super.save(UserLoginLog.builder()
+        UserLoginLog loginLog = UserLoginLog.builder()
                 .userId(userData.getUserId())
                 .gameId(userData.getGameId())
                 .roleId(roleId)
@@ -32,12 +37,17 @@ public class UserLoginLogServiceImpl extends ServiceImpl<UserLoginLogMapper, Use
                 .deviceType(userData.getDeviceType())
                 .type(type)
                 .createTime(LocalDateTime.now())
-                .build());
+                .build();
+        boolean result = super.save(loginLog);
+        if (result) {
+            redisUtil.addToSet(RedisKeyConstant.IP_DATA_ASSAY_QUEUE, "LOGIN:" + loginLog.getId());
+        }
+        return result;
     }
 
     @Override
     public Boolean createUserLoginLog(UserData userData, Integer type) {
-        return super.save(UserLoginLog.builder()
+        UserLoginLog loginLog = UserLoginLog.builder()
                 .userId(userData.getUserId())
                 .gameId(userData.getGameId())
                 .os(userData.getDeviceSystem())
@@ -45,6 +55,11 @@ public class UserLoginLogServiceImpl extends ServiceImpl<UserLoginLogMapper, Use
                 .deviceType(userData.getDeviceType())
                 .type(type)
                 .createTime(LocalDateTime.now())
-                .build());
+                .build();
+        boolean result = super.save(loginLog);
+        if (result) {
+            redisUtil.addToSet(RedisKeyConstant.IP_DATA_ASSAY_QUEUE, "LOGIN:" + loginLog.getId());
+        }
+        return result;
     }
 }