diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/BindInviteCodeExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/BindInviteCodeExe.java index c2317000..7c389b6d 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/BindInviteCodeExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/BindInviteCodeExe.java @@ -1,5 +1,6 @@ package com.red.circle.other.app.command.activity; +import com.red.circle.other.app.command.user.InviteCodeService; import com.red.circle.other.app.dto.cmd.activity.BindInviteCodeCmd; import com.red.circle.other.domain.gateway.user.UserProfileGateway; import com.red.circle.other.domain.model.user.UserProfile; @@ -28,6 +29,7 @@ public class BindInviteCodeExe { private final UserProfileGateway userProfileGateway; private final UserInviteUserService userInviteUserService; + private final InviteCodeService inviteCodeService; /** * 执行绑定邀请码 @@ -54,8 +56,8 @@ public class BindInviteCodeExe { throw new RuntimeException("The inviter has been bound before and cannot be bound again"); } - // 3. 根据邀请码查找邀请人(邀请码即为用户账号) - UserProfile inviterProfile = userProfileGateway.getByAccount(sysOrigin, inviteCode); + // 3. 根据邀请码查找邀请人 + UserProfile inviterProfile = inviteCodeService.getUserProfileByInviteCode(inviteCode); if (inviterProfile == null) { throw new RuntimeException("The invitation code is invalid and the corresponding user was not found"); } diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/GetMyInviteCodeExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/GetMyInviteCodeExe.java index c12f0ea3..874643ea 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/GetMyInviteCodeExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/activity/GetMyInviteCodeExe.java @@ -1,6 +1,7 @@ package com.red.circle.other.app.command.activity; import com.red.circle.common.business.dto.cmd.AppExtCommand; +import com.red.circle.other.app.command.user.InviteCodeService; import com.red.circle.other.app.dto.clientobject.activity.InviteCodeCO; import com.red.circle.other.domain.gateway.user.UserProfileGateway; import com.red.circle.other.domain.model.user.UserProfile; @@ -26,6 +27,7 @@ public class GetMyInviteCodeExe { private final UserProfileGateway userProfileGateway; private final UserInviteUserService userInviteUserService; + private final InviteCodeService inviteCodeService; /** * 执行获取我的邀请码 @@ -43,9 +45,17 @@ public class GetMyInviteCodeExe { return null; } + // 调用邀请码服务:获取或创建邀请码 + String inviteCode = inviteCodeService.generateInviteCode(userId); + if (inviteCode == null) { + log.error("未能生成邀请码, userId={}", userId); + return null; + } + + // 构造返回对象 InviteCodeCO inviteCodeCO = new InviteCodeCO(); inviteCodeCO.setUserId(userId); - inviteCodeCO.setInviteCode(userProfile.getAccount()); // 使用用户账号作为邀请码 + inviteCodeCO.setInviteCode(inviteCode); // 查询是否已绑定邀请人 List inviteUserList = userInviteUserService.query().eq(UserInviteUser::getUserId, userId).list(); diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/user/InviteCodeService.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/user/InviteCodeService.java new file mode 100644 index 00000000..27b063b1 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/user/InviteCodeService.java @@ -0,0 +1,80 @@ +package com.red.circle.other.app.command.user; + +import com.red.circle.other.app.util.Base36InviteCodeGenerator; +import com.red.circle.other.domain.gateway.user.UserProfileGateway; +import com.red.circle.other.domain.model.user.UserProfile; +import com.red.circle.other.infra.database.rds.dao.user.user.InviteCodeMappingDAO; +import com.red.circle.other.infra.database.rds.entity.user.user.InviteCodeMapping; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class InviteCodeService { + + private final InviteCodeMappingDAO inviteCodeMappingDAO; + private final UserProfileGateway userProfileGateway; + + public String generateInviteCode(Long userId) { + UserProfile profile = userProfileGateway.getByUserId(userId); + if (profile == null) { + log.warn("用户不存在, userId={}", userId); + return null; + } + + // 先查是否已有邀请码 + InviteCodeMapping existing = inviteCodeMappingDAO.selectOneByUserId(userId); + if (existing != null) { + return existing.getInviteCode(); + } + + // 生成邀请码(基于 userId 编码) + String code; + try { + code = Base36InviteCodeGenerator.encode(Long.parseLong(profile.getAccount())); + } catch (IllegalArgumentException e) { + log.error("用户ID过大,无法生成邀请码: {}", userId); + return null; + } + + // 插入映射关系(注意唯一性约束防止重复) + InviteCodeMapping mapping = new InviteCodeMapping(); + mapping.setInviteCode(code); + mapping.setUserId(userId); + mapping.setAccount(profile.getAccount()); + + try { + inviteCodeMappingDAO.insert(mapping); + return code; + } catch (DuplicateKeyException e) { + // 并发情况下可能冲突,重试一次 + log.info("邀请码冲突,重试生成: {}", code); + InviteCodeMapping conflict = inviteCodeMappingDAO.selectByInviteCode(code); + if (conflict != null) { + return conflict.getInviteCode(); + } + throw e; + } + } + + public UserProfile getUserProfileByInviteCode(String inviteCode) { + if (inviteCode == null || inviteCode.isEmpty()) { + return null; + } + inviteCode = inviteCode.trim().toUpperCase(); + + // 查询映射表 + InviteCodeMapping mapping = inviteCodeMappingDAO.selectByInviteCode(inviteCode); + if (mapping == null) { + log.warn("未找到对应的邀请码记录: {}", inviteCode); + return null; + } + + // 返回用户信息 + return userProfileGateway.getByUserId(mapping.getUserId()); + } + +} \ No newline at end of file diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/util/Base36InviteCodeGenerator.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/util/Base36InviteCodeGenerator.java new file mode 100644 index 00000000..8e1749e4 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/util/Base36InviteCodeGenerator.java @@ -0,0 +1,77 @@ +package com.red.circle.other.app.util; + +import java.security.SecureRandom; + +public class Base36InviteCodeGenerator { + + private static final String CHAR_SET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // 36字符 + private static final int CODE_LENGTH = 8; // 固定8位 + private static final SecureRandom RANDOM = new SecureRandom(); + + /** + * 将用户ID编码为8位随机邀请码(Base36) + * 使用用户ID作为随机种子,确保同一ID生成相同邀请码 + */ + public static String encode(long userId) { + if (userId < 0) { + throw new IllegalArgumentException("用户ID不能为负数: " + userId); + } + + // 使用用户ID作为种子初始化随机数生成器,确保同一ID生成相同邀请码 + SecureRandom random = new SecureRandom(); + random.setSeed(userId); + + StringBuilder sb = new StringBuilder(CODE_LENGTH); + + // 第一位不使用0,避免看起来像前导零 + sb.append(CHAR_SET.charAt(1 + random.nextInt(35))); // 1-35,即 1-Z + + // 后续7位随机生成 + for (int i = 1; i < CODE_LENGTH; i++) { + sb.append(CHAR_SET.charAt(random.nextInt(36))); // 0-35,即 0-Z + } + + return sb.toString(); + } + + /** + * 生成完全随机的8位邀请码(不基于用户ID) + */ + public static String generateRandom() { + StringBuilder sb = new StringBuilder(CODE_LENGTH); + + // 第一位不使用0 + sb.append(CHAR_SET.charAt(1 + RANDOM.nextInt(35))); // 1-Z + + // 后续7位随机生成 + for (int i = 1; i < CODE_LENGTH; i++) { + sb.append(CHAR_SET.charAt(RANDOM.nextInt(36))); // 0-Z + } + + return sb.toString(); + } + + // 测试 + public static void main(String[] args) { + System.out.println("=== 基于用户ID的邀请码生成(同一ID生成相同邀请码)==="); + long[] testIds = {1L, 100L, 8826602L, 1000000L, 99999999L}; + + for (long userId : testIds) { + String code1 = encode(userId); + String code2 = encode(userId); // 再次生成,验证是否相同 + System.out.println("ID: " + userId + " -> 邀请码: " + code1 + " (再次生成: " + code2 + ", 相同: " + code1.equals(code2) + ")"); + } + + System.out.println("\n=== 特定ID测试 ==="); + long userId = 8826602L; + String code = encode(userId); + System.out.println("ID " + userId + " -> 邀请码: " + code); + System.out.println("无前导零: " + !code.startsWith("0")); + System.out.println("长度: " + code.length()); + + System.out.println("\n=== 完全随机邀请码生成 ==="); + for (int i = 0; i < 5; i++) { + System.out.println("随机邀请码 " + (i + 1) + ": " + generateRandom()); + } + } +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/dao/user/user/InviteCodeMappingDAO.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/dao/user/user/InviteCodeMappingDAO.java new file mode 100644 index 00000000..2ac56563 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/dao/user/user/InviteCodeMappingDAO.java @@ -0,0 +1,42 @@ +package com.red.circle.other.infra.database.rds.dao.user.user; + +import com.red.circle.framework.mybatis.dao.BaseDAO; +import com.red.circle.other.infra.database.rds.entity.user.user.InviteCodeMapping; +import org.apache.ibatis.annotations.Param; + +/** + *

+ * 邀请码映射关系 Mapper 接口. + *

+ * + * @author generated + * @since 2025-10-21 + */ +public interface InviteCodeMappingDAO extends BaseDAO { + + + /** + * 根据邀请码查询映射记录. + * + * @param inviteCode 邀请码 + * @return 邀请码映射实体 + */ + InviteCodeMapping selectByInviteCode(@Param("inviteCode") String inviteCode); + + /** + * 根据用户ID查询映射记录. + * + * @param userId 用户ID + * @return 邀请码映射实体 + */ + InviteCodeMapping selectOneByUserId(@Param("userId") Long userId); + + /** + * 根据账号查询映射记录. + * + * @param account 用户账号 + * @return 邀请码映射实体 + */ + InviteCodeMapping selectByAccount(@Param("account") String account); + +} \ No newline at end of file diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/entity/user/user/InviteCodeMapping.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/entity/user/user/InviteCodeMapping.java new file mode 100644 index 00000000..8de942ee --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/entity/user/user/InviteCodeMapping.java @@ -0,0 +1,61 @@ +package com.red.circle.other.infra.database.rds.entity.user.user; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.red.circle.framework.mybatis.entity.TimestampBaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.sql.Timestamp; + +/** + *

+ * 邀请码映射关系表. + *

+ * + * @author generated + * @since 2025-10-21 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@TableName("invite_code_mapping") +public class InviteCodeMapping implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键标识. + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + /** + * 邀请码(8位数字+大写字母). + */ + @TableField("invite_code") + private String inviteCode; + + /** + * 用户ID. + */ + @TableField("user_id") + private Long userId; + + /** + * 用户账号(冗余存储,便于查询). + */ + @TableField("account") + private String account; + + /** + * 创建时间 + */ + @TableField("create_time") + private Timestamp createTime; + +} \ No newline at end of file diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/resources/dao/user/user/InviteCodeMappingDAO.xml b/rc-service/rc-service-other/other-infrastructure/src/main/resources/dao/user/user/InviteCodeMappingDAO.xml new file mode 100644 index 00000000..4f916043 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/resources/dao/user/user/InviteCodeMappingDAO.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/rc-service/rc-service-other/other-start/src/test/java/SpringTest.java b/rc-service/rc-service-other/other-start/src/test/java/SpringTest.java new file mode 100644 index 00000000..4cbb0f56 --- /dev/null +++ b/rc-service/rc-service-other/other-start/src/test/java/SpringTest.java @@ -0,0 +1,14 @@ +import com.red.circle.OtherServiceApplication; +import com.red.circle.other.app.scheduler.TeamBillTask; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@SpringBootTest(classes = OtherServiceApplication.class) +@RunWith(SpringRunner.class) +public class SpringTest { + + +}