From 384bce80449b17651937429038c4370dbce60e7b Mon Sep 17 00:00:00 2001
From: liujie <1793218484@qq.com>
Date: 星期二, 13 十月 2026 15:12:22 +0800
Subject: [PATCH] Merge branch 'master' of http://120.76.84.145:10101/gitblit/r/java/Hugray

---
 hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/impl/AccountUserServiceImpl.java |  182 +++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 181 insertions(+), 1 deletions(-)

diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/impl/AccountUserServiceImpl.java b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/impl/AccountUserServiceImpl.java
index 61263a6..47b2cec 100644
--- a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/impl/AccountUserServiceImpl.java
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/impl/AccountUserServiceImpl.java
@@ -2,23 +2,33 @@
 
 import java.util.Collections;
 import java.util.Date;
+import java.security.SecureRandom;
 import java.util.List;
 import java.util.Objects;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import com.ruoyi.common.core.constant.UserConstants;
 import com.ruoyi.common.core.exception.ServiceException;
 import com.ruoyi.common.core.utils.DateUtils;
+import com.ruoyi.common.core.exception.ServiceException;
 import com.ruoyi.common.core.utils.StringUtils;
 import com.ruoyi.common.security.utils.SecurityUtils;
 import com.ruoyi.user.domain.AccountAddress;
+import com.ruoyi.user.config.UniversalCodeProperties;
+import com.ruoyi.user.constant.ReferralConstants;
 import com.ruoyi.user.domain.AccountUser;
 import com.ruoyi.user.domain.vo.AccountUserDetailVo;
 import com.ruoyi.user.domain.vo.MemberOptionVo;
 import com.ruoyi.user.mapper.AccountAddressMapper;
 import com.ruoyi.user.mapper.AccountAgreementMapper;
 import com.ruoyi.user.mapper.AccountCertificationMapper;
+import com.ruoyi.user.domain.vo.AccountUserInfoVo;
+import com.ruoyi.user.domain.vo.EarningSummaryVo;
+import com.ruoyi.user.enums.MemberLevelEnum;
+import com.ruoyi.user.mapper.AccountEarningRecordMapper;
 import com.ruoyi.user.mapper.AccountUserMapper;
 import com.ruoyi.user.mapper.AccountWarehouseMapper;
 import com.ruoyi.user.service.IAccountUserService;
@@ -31,6 +41,8 @@
 @Service
 public class AccountUserServiceImpl implements IAccountUserService
 {
+    private static final Logger log = LoggerFactory.getLogger(AccountUserServiceImpl.class);
+
     @Autowired
     private AccountUserMapper accountUserMapper;
 
@@ -46,10 +58,25 @@
     @Autowired
     private AccountWarehouseMapper accountWarehouseMapper;
 
+    @Autowired
+    private AccountEarningRecordMapper accountEarningRecordMapper;
+
+    @Autowired
+    private UniversalCodeProperties universalCodeProperties;
+
+    /** 推荐码随机源(SecureRandom 线程安全,避免每次创建) */
+    private static final SecureRandom RANDOM = new SecureRandom();
+
     @Override
     public AccountUser selectAccountUserById(Long userId)
     {
         return accountUserMapper.selectAccountUserById(userId);
+    }
+
+    @Override
+    public AccountUser selectAccountUserByPhone(String phone)
+    {
+        return accountUserMapper.selectAccountUserByPhone(phone);
     }
 
     @Override
@@ -90,7 +117,11 @@
     @Override
     public int insertAccountUser(AccountUser accountUser)
     {
-        accountUser.setPassword(SecurityUtils.encryptPassword(accountUser.getPassword()));
+        // C端注册无密码,密码为空时跳过加密,避免 encryptPassword(null) 抛 NPE
+        if (StringUtils.isNotEmpty(accountUser.getPassword()))
+        {
+            accountUser.setPassword(SecurityUtils.encryptPassword(accountUser.getPassword()));
+        }
         return accountUserMapper.insertAccountUser(accountUser);
     }
 
@@ -271,4 +302,153 @@
         }
         return now;
     }
+
+    @Override
+    public AccountUser registerMember(String phone, String referralCode)
+    {
+        // STEP 1: 二次校验手机号未注册(防并发 / 防重复提交,网关侧已用分布式锁兜底)
+        if (StringUtils.isNotNull(accountUserMapper.selectAccountUserByPhone(phone)))
+        {
+            throw new ServiceException("手机号已注册,请前往登录");
+        }
+        // STEP 2: 判定是否命中通用推荐码(命中则不绑定推荐人,且直接发放 L4 会员等级)
+        boolean universalReferral = isUniversalReferralCode(referralCode);
+        // STEP 3: 解析推荐人——命中通用推荐码则不绑定推荐人,否则按码定位并校验
+        Long referrerUserId = resolveReferrerUserId(phone, referralCode, universalReferral);
+        // STEP 4: 生成全局唯一的本人推荐码(库内查重,冲突重试)
+        String myReferralCode = generateUniqueReferralCode();
+        // STEP 5: 组装会员信息(C端无密码,账号=手机号;姓名注册未采集,落库留空,由用户后续在资料页完善)
+        AccountUser accountUser = new AccountUser();
+        accountUser.setUserName(phone);
+        accountUser.setPhone(phone);
+        accountUser.setStatus(UserConstants.NORMAL);
+        accountUser.setMyReferralCode(myReferralCode);
+        accountUser.setReferrerId(referrerUserId);
+        // 通用推荐码注册的会员直接发放 L4 等级;其余不设等级,走库默认值 C(普通用户)
+        if (universalReferral)
+        {
+            accountUser.setSystemLevel(MemberLevelEnum.L4.getCode());
+        }
+        accountUser.setCreateBy(phone);
+        if (insertAccountUser(accountUser) <= 0)
+        {
+            throw new ServiceException("注册失败,请稍后重试");
+        }
+        return accountUser;
+    }
+
+    @Override
+    public AccountUserInfoVo getAccountUserInfo(Long userId)
+    {
+        // STEP 1: 校验会员ID(登录态缺失时直接失败,避免无归属查询)
+        if (StringUtils.isNull(userId))
+        {
+            throw new ServiceException("获取会员信息失败");
+        }
+        // STEP 2: 按会员ID查询会员(逻辑删除已在 SQL 中过滤)
+        AccountUser accountUser = accountUserMapper.selectAccountUserById(userId);
+        if (StringUtils.isNull(accountUser))
+        {
+            throw new ServiceException("获取会员信息失败");
+        }
+        // STEP 3: 查询收益汇总(单位分),无任何流水时直接返回全零,避免前端 null 分支
+        EarningSummaryVo earnings = accountEarningRecordMapper.selectEarningSummary(userId);
+        if (StringUtils.isNull(earnings))
+        {
+            earnings = EarningSummaryVo.zero();
+        }
+        // STEP 4: 组装展示对象;等级为空或非法值统一兜底为 C(普通用户);手机号脱敏后下发
+        AccountUserInfoVo vo = new AccountUserInfoVo();
+        vo.setUserId(accountUser.getUserId());
+        vo.setNickname(accountUser.getNickname());
+        vo.setPhone(maskPhone(accountUser.getPhone()));
+        vo.setAvatar(accountUser.getAvatar());
+        vo.setMemberLevel(MemberLevelEnum.normalize(accountUser.getSystemLevel()));
+        vo.setEarnings(earnings);
+        // STEP 5: 返回个人中心展示数据
+        return vo;
+    }
+
+    /**
+     * 判定是否为通用推荐码
+     *
+     * @param referralCode 注册提交的推荐码
+     * @return true 表示命中通用推荐码
+     */
+    private boolean isUniversalReferralCode(String referralCode)
+    {
+        String universalCode = universalCodeProperties.getUniversalReferralCode();
+        return StringUtils.isNotEmpty(universalCode) && universalCode.equals(referralCode);
+    }
+
+    /**
+     * 解析注册时的推荐人用户ID
+     *
+     * 命中通用推荐码(测试便利)时跳过推荐人查询与校验,返回 null(不绑定推荐人);
+     * 否则按推荐码定位推荐人,不存在或已停用则拒绝注册。
+     *
+     * @param phone 注册手机号(仅用于日志留痕,脱敏输出)
+     * @param referralCode 推荐码
+     * @param universalReferral 是否已命中通用推荐码
+     * @return 推荐人用户ID;命中通用码时为 null
+     */
+    private Long resolveReferrerUserId(String phone, String referralCode, boolean universalReferral)
+    {
+        // 命中通用推荐码:不绑定推荐人
+        if (universalReferral)
+        {
+            log.warn("[REFERRAL] universal code hit, not binding referrer, phone={}", maskPhone(phone));
+            return null;
+        }
+        // 按推荐码定位推荐人,不存在或已停用则拒绝注册
+        AccountUser referrer = accountUserMapper.selectAccountUserByReferralCode(referralCode);
+        if (StringUtils.isNull(referrer))
+        {
+            throw new ServiceException("推荐人码无效");
+        }
+        if (UserConstants.USER_DISABLE.equals(referrer.getStatus()))
+        {
+            throw new ServiceException("推荐人账号异常");
+        }
+        return referrer.getUserId();
+    }
+
+    /**
+     * 手机号脱敏,形如 138****0000
+     *
+     * @param phone 手机号
+     * @return 脱敏后的手机号
+     */
+    private String maskPhone(String phone)
+    {
+        if (StringUtils.isEmpty(phone) || phone.length() < 7)
+        {
+            return phone;
+        }
+        return phone.substring(0, 3) + "****" + phone.substring(phone.length() - 4);
+    }
+
+    /**
+     * 生成库内唯一的推荐码
+     *
+     * @return 8 位大写字母数字推荐码
+     */
+    private String generateUniqueReferralCode()
+    {
+        String chars = ReferralConstants.CODE_CHARS;
+        for (int retry = 0; retry < ReferralConstants.GEN_MAX_RETRY; retry++)
+        {
+            StringBuilder sb = new StringBuilder(ReferralConstants.CODE_LENGTH);
+            for (int i = 0; i < ReferralConstants.CODE_LENGTH; i++)
+            {
+                sb.append(chars.charAt(RANDOM.nextInt(chars.length())));
+            }
+            String code = sb.toString();
+            if (StringUtils.isNull(accountUserMapper.selectAccountUserByReferralCode(code)))
+            {
+                return code;
+            }
+        }
+        throw new ServiceException("注册失败,请稍后重试");
+    }
 }

--
Gitblit v1.7.1