package com.ruoyi.user.service.impl;
|
|
import java.util.ArrayList;
|
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.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.AccountInviteeVo;
|
import com.ruoyi.user.domain.vo.AccountReferrerVo;
|
import com.ruoyi.user.domain.vo.AccountUserDetailVo;
|
import com.ruoyi.user.domain.vo.InviteCodeVo;
|
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;
|
|
/**
|
* 会员用户 业务层实现
|
*
|
* @author ruoyi
|
*/
|
@Service
|
public class AccountUserServiceImpl implements IAccountUserService
|
{
|
private static final Logger log = LoggerFactory.getLogger(AccountUserServiceImpl.class);
|
|
@Autowired
|
private AccountUserMapper accountUserMapper;
|
|
@Autowired
|
private AccountCertificationMapper accountCertificationMapper;
|
|
@Autowired
|
private AccountAddressMapper accountAddressMapper;
|
|
@Autowired
|
private AccountAgreementMapper accountAgreementMapper;
|
|
@Autowired
|
private AccountWarehouseMapper accountWarehouseMapper;
|
|
@Autowired
|
private AccountEarningRecordMapper accountEarningRecordMapper;
|
|
@Autowired
|
private UniversalCodeProperties universalCodeProperties;
|
|
/** 推荐码随机源(SecureRandom 线程安全,避免每次创建) */
|
private static final SecureRandom RANDOM = new SecureRandom();
|
|
/** 头像地址最大长度(对应 account_user.avatar varchar(255),超长直接拒绝避免截断) */
|
private static final int AVATAR_MAX_LENGTH = 255;
|
|
/** 认证状态:未认证(auth_status 为空时的兜底值) */
|
private static final String AUTH_STATUS_NONE = "0";
|
|
@Override
|
public AccountUser selectAccountUserById(Long userId)
|
{
|
return accountUserMapper.selectAccountUserById(userId);
|
}
|
|
@Override
|
public AccountUser selectAccountUserByPhone(String phone)
|
{
|
return accountUserMapper.selectAccountUserByPhone(phone);
|
}
|
|
@Override
|
public List<AccountUser> selectAccountUserList(AccountUser accountUser)
|
{
|
return accountUserMapper.selectAccountUserList(accountUser);
|
}
|
|
@Override
|
public boolean checkUsernameUnique(AccountUser accountUser)
|
{
|
Long userId = StringUtils.isNull(accountUser.getUserId()) ? -1L : accountUser.getUserId();
|
AccountUser info = accountUserMapper.selectAccountUserByUsername(accountUser.getUserName());
|
if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue())
|
{
|
return UserConstants.NOT_UNIQUE;
|
}
|
return UserConstants.UNIQUE;
|
}
|
|
@Override
|
public boolean checkPhoneUnique(AccountUser accountUser)
|
{
|
// 手机号非必填,为空时不做唯一性校验,否则会与库里其它空手机号记录相撞
|
if (StringUtils.isEmpty(accountUser.getPhone()))
|
{
|
return UserConstants.UNIQUE;
|
}
|
Long userId = StringUtils.isNull(accountUser.getUserId()) ? -1L : accountUser.getUserId();
|
AccountUser info = accountUserMapper.selectAccountUserByPhone(accountUser.getPhone());
|
if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue())
|
{
|
return UserConstants.NOT_UNIQUE;
|
}
|
return UserConstants.UNIQUE;
|
}
|
|
@Override
|
public int insertAccountUser(AccountUser accountUser)
|
{
|
// C端注册无密码,密码为空时跳过加密,避免 encryptPassword(null) 抛 NPE
|
if (StringUtils.isNotEmpty(accountUser.getPassword()))
|
{
|
accountUser.setPassword(SecurityUtils.encryptPassword(accountUser.getPassword()));
|
}
|
return accountUserMapper.insertAccountUser(accountUser);
|
}
|
|
@Override
|
public int updateAccountUser(AccountUser accountUser)
|
{
|
// 不为空才加密;为空时保持 null,mapper 的 <if> 会跳过该列,原密码不受影响
|
if (StringUtils.isNotEmpty(accountUser.getPassword()))
|
{
|
accountUser.setPassword(SecurityUtils.encryptPassword(accountUser.getPassword()));
|
}
|
return accountUserMapper.updateAccountUser(accountUser);
|
}
|
|
@Override
|
public int deleteAccountUserByIds(Long[] userIds)
|
{
|
return accountUserMapper.deleteAccountUserByIds(userIds);
|
}
|
|
@Override
|
public AccountUserDetailVo selectAccountUserDetail(Long userId)
|
{
|
AccountUserDetailVo detail = new AccountUserDetailVo();
|
detail.setUser(accountUserMapper.selectAccountUserVoById(userId));
|
detail.setCertification(accountCertificationMapper.selectCertificationByUserId(userId));
|
detail.setAddresses(accountAddressMapper.selectAddressByUserId(userId));
|
detail.setAgreements(accountAgreementMapper.selectAgreementByUserId(userId));
|
detail.setWarehouses(accountWarehouseMapper.selectWarehouseByUserId(userId));
|
return detail;
|
}
|
|
@Override
|
public int updateAccountUserRelation(AccountUser accountUser)
|
{
|
AccountUser old = accountUserMapper.selectAccountUserById(accountUser.getUserId());
|
if (StringUtils.isNull(old))
|
{
|
return 0;
|
}
|
validateSupplier(old, accountUser.getSupplierId());
|
// 绑定时间由服务端推导:换成另一个人的记为当前时间,解绑则清空,
|
// 关系没动则保留原值——否则每次保存都会把绑定时间刷成今天。
|
Date now = DateUtils.getNowDate();
|
accountUser.setReferrerBindTime(
|
resolveBindTime(old.getReferrerId(), accountUser.getReferrerId(), old.getReferrerBindTime(), now));
|
accountUser.setSupplierBindTime(
|
resolveBindTime(old.getSupplierId(), accountUser.getSupplierId(), old.getSupplierBindTime(), now));
|
return accountUserMapper.updateUserRelation(accountUser);
|
}
|
|
@Override
|
public List<MemberOptionVo> searchSupplierCandidates(Long userId, String keyword)
|
{
|
// 空关键字不回结果:候选是等级高于该会员的全量集合,放开等于把大半张会员表倒进下拉。
|
// 与 AccountCityCompanyServiceImpl.searchL4Member 是同一口径(那边搜 L4,这边搜等级高于该会员)。
|
if (StringUtils.isEmpty(keyword))
|
{
|
return Collections.emptyList();
|
}
|
AccountUser member = accountUserMapper.selectAccountUserById(userId);
|
if (StringUtils.isNull(member))
|
{
|
throw new ServiceException("会员不存在");
|
}
|
// 严格高于该会员的等级集合;该会员已是最高等级时为空,必须提前返回——
|
// 空集合会让候选 SQL 拼出 in () 的语法错误。
|
List<String> levels = AccountUser.levelsAbove(member.getSystemLevel());
|
if (levels.isEmpty())
|
{
|
return Collections.emptyList();
|
}
|
String kw = keyword.trim();
|
// 纯数字时补一个 keywordId 走等值匹配(会员编号),非数字则不参与该列比较——
|
// 写成 user_id like 或 cast(user_id as char) 都会让主键索引用不上。同 searchL4Member。
|
Long keywordId = null;
|
try
|
{
|
keywordId = Long.valueOf(kw);
|
}
|
catch (NumberFormatException ignored)
|
{
|
// 非纯数字:只走手机号/昵称/姓名模糊匹配
|
}
|
return accountUserMapper.selectSupplierCandidates(userId, levels, kw, keywordId);
|
}
|
|
/**
|
* 校验新供货人的等级必须严格高于该会员(C < L1 < L2 < L3 < L4)。
|
*
|
* 这是提交时的第二道闸:弹窗只列出合法候选,但 /account/user/relation 是公开入口,
|
* 绕过页面直接打接口也得拦住。解除供货(传 null)不需要校验。
|
*
|
* 与旧值相同时直接放行:存量关系是「历来没有等级限制」时期写下的(例如 L4 的供货人是 C),
|
* 不该因为这次没换人被卡死——那是另一件事,按本项目的规矩也不在这里顺手修历史数据。
|
*/
|
private void validateSupplier(AccountUser old, Long newSupplierId)
|
{
|
if (newSupplierId == null || newSupplierId.equals(old.getSupplierId()))
|
{
|
return;
|
}
|
// selectAccountUserById 的 where 自带 del_flag = '0',查不到即「不存在或已删除」
|
AccountUser supplier = accountUserMapper.selectAccountUserById(newSupplierId);
|
if (StringUtils.isNull(supplier))
|
{
|
throw new ServiceException("新供货人不存在");
|
}
|
if (!AccountUser.isHigherLevel(supplier.getSystemLevel(), old.getSystemLevel()))
|
{
|
throw new ServiceException("新供货人的系统等级必须高于该会员(该会员:" + old.getSystemLevel()
|
+ ",所选:" + supplier.getSystemLevel() + ")");
|
}
|
}
|
|
@Override
|
public int changeStatus(AccountUser accountUser)
|
{
|
// 本接口只服务列表页的「禁用/启用」,故目标状态只允许「正常/禁用」两态。
|
// 注销中、已注销必须由注销审核流程驱动(见 AccountCancelApplyServiceImpl):
|
// 若这里放开,「已注销」的会员一次普通请求就能被改回「正常」,等于绕过审核。
|
String target = accountUser.getStatus();
|
if (!AccountUser.STATUS_NORMAL.equals(target) && !AccountUser.STATUS_DISABLED.equals(target))
|
{
|
throw new ServiceException("账户状态只能改为「正常」或「禁用」");
|
}
|
AccountUser old = accountUserMapper.selectAccountUserById(accountUser.getUserId());
|
if (StringUtils.isNull(old))
|
{
|
throw new ServiceException("会员不存在");
|
}
|
// 反向也要拦:注销中/已注销的会员不允许从列表页改回正常或禁用,否则审核结论形同虚设
|
if (AccountUser.STATUS_CANCELING.equals(old.getStatus()) || AccountUser.STATUS_CANCELED.equals(old.getStatus()))
|
{
|
throw new ServiceException("该会员正在注销或已注销,账户状态只能由注销审核流程变更");
|
}
|
return accountUserMapper.updateUserStatus(accountUser);
|
}
|
|
@Override
|
@Transactional
|
public int updateAccountAddress(AccountAddress accountAddress)
|
{
|
// 置为默认前先清掉同会员其它行的默认标记:MySQL 没有部分唯一索引,
|
// "每会员至多一条 is_default='1'"只能靠这两条语句在同一事务内保证。
|
if ("1".equals(accountAddress.getIsDefault()))
|
{
|
accountAddressMapper.clearDefaultByUserId(accountAddress.getUserId());
|
}
|
int rows = accountAddressMapper.updateAddress(accountAddress);
|
// updateAddress 的 where 带 user_id,id 与 userId 对不上时会更新 0 行。
|
// 此时上面的默认标记已被清掉,必须抛异常回滚,
|
// 否则该会员会落到"一个默认地址都没有"的状态。
|
if (rows == 0)
|
{
|
throw new ServiceException("收货地址不存在或不属于该会员");
|
}
|
return rows;
|
}
|
|
@Override
|
public int deleteAccountAddressByIds(Long[] ids)
|
{
|
return accountAddressMapper.deleteAddressByIds(ids);
|
}
|
|
|
private Date resolveBindTime(Long oldId, Long newId, Date oldTime, Date now)
|
{
|
if (newId == null)
|
{
|
return null;
|
}
|
// 关系未变时沿用原绑定时间;oldTime 为空(历史数据没记)则补当前时间
|
if (Objects.equals(oldId, newId) && oldTime != null)
|
{
|
return oldTime;
|
}
|
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.setAuthStatus(StringUtils.isEmpty(accountUser.getAuthStatus())
|
? AUTH_STATUS_NONE : accountUser.getAuthStatus());
|
// 企业/个体认证状态同样兜底为「未认证」(与个人认证状态相互独立)
|
vo.setCompanyAuthStatus(StringUtils.isEmpty(accountUser.getCompanyAuthStatus())
|
? AUTH_STATUS_NONE : accountUser.getCompanyAuthStatus());
|
vo.setEarnings(earnings);
|
// STEP 5: 返回个人中心展示数据
|
return vo;
|
}
|
|
@Override
|
public void updateAvatar(Long userId, String avatarUrl)
|
{
|
// STEP 1: 校验会员ID(登录态缺失时直接失败,避免无归属更新)
|
if (StringUtils.isNull(userId))
|
{
|
throw new ServiceException("更新头像失败");
|
}
|
// STEP 2: 校验头像地址非空
|
if (StringUtils.isEmpty(avatarUrl))
|
{
|
throw new ServiceException("头像地址不能为空");
|
}
|
// STEP 3: 校验长度,超出列长直接拒绝,避免落库截断
|
if (avatarUrl.length() > AVATAR_MAX_LENGTH)
|
{
|
throw new ServiceException("头像地址过长");
|
}
|
// STEP 4: 只更新 avatar 一列(单列语句,不触碰其它资料)
|
AccountUser update = new AccountUser();
|
update.setUserId(userId);
|
update.setAvatar(avatarUrl);
|
update.setUpdateBy(SecurityUtils.getUsername());
|
// STEP 5: 影响行数为 0 说明会员不存在或已删除(SQL 自带 del_flag='0' 过滤)
|
if (accountUserMapper.updateUserAvatar(update) == 0)
|
{
|
throw new ServiceException("更新头像失败");
|
}
|
}
|
|
@Override
|
public InviteCodeVo getInviteCode(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: 组装展示对象(邀请码注册时生成,C 端会员必有)
|
InviteCodeVo vo = new InviteCodeVo();
|
vo.setReferralCode(accountUser.getMyReferralCode());
|
return vo;
|
}
|
|
@Override
|
public List<AccountInviteeVo> getInvitees(Long userId)
|
{
|
// STEP 1: 校验会员ID(登录态缺失时直接失败,避免无归属查询)
|
if (StringUtils.isNull(userId))
|
{
|
throw new ServiceException("获取邀请列表失败");
|
}
|
// STEP 2: 按 referrer_id 查询直接下级,SQL 已按绑定时间倒序(配合 Controller 分页)
|
List<AccountUser> invitees = accountUserMapper.selectInvitees(userId);
|
// STEP 3: 逐条组装展示对象;手机号脱敏、绑定日格式化,等级原值直出
|
List<AccountInviteeVo> result = new ArrayList<>(invitees.size());
|
for (AccountUser invitee : invitees)
|
{
|
AccountInviteeVo vo = new AccountInviteeVo();
|
vo.setUserId(invitee.getUserId());
|
vo.setNickname(invitee.getNickname());
|
vo.setAvatar(invitee.getAvatar());
|
vo.setPhone(maskPhone(invitee.getPhone()));
|
vo.setSystemLevel(invitee.getSystemLevel());
|
vo.setBindDate(formatBindDate(invitee.getReferrerBindTime()));
|
vo.setBuyAmountTotal(invitee.getBuyAmountTotal());
|
result.add(vo);
|
}
|
return result;
|
}
|
|
@Override
|
public AccountReferrerVo getReferrer(Long userId)
|
{
|
// STEP 1: 校验会员ID(登录态缺失时直接失败,避免无归属查询)
|
if (StringUtils.isNull(userId))
|
{
|
throw new ServiceException("获取上级信息失败");
|
}
|
// STEP 2: 查询会员本人,取其 referrer_id 与绑定时间
|
AccountUser self = accountUserMapper.selectAccountUserById(userId);
|
if (StringUtils.isNull(self))
|
{
|
throw new ServiceException("获取上级信息失败");
|
}
|
AccountReferrerVo vo = new AccountReferrerVo();
|
Long referrerId = self.getReferrerId();
|
// STEP 3: 无上级(命中通用推荐码注册或历史数据)时返回空对象,前端渲染空态
|
if (StringUtils.isNull(referrerId))
|
{
|
return vo;
|
}
|
// STEP 4: 按主键查上级;上级已被逻辑删除时同样返回空对象
|
AccountUser referrer = accountUserMapper.selectAccountUserById(referrerId);
|
if (StringUtils.isNull(referrer))
|
{
|
return vo;
|
}
|
// STEP 5: 组装展示对象;绑定时间取本人记录上的 referrer_bind_time
|
vo.setUserId(referrer.getUserId());
|
vo.setNickname(referrer.getNickname());
|
vo.setAvatar(referrer.getAvatar());
|
vo.setPhone(maskPhone(referrer.getPhone()));
|
vo.setSystemLevel(referrer.getSystemLevel());
|
vo.setBindDate(formatBindDate(self.getReferrerBindTime()));
|
return vo;
|
}
|
|
/**
|
* 绑定时间格式化(yyyy-MM-dd)
|
*
|
* @param bindTime 绑定时间,可为空
|
* @return 格式化后的日期字符串;入参为空时返回 null
|
*/
|
private String formatBindDate(Date bindTime)
|
{
|
return StringUtils.isNull(bindTime) ? null : DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, bindTime);
|
}
|
|
/**
|
* 判定是否为通用推荐码
|
*
|
* @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("注册失败,请稍后重试");
|
}
|
}
|