package com.ruoyi.user.service.impl; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Objects; 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.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.mapper.AccountUserMapper; import com.ruoyi.user.mapper.AccountWarehouseMapper; import com.ruoyi.user.service.IAccountUserService; /** * 会员用户 业务层实现 * * @author ruoyi */ @Service public class AccountUserServiceImpl implements IAccountUserService { @Autowired private AccountUserMapper accountUserMapper; @Autowired private AccountCertificationMapper accountCertificationMapper; @Autowired private AccountAddressMapper accountAddressMapper; @Autowired private AccountAgreementMapper accountAgreementMapper; @Autowired private AccountWarehouseMapper accountWarehouseMapper; @Override public AccountUser selectAccountUserById(Long userId) { return accountUserMapper.selectAccountUserById(userId); } @Override public List 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) { accountUser.setPassword(SecurityUtils.encryptPassword(accountUser.getPassword())); return accountUserMapper.insertAccountUser(accountUser); } @Override public int updateAccountUser(AccountUser accountUser) { // 不为空才加密;为空时保持 null,mapper 的 会跳过该列,原密码不受影响 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 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 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; } }