邀请码功能完善
This commit is contained in:
parent
f63bbc8c65
commit
88ef879b22
@ -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");
|
||||
}
|
||||
|
||||
@ -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<UserInviteUser> inviteUserList = userInviteUserService.query().eq(UserInviteUser::getUserId, userId).list();
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 邀请码映射关系 Mapper 接口.
|
||||
* </p>
|
||||
*
|
||||
* @author generated
|
||||
* @since 2025-10-21
|
||||
*/
|
||||
public interface InviteCodeMappingDAO extends BaseDAO<InviteCodeMapping> {
|
||||
|
||||
|
||||
/**
|
||||
* 根据邀请码查询映射记录.
|
||||
*
|
||||
* @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);
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 邀请码映射关系表.
|
||||
* </p>
|
||||
*
|
||||
* @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;
|
||||
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.red.circle.other.infra.database.rds.dao.user.user.InviteCodeMappingDAO">
|
||||
|
||||
<!-- Result Map -->
|
||||
<resultMap id="InviteCodeMappingResult" type="com.red.circle.other.infra.database.rds.entity.user.user.InviteCodeMapping">
|
||||
<id property="id" column="id"/>
|
||||
<result property="inviteCode" column="invite_code"/>
|
||||
<result property="userId" column="user_id"/>
|
||||
<result property="account" column="account"/>
|
||||
<result property="createTime" column="create_time" />
|
||||
</resultMap>
|
||||
|
||||
<!-- 根据邀请码查询 -->
|
||||
<select id="selectByInviteCode" resultMap="InviteCodeMappingResult">
|
||||
SELECT
|
||||
id,
|
||||
invite_code,
|
||||
user_id,
|
||||
account,
|
||||
create_time
|
||||
FROM invite_code_mapping
|
||||
WHERE invite_code = #{inviteCode}
|
||||
</select>
|
||||
|
||||
<!-- 根据用户ID查询 -->
|
||||
<select id="selectOneByUserId" resultMap="InviteCodeMappingResult">
|
||||
SELECT
|
||||
id,
|
||||
invite_code,
|
||||
user_id,
|
||||
account,
|
||||
create_time
|
||||
FROM invite_code_mapping
|
||||
WHERE user_id = #{userId}
|
||||
</select>
|
||||
|
||||
<!-- 根据账号查询 -->
|
||||
<select id="selectByAccount" resultMap="InviteCodeMappingResult">
|
||||
SELECT
|
||||
id,
|
||||
invite_code,
|
||||
user_id,
|
||||
account,
|
||||
create_time
|
||||
FROM invite_code_mapping
|
||||
WHERE account = #{account}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@ -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 {
|
||||
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user