| | |
| | | package com.ruoyi.user.service.impl; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.security.SecureRandom; |
| | |
| | | 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.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; |
| | |
| | | |
| | | /** 推荐码随机源(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) |
| | |
| | | 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); |
| | | } |
| | | |
| | | /** |
| | | * 判定是否为通用推荐码 |
| | | * |