|
@@ -51,6 +51,7 @@ public class InviteService {
|
|
|
private final CrawlerUsageLogMapper crawlerUsageLogMapper;
|
|
private final CrawlerUsageLogMapper crawlerUsageLogMapper;
|
|
|
private final com.xuekairui.user.service.CrawlerService crawlerService;
|
|
private final com.xuekairui.user.service.CrawlerService crawlerService;
|
|
|
private final com.xuekairui.user.service.BusinessLicenseService businessLicenseService;
|
|
private final com.xuekairui.user.service.BusinessLicenseService businessLicenseService;
|
|
|
|
|
+ private final com.xuekairui.user.service.WechatMiniProgramService wechatMiniProgramService;
|
|
|
|
|
|
|
|
/** 活动模块可选依赖:未加载时跳过活动相关发奖 */
|
|
/** 活动模块可选依赖:未加载时跳过活动相关发奖 */
|
|
|
@Autowired(required = false)
|
|
@Autowired(required = false)
|
|
@@ -83,7 +84,7 @@ public class InviteService {
|
|
|
/**
|
|
/**
|
|
|
* 获取或创建用户的邀请码
|
|
* 获取或创建用户的邀请码
|
|
|
*/
|
|
*/
|
|
|
- public InviteCodeResponse getOrCreateInviteCode(Long userId) {
|
|
|
|
|
|
|
+ public InviteCodeResponse getOrCreateInviteCode(Long userId, String loginSource) {
|
|
|
// 查找用户已有的有效邀请码
|
|
// 查找用户已有的有效邀请码
|
|
|
InviteCode existingCode = inviteCodeMapper.selectOne(
|
|
InviteCode existingCode = inviteCodeMapper.selectOne(
|
|
|
new LambdaQueryWrapper<InviteCode>()
|
|
new LambdaQueryWrapper<InviteCode>()
|
|
@@ -95,7 +96,7 @@ public class InviteService {
|
|
|
.last("LIMIT 1"));
|
|
.last("LIMIT 1"));
|
|
|
|
|
|
|
|
if (existingCode != null) {
|
|
if (existingCode != null) {
|
|
|
- return toCodeResponse(existingCode);
|
|
|
|
|
|
|
+ return toCodeResponse(existingCode, loginSource);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 创建新邀请码(uk_code 唯一索引兜底防止并发碰撞)
|
|
// 创建新邀请码(uk_code 唯一索引兜底防止并发碰撞)
|
|
@@ -116,7 +117,7 @@ public class InviteService {
|
|
|
try {
|
|
try {
|
|
|
inviteCodeMapper.insert(newCode);
|
|
inviteCodeMapper.insert(newCode);
|
|
|
log.info("用户 {} 创建邀请码: {}", userId, code);
|
|
log.info("用户 {} 创建邀请码: {}", userId, code);
|
|
|
- return toCodeResponse(newCode);
|
|
|
|
|
|
|
+ return toCodeResponse(newCode, loginSource);
|
|
|
} catch (org.springframework.dao.DuplicateKeyException e) {
|
|
} catch (org.springframework.dao.DuplicateKeyException e) {
|
|
|
log.warn("邀请码并发碰撞(attempt={}): {}", attempt + 1, code);
|
|
log.warn("邀请码并发碰撞(attempt={}): {}", attempt + 1, code);
|
|
|
if (attempt == maxInsertRetries - 1) {
|
|
if (attempt == maxInsertRetries - 1) {
|
|
@@ -140,6 +141,7 @@ public class InviteService {
|
|
|
* @param channel 渠道(可选,默认app)
|
|
* @param channel 渠道(可选,默认app)
|
|
|
*/
|
|
*/
|
|
|
public InvitePageResponse getInvitePage(String code, String channel) {
|
|
public InvitePageResponse getInvitePage(String code, String channel) {
|
|
|
|
|
+ log.info("[getInvitePage] code={}, channel={}", code, channel);
|
|
|
InviteCode inviteCode = inviteCodeMapper.selectOne(
|
|
InviteCode inviteCode = inviteCodeMapper.selectOne(
|
|
|
new LambdaQueryWrapper<InviteCode>()
|
|
new LambdaQueryWrapper<InviteCode>()
|
|
|
.eq(InviteCode::getCode, code));
|
|
.eq(InviteCode::getCode, code));
|
|
@@ -171,7 +173,21 @@ public class InviteService {
|
|
|
|
|
|
|
|
// 构建小程序路径(含邀请码参数)
|
|
// 构建小程序路径(含邀请码参数)
|
|
|
String miniappPath = config.getMiniappPath();
|
|
String miniappPath = config.getMiniappPath();
|
|
|
- if (miniappPath != null && !miniappPath.contains("?")) {
|
|
|
|
|
|
|
+ String mpLink = null;
|
|
|
|
|
+ if ("miniapp".equals(channel) && miniappPath != null && !miniappPath.isBlank()) {
|
|
|
|
|
+ String query = "inviteCode=" + code;
|
|
|
|
|
+ mpLink = wechatMiniProgramService.generateMpLink(miniappPath, query);
|
|
|
|
|
+ if (mpLink != null) {
|
|
|
|
|
+ log.info("[getInvitePage] 落地页生成小程序 mp:// 链接成功: code={}, mpLink={}", code, mpLink);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (!miniappPath.contains("?")) {
|
|
|
|
|
+ miniappPath = miniappPath + "?inviteCode=" + code;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ miniappPath = miniappPath + "&inviteCode=" + code;
|
|
|
|
|
+ }
|
|
|
|
|
+ log.warn("[getInvitePage] 落地页生成小程序 mp:// 链接失败,降级为普通路径: code={}", code);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (miniappPath != null && !miniappPath.contains("?")) {
|
|
|
miniappPath = miniappPath + "?inviteCode=" + code;
|
|
miniappPath = miniappPath + "?inviteCode=" + code;
|
|
|
} else if (miniappPath != null) {
|
|
} else if (miniappPath != null) {
|
|
|
miniappPath = miniappPath + "&inviteCode=" + code;
|
|
miniappPath = miniappPath + "&inviteCode=" + code;
|
|
@@ -225,6 +241,7 @@ public class InviteService {
|
|
|
// 渠道特定字段
|
|
// 渠道特定字段
|
|
|
.appDownloadUrl(appDownloadUrl)
|
|
.appDownloadUrl(appDownloadUrl)
|
|
|
.miniappPath(miniappPath)
|
|
.miniappPath(miniappPath)
|
|
|
|
|
+ .mpLink(mpLink)
|
|
|
.miniappAppId(config.getMiniappAppId())
|
|
.miniappAppId(config.getMiniappAppId())
|
|
|
.wechatRedirectUrl(config.getWechatRedirectUrl())
|
|
.wechatRedirectUrl(config.getWechatRedirectUrl())
|
|
|
.dingtalkAppId(config.getDingtalkAppId())
|
|
.dingtalkAppId(config.getDingtalkAppId())
|
|
@@ -718,11 +735,11 @@ public class InviteService {
|
|
|
* 获取用户邀请统计
|
|
* 获取用户邀请统计
|
|
|
* 返回如:"已邀请5人,3人注册 +21次"
|
|
* 返回如:"已邀请5人,3人注册 +21次"
|
|
|
*/
|
|
*/
|
|
|
- public InviteStatsResponse getInviteStats(Long userId) {
|
|
|
|
|
|
|
+ public InviteStatsResponse getInviteStats(Long userId, String loginSource) {
|
|
|
InviteConfig config = inviteConfigService.getActiveConfig();
|
|
InviteConfig config = inviteConfigService.getActiveConfig();
|
|
|
|
|
|
|
|
// 获取用户邀请码
|
|
// 获取用户邀请码
|
|
|
- InviteCodeResponse codeResponse = getOrCreateInviteCode(userId);
|
|
|
|
|
|
|
+ InviteCodeResponse codeResponse = getOrCreateInviteCode(userId, loginSource);
|
|
|
|
|
|
|
|
// 所有邀请记录
|
|
// 所有邀请记录
|
|
|
List<InviteRelation> allRelations = inviteRelationMapper.selectList(
|
|
List<InviteRelation> allRelations = inviteRelationMapper.selectList(
|
|
@@ -993,15 +1010,44 @@ public class InviteService {
|
|
|
/**
|
|
/**
|
|
|
* 实体转响应(含邀请链接,文案从配置读取)
|
|
* 实体转响应(含邀请链接,文案从配置读取)
|
|
|
*/
|
|
*/
|
|
|
- private InviteCodeResponse toCodeResponse(InviteCode code) {
|
|
|
|
|
|
|
+ private InviteCodeResponse toCodeResponse(InviteCode code, String loginSource) {
|
|
|
|
|
+ log.info("[toCodeResponse] code={}, loginSource={}, userId={}", code.getCode(), loginSource, code.getUserId());
|
|
|
// 获取配置与邀请人信息
|
|
// 获取配置与邀请人信息
|
|
|
InviteConfig config = inviteConfigService.getActiveConfig();
|
|
InviteConfig config = inviteConfigService.getActiveConfig();
|
|
|
User inviter = userMapper.selectById(code.getUserId());
|
|
User inviter = userMapper.selectById(code.getUserId());
|
|
|
String inviterNickname = getDisplayName(inviter);
|
|
String inviterNickname = getDisplayName(inviter);
|
|
|
- String inviteLink = inviteBaseUrl + "/api/invite/" + code.getCode();
|
|
|
|
|
String appName = config.getAppName() != null ? config.getAppName() : "智价云(药店版)";
|
|
String appName = config.getAppName() != null ? config.getAppName() : "智价云(药店版)";
|
|
|
int rewardMonths = config.getRewardMonths() != null ? config.getRewardMonths() : 1;
|
|
int rewardMonths = config.getRewardMonths() != null ? config.getRewardMonths() : 1;
|
|
|
|
|
|
|
|
|
|
+ // 根据登录来源生成不同的邀请链接
|
|
|
|
|
+ String inviteLink;
|
|
|
|
|
+ if ("MINIAPP".equals(loginSource)) {
|
|
|
|
|
+ // 小程序来源:调用微信 API 生成 mp:// 链接
|
|
|
|
|
+ String miniappPath = config.getMiniappPath();
|
|
|
|
|
+ if (miniappPath != null && !miniappPath.isBlank()) {
|
|
|
|
|
+ // 构建查询参数(带邀请码)
|
|
|
|
|
+ String query = "inviteCode=" + code.getCode();
|
|
|
|
|
+ // 调用微信 API 生成 mp:// 链接
|
|
|
|
|
+ String mpLink = wechatMiniProgramService.generateMpLink(miniappPath, query);
|
|
|
|
|
+ if (mpLink != null) {
|
|
|
|
|
+ inviteLink = mpLink;
|
|
|
|
|
+ log.info("[toCodeResponse] 生成小程序 mp:// 链接成功: code={}, mpLink={}", code.getCode(), inviteLink);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 生成失败,降级为普通链接
|
|
|
|
|
+ inviteLink = inviteBaseUrl + "/api/invite/" + code.getCode();
|
|
|
|
|
+ log.warn("[toCodeResponse] 生成小程序 mp:// 链接失败,降级为普通链接: code={}", code.getCode());
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 配置不完整,降级为普通链接
|
|
|
|
|
+ inviteLink = inviteBaseUrl + "/api/invite/" + code.getCode();
|
|
|
|
|
+ log.warn("[toCodeResponse] miniappPath 配置为空,降级为普通链接: code={}", code.getCode());
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 其他来源:使用下载链接
|
|
|
|
|
+ inviteLink = inviteBaseUrl + "/api/invite/" + code.getCode();
|
|
|
|
|
+ log.info("[toCodeResponse] 非小程序来源,生成普通链接: code={}, loginSource={}, link={}", code.getCode(), loginSource, inviteLink);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 分享标题:从配置模板读取,支持占位符 {inviter}、{appName}
|
|
// 分享标题:从配置模板读取,支持占位符 {inviter}、{appName}
|
|
|
String shareTitleTemplate = config.getShareTitleTemplate();
|
|
String shareTitleTemplate = config.getShareTitleTemplate();
|
|
|
String shareTitle = (shareTitleTemplate != null && !shareTitleTemplate.isBlank())
|
|
String shareTitle = (shareTitleTemplate != null && !shareTitleTemplate.isBlank())
|
|
@@ -1144,7 +1190,7 @@ public class InviteService {
|
|
|
/**
|
|
/**
|
|
|
* 获取用户邀请转化统计(点击-注册转化率)
|
|
* 获取用户邀请转化统计(点击-注册转化率)
|
|
|
*/
|
|
*/
|
|
|
- public InviteConversionStatsResponse getInviteConversionStats(Long userId, String phone) {
|
|
|
|
|
|
|
+ public InviteConversionStatsResponse getInviteConversionStats(Long userId, String phone, String loginSource) {
|
|
|
if (userId == null && phone != null && !phone.isBlank()) {
|
|
if (userId == null && phone != null && !phone.isBlank()) {
|
|
|
User u = userMapper.selectOne(
|
|
User u = userMapper.selectOne(
|
|
|
new LambdaQueryWrapper<User>().eq(User::getPhone, phone));
|
|
new LambdaQueryWrapper<User>().eq(User::getPhone, phone));
|
|
@@ -1157,7 +1203,7 @@ public class InviteService {
|
|
|
throw new BusinessException(ErrorCode.PARAM_ERROR, "用户ID和手机号至少填写一个");
|
|
throw new BusinessException(ErrorCode.PARAM_ERROR, "用户ID和手机号至少填写一个");
|
|
|
}
|
|
}
|
|
|
// 获取用户邀请码
|
|
// 获取用户邀请码
|
|
|
- InviteCodeResponse codeResponse = getOrCreateInviteCode(userId);
|
|
|
|
|
|
|
+ InviteCodeResponse codeResponse = getOrCreateInviteCode(userId, loginSource);
|
|
|
|
|
|
|
|
// 获取邀请码实体
|
|
// 获取邀请码实体
|
|
|
InviteCode myCode = inviteCodeMapper.selectOne(
|
|
InviteCode myCode = inviteCodeMapper.selectOne(
|