package com.sinata.appointment.service; import cn.dev33.satoken.stp.StpUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.sinata.appointment.constants.BizErrorCode; import com.sinata.appointment.dto.ResetPasswordRequest; import com.sinata.appointment.dto.SysUserCreateRequest; import com.sinata.appointment.dto.SysUserDetailVo; import com.sinata.appointment.dto.SysUserListVo; import com.sinata.appointment.dto.SysUserUpdateRequest; import com.sinata.appointment.dto.UserRoleLabelRow; import com.sinata.appointment.dto.UserStatusPatchRequest; import com.sinata.appointment.entity.SysRole; import com.sinata.appointment.entity.SysUser; import com.sinata.appointment.entity.SysUserRole; import com.sinata.appointment.exception.BizException; import com.sinata.appointment.mapper.SysRoleMapper; import com.sinata.appointment.mapper.SysUserMapper; import com.sinata.appointment.mapper.SysUserRoleMapper; import com.sinata.appointment.util.MobileMaskUtil; import com.sinata.appointment.util.UuidAssert; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; @Service @RequiredArgsConstructor public class SysUserApplicationService { private final SysUserMapper sysUserMapper; private final SysRoleMapper sysRoleMapper; private final SysUserRoleMapper sysUserRoleMapper; private final BCryptPasswordEncoder bcrypt; public IPage pageUsers( long page, long size, String realName, String mobile, Long roleId, Integer status) { LambdaQueryWrapper q = new LambdaQueryWrapper<>(); q.eq(SysUser::getDelFlag, 0); if (StringUtils.hasText(realName)) { q.like(SysUser::getRealName, realName.trim()); } if (StringUtils.hasText(mobile)) { q.like(SysUser::getMobile, mobile.trim()); } if (roleId != null) { q.apply( "id IN (SELECT user_id FROM sys_user_role WHERE role_id = {0} AND del_flag = 0)", roleId); } if (status != null) { q.eq(SysUser::getStatus, status); } q.orderByDesc(SysUser::getCreateTime); Page p = sysUserMapper.selectPage(new Page<>(page, size), q); List userIds = p.getRecords().stream().map(SysUser::getId).toList(); Map roleLine = roleDisplayByUserIds(userIds); return p.convert( u -> { SysUserListVo vo = new SysUserListVo(); vo.setId(u.getId()); vo.setUuid(u.getUuid()); vo.setUsername(u.getUsername()); vo.setRealName(u.getRealName()); vo.setMobileMasked(MobileMaskUtil.mask(u.getMobile())); vo.setRolesDisplay(roleLine.getOrDefault(u.getId(), "—")); vo.setStatus(u.getStatus()); vo.setCreateTime(u.getCreateTime()); return vo; }); } private Map roleDisplayByUserIds(List userIds) { if (userIds.isEmpty()) { return Map.of(); } List rows = sysUserMapper.listRoleLabelsByUserIds(userIds); return rows.stream() .collect( Collectors.groupingBy( UserRoleLabelRow::getUserId, Collectors.mapping( UserRoleLabelRow::getRoleName, Collectors.joining("、")))); } public SysUserDetailVo getDetail(Long id, String uuid) { SysUser u = loadActiveWithUuid(id, uuid); SysUserDetailVo vo = new SysUserDetailVo(); vo.setId(u.getId()); vo.setUuid(u.getUuid()); vo.setUsername(u.getUsername()); vo.setRealName(u.getRealName()); vo.setMobile(u.getMobile()); vo.setAvatarUrl(u.getAvatarUrl()); vo.setStatus(u.getStatus()); vo.setRoleIds(listRoleIds(id)); return vo; } @Transactional(rollbackFor = Exception.class) public Map createUser(SysUserCreateRequest req) { long exists = sysUserMapper.selectCount( new LambdaQueryWrapper() .eq(SysUser::getUsername, req.getUsername().trim()) .eq(SysUser::getDelFlag, 0)); if (exists > 0) { throw new BizException(BizErrorCode.DUPLICATE_USERNAME, "登录账号已存在"); } validateRoleIds(req.getRoleIds()); SysUser u = new SysUser(); u.setUsername(req.getUsername().trim()); u.setPasswordHash(bcrypt.encode(req.getPassword())); u.setRealName(trimToNull(req.getRealName())); u.setMobile(trimToNull(req.getMobile())); u.setAvatarUrl(trimToNull(req.getAvatarUrl())); u.setStatus(1); sysUserMapper.insert(u); SysUser fresh = sysUserMapper.selectById(u.getId()); if (fresh == null || !StringUtils.hasText(fresh.getUuid())) { throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "创建后读取用户失败"); } replaceRoles(fresh.getId(), req.getRoleIds()); Map m = new HashMap<>(); m.put("id", fresh.getId()); m.put("uuid", fresh.getUuid()); return m; } @Transactional(rollbackFor = Exception.class) public void updateUser(Long id, SysUserUpdateRequest req) { SysUser u = loadActiveWithUuid(id, req.getUuid()); validateRoleIds(req.getRoleIds()); u.setRealName(trimToNull(req.getRealName())); u.setMobile(trimToNull(req.getMobile())); u.setAvatarUrl(trimToNull(req.getAvatarUrl())); sysUserMapper.updateById(u); replaceRoles(id, req.getRoleIds()); } @Transactional(rollbackFor = Exception.class) public void resetPassword(Long id, ResetPasswordRequest req) { if (!Objects.equals(req.getNewPassword(), req.getNewPasswordConfirm())) { throw new BizException(BizErrorCode.PASSWORD_CONFIRM_MISMATCH, "两次输入的新密码不一致"); } SysUser u = loadActiveWithUuid(id, req.getUuid()); u.setPasswordHash(bcrypt.encode(req.getNewPassword())); sysUserMapper.updateById(u); kickSysUserOffline(id); } @Transactional(rollbackFor = Exception.class) public void patchStatus(Long id, UserStatusPatchRequest req, long operatorUserId) { int st = req.getStatus(); if (st != 0 && st != 1) { throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "状态无效"); } if (st == 0 && id == operatorUserId) { throw new BizException(BizErrorCode.USER_SELF_FORBIDDEN, "不能禁用自己的账号"); } SysUser u = loadActiveWithUuid(id, req.getUuid()); u.setStatus(st); sysUserMapper.updateById(u); if (st == 0) { kickSysUserOffline(id); } } @Transactional(rollbackFor = Exception.class) public void deleteUser(Long id, String uuid, long operatorUserId) { if (id == operatorUserId) { throw new BizException(BizErrorCode.USER_SELF_FORBIDDEN, "不能删除自己的账号"); } SysUser u = loadActiveWithUuid(id, uuid); sysUserRoleMapper.delete( new LambdaQueryWrapper() .eq(SysUserRole::getUserId, id) .eq(SysUserRole::getDelFlag, 0)); int affected = sysUserMapper.deleteById(u.getId()); if (affected <= 0) { throw new BizException(BizErrorCode.RESOURCE_VERIFY_FAILED, "删除失败,请刷新后重试"); } kickSysUserOffline(id); } private static void kickSysUserOffline(Long sysUserId) { StpUtil.kickout(sysUserId); } private SysUser loadActiveWithUuid(Long id, String uuid) { SysUser u = sysUserMapper.selectById(id); if (u == null || u.getDelFlag() != null && u.getDelFlag() == 1) { throw new BizException(BizErrorCode.RESOURCE_VERIFY_FAILED, "资源不存在或无权访问"); } UuidAssert.match(u, uuid); return u; } private void validateRoleIds(List roleIds) { if (roleIds == null || roleIds.isEmpty()) { throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "至少选择一个角色"); } for (Long rid : roleIds) { SysRole r = sysRoleMapper.selectById(rid); if (r == null || r.getDelFlag() != null && r.getDelFlag() == 1) { throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "角色不存在: " + rid); } } } private List listRoleIds(Long userId) { return sysUserRoleMapper .selectList( new LambdaQueryWrapper() .eq(SysUserRole::getUserId, userId) .eq(SysUserRole::getDelFlag, 0)) .stream() .map(SysUserRole::getRoleId) .toList(); } private void replaceRoles(Long userId, List roleIds) { sysUserRoleMapper.delete( new LambdaQueryWrapper() .eq(SysUserRole::getUserId, userId) .eq(SysUserRole::getDelFlag, 0)); for (Long rid : roleIds) { SysUserRole ur = new SysUserRole(); ur.setUserId(userId); ur.setRoleId(rid); sysUserRoleMapper.insert(ur); } } private static String trimToNull(String s) { if (!StringUtils.hasText(s)) { return null; } String t = s.trim(); return t.isEmpty() ? null : t; } }