init: UserBaseService (backend + deploy + scripts)
ci-userbase-service / build-and-push (push) Successful in 40s
ci-userbase-service / build-and-push (push) Successful in 40s
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
package com.yunhai.userbase.service.impl;
|
||||
|
||||
import com.bestsch.utils.DException;
|
||||
import com.yunhai.userbase.enums.IEnum;
|
||||
import com.yunhai.userbase.enums.StatusEnum;
|
||||
import com.yunhai.userbase.enums.SysRoleEnum;
|
||||
import com.yunhai.userbase.enums.UserTypeEnum;
|
||||
import com.yunhai.userbase.entity.SysUser;
|
||||
import com.yunhai.userbase.entity.SysUserGroupRel;
|
||||
import com.yunhai.userbase.model.sysuser.SysUserQuery;
|
||||
import com.yunhai.userbase.model.sysuser.SysUserRequest;
|
||||
import com.yunhai.userbase.model.sysuser.SysUserVo;
|
||||
import com.yunhai.userbase.repository.SysGroupRepository;
|
||||
import com.yunhai.userbase.repository.SysUserAppRoleRelRepository;
|
||||
import com.yunhai.userbase.repository.SysUserGroupRelRepository;
|
||||
import com.yunhai.userbase.repository.SysUserRepository;
|
||||
import com.yunhai.userbase.service.ISysGroupService;
|
||||
import com.yunhai.userbase.service.ISysUserService;
|
||||
import com.yunhai.userbase.utils.ValidatorUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
import javax.persistence.criteria.Subquery;
|
||||
import javax.transaction.Transactional;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author chenx
|
||||
* @date 2026/7/20 16:14
|
||||
* @description 系统用户服务实现
|
||||
*/
|
||||
@Service
|
||||
public class SysUserService implements ISysUserService {
|
||||
|
||||
/** 主归属标识:1=本职主学校/部门 */
|
||||
private static final int IS_MAIN = 1;
|
||||
|
||||
/** 系统内置超级管理员账号(与 sys_user_init.sql 种子账号保持一致) */
|
||||
private static final Set<String> SUPER_ADMIN_ACCOUNTS =
|
||||
new HashSet<>(Arrays.asList("yunhaisch", "yunhaisch2", "yunhaisch3"));
|
||||
|
||||
@Autowired
|
||||
private SysUserRepository sysUserRepository;
|
||||
|
||||
@Autowired
|
||||
private SysUserGroupRelRepository sysUserGroupRelRepository;
|
||||
|
||||
@Autowired
|
||||
private SysUserAppRoleRelRepository sysUserAppRoleRelRepository;
|
||||
|
||||
@Autowired
|
||||
private SysGroupRepository sysGroupRepository;
|
||||
|
||||
@Autowired
|
||||
private ISysGroupService sysGroupService;
|
||||
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void add(SysUserRequest request) {
|
||||
if (sysUserRepository.countByAccount(request.getAccount()) > 0) {
|
||||
throw new DException("账号已存在");
|
||||
}
|
||||
if (!sysGroupRepository.existsById(request.getGroupId())) {
|
||||
throw new DException("所属分组不存在");
|
||||
}
|
||||
validateProfile(request);
|
||||
String encodedPassword = validateAndEncodePassword(request.getPassword(), request.getConfirmPassword());
|
||||
|
||||
SysUser entity = new SysUser();
|
||||
entity.setAccount(request.getAccount());
|
||||
entity.setPassword(encodedPassword);
|
||||
entity.setRealName(request.getRealName());
|
||||
entity.setPhone(request.getPhone());
|
||||
entity.setBirthday(request.getBirthday());
|
||||
entity.setIdcard(request.getIdcard());
|
||||
entity.setProfilePicture(request.getProfilePicture());
|
||||
entity.setRoles(request.getRoles());
|
||||
// 用户类型推导:超级管理员账号固定为 1;其余含 ADMIN(7) 角色 -> 2 学校管理员;默认 0 普通用户
|
||||
entity.setType(resolveUserType(entity.getAccount(), entity.getRoles()));
|
||||
// disabled 用枚举存储:不传时默认 NORMAL(正常/未禁用)
|
||||
entity.setDisabled(request.getDisabled() != null ? resolveStatus(request.getDisabled()) : StatusEnum.NORMAL);
|
||||
entity.setExpired(request.getExpired() != null ? request.getExpired() : false);
|
||||
sysUserRepository.save(entity);
|
||||
|
||||
// 写入组用户关联
|
||||
SysUserGroupRel rel = new SysUserGroupRel();
|
||||
rel.setUserId(entity.getId());
|
||||
rel.setGroupId(request.getGroupId());
|
||||
rel.setIsMain(IS_MAIN);
|
||||
rel.setCreateTime(LocalDateTime.now());
|
||||
sysUserGroupRelRepository.save(rel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void edit(Integer id, SysUserRequest request) {
|
||||
if (sysUserRepository.countByAccountAndIdNot(request.getAccount(), id) > 0) {
|
||||
throw new DException("账号已被其他用户使用");
|
||||
}
|
||||
SysUser entity = sysUserRepository.findById(id)
|
||||
.orElseThrow(() -> new DException("系统用户不存在"));
|
||||
validateProfile(request);
|
||||
|
||||
entity.setAccount(request.getAccount());
|
||||
// 密码非空才修改:需两次一致 + 弱密码校验 + BCrypt 加密
|
||||
if (StringUtils.isNotBlank(request.getPassword())) {
|
||||
entity.setPassword(validateAndEncodePassword(request.getPassword(), request.getConfirmPassword()));
|
||||
}
|
||||
entity.setRealName(request.getRealName());
|
||||
entity.setPhone(request.getPhone());
|
||||
entity.setBirthday(request.getBirthday());
|
||||
entity.setIdcard(request.getIdcard());
|
||||
entity.setProfilePicture(request.getProfilePicture());
|
||||
entity.setRoles(request.getRoles());
|
||||
// 用户类型推导:仅当本次传入了角色时才重算,避免编辑时未传角色把 type 误重置为 0
|
||||
if (request.getRoles() != null) {
|
||||
entity.setType(resolveUserType(entity.getAccount(), entity.getRoles()));
|
||||
}
|
||||
if (request.getDisabled() != null) {
|
||||
entity.setDisabled(resolveStatus(request.getDisabled()));
|
||||
}
|
||||
if (request.getExpired() != null) {
|
||||
entity.setExpired(request.getExpired());
|
||||
}
|
||||
sysUserRepository.save(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void delete(Integer id) {
|
||||
if (!sysUserRepository.existsById(id)) {
|
||||
throw new DException("系统用户不存在");
|
||||
}
|
||||
// 先清理组用户关联、用户-角色绑定,再删除用户
|
||||
sysUserGroupRelRepository.deleteByUserId(id);
|
||||
// AI-FIX(角色授权): 删除用户时级联清理用户-业务角色绑定(逻辑外键无物理 FK,避免残留孤儿数据)
|
||||
sysUserAppRoleRelRepository.deleteByUserIdBulk(id);
|
||||
sysUserRepository.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void changeStatus(Integer id, boolean disabled) {
|
||||
SysUser entity = sysUserRepository.findById(id)
|
||||
.orElseThrow(() -> new DException("系统用户不存在"));
|
||||
// 实体 disabled 字段为 StatusEnum(0=正常/未禁用,1=禁用):boolean 直接映射枚举
|
||||
entity.setDisabled(disabled ? StatusEnum.DISABLED : StatusEnum.NORMAL);
|
||||
sysUserRepository.save(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<SysUserVo> list(SysUserQuery query, Pageable pageable) {
|
||||
Specification<SysUser> spec = buildSpecification(query);
|
||||
return sysUserRepository.findAll(spec, pageable).map(this::convertToVo);
|
||||
}
|
||||
|
||||
// ===================== 私有工具 =====================
|
||||
|
||||
/**
|
||||
* 组装用户列表查询条件:组范围(含子组)+ 关键字(账号/姓名/身份证模糊、手机号精确)+ 启用状态。<br>
|
||||
*/
|
||||
private Specification<SysUser> buildSpecification(SysUserQuery query) {
|
||||
return (root, cbQuery, cb) -> {
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
|
||||
// 组范围过滤:用户ID 属于目标组集合下的关联记录
|
||||
if (query.getGroupId() != null) {
|
||||
List<Integer> groupIds = resolveGroupIds(query.getGroupId(), query.getWithSub());
|
||||
Subquery<Integer> sub = cbQuery.subquery(Integer.class);
|
||||
Root<SysUserGroupRel> relRoot = sub.from(SysUserGroupRel.class);
|
||||
sub.select(relRoot.get("userId"));
|
||||
sub.where(relRoot.get("groupId").in(groupIds));
|
||||
predicates.add(root.get("id").in(sub));
|
||||
}
|
||||
|
||||
// 关键字:账号/姓名/身份证模糊,手机号精确
|
||||
if (StringUtils.isNotBlank(query.getName())) {
|
||||
String keyword = query.getName().trim();
|
||||
String like = "%" + keyword + "%";
|
||||
Predicate byAccount = cb.like(root.get("account"), like);
|
||||
Predicate byRealName = cb.like(root.get("realName"), like);
|
||||
Predicate byIdcard = cb.like(root.get("idcard"), like);
|
||||
Predicate byPhone = cb.equal(root.get("phone"), keyword);
|
||||
predicates.add(cb.or(byAccount, byRealName, byIdcard, byPhone));
|
||||
}
|
||||
|
||||
if (query.getDisabled() != null) {
|
||||
// 实体 disabled 为 StatusEnum,查询条件需用枚举值比较(按 key 解析)
|
||||
predicates.add(cb.equal(root.get("disabled"), IEnum.getByKey(StatusEnum.class, query.getDisabled())));
|
||||
}
|
||||
return cb.and(predicates.toArray(new Predicate[0]));
|
||||
};
|
||||
}
|
||||
|
||||
/** 依据 withSub 决定组范围:1=含所有子孙组,否则仅当前组。 */
|
||||
private List<Integer> resolveGroupIds(Integer groupId, Integer withSub) {
|
||||
if (withSub != null && withSub == 1) {
|
||||
return sysGroupService.listSelfAndDescendantIds(groupId);
|
||||
}
|
||||
List<Integer> single = new ArrayList<>();
|
||||
single.add(groupId);
|
||||
return single;
|
||||
}
|
||||
|
||||
/** 校验手机号与身份证(有值才校验)。 */
|
||||
private void validateProfile(SysUserRequest request) {
|
||||
if (StringUtils.isNotBlank(request.getPhone()) && !ValidatorUtil.isPhone(request.getPhone())) {
|
||||
throw new DException("手机号格式不正确");
|
||||
}
|
||||
if (StringUtils.isNotBlank(request.getIdcard()) && !ValidatorUtil.isIdCard(request.getIdcard())) {
|
||||
throw new DException("身份证号不合法");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验两次密码一致性与弱密码规则,通过后返回 BCrypt 密文。<br>
|
||||
*/
|
||||
private String validateAndEncodePassword(String password, String confirmPassword) {
|
||||
if (!StringUtils.equals(password, confirmPassword)) {
|
||||
throw new DException("两次输入的密码不一致");
|
||||
}
|
||||
String weakReason = ValidatorUtil.checkPasswordStrength(password);
|
||||
if (weakReason != null) {
|
||||
throw new DException(weakReason);
|
||||
}
|
||||
return passwordEncoder.encode(password);
|
||||
}
|
||||
|
||||
private SysUserVo convertToVo(SysUser entity) {
|
||||
SysUserVo vo = new SysUserVo();
|
||||
BeanUtils.copyProperties(entity, vo);
|
||||
// disabled 为枚举,需要单独映射为 key(与 GroupTypeEnum 的 type 处理一致)
|
||||
vo.setDisabled(entity.getDisabled() == null ? null : entity.getDisabled().getKey());
|
||||
// type 为枚举,同样映射为 key
|
||||
vo.setType(entity.getType() == null ? null : entity.getType().getKey());
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 推导用户类型(见 {@link UserTypeEnum}):
|
||||
* - 内置超级管理员账号 -> 1 超级管理员;
|
||||
* - 其余账号若拥有 ADMIN(7) 角色 -> 2 学校管理员;
|
||||
* - 其他 -> 0 普通用户。
|
||||
*/
|
||||
private UserTypeEnum resolveUserType(String account, List<Integer> roles) {
|
||||
if (account != null && SUPER_ADMIN_ACCOUNTS.contains(account)) {
|
||||
return UserTypeEnum.SUPER_ADMIN;
|
||||
}
|
||||
if (roles != null && roles.contains(SysRoleEnum.ADMIN.getKey())) {
|
||||
return UserTypeEnum.SCHOOL_ADMIN;
|
||||
}
|
||||
return UserTypeEnum.NORMAL;
|
||||
}
|
||||
|
||||
/** 校验并返回用户状态枚举;非法状态抛异常。 */
|
||||
private StatusEnum resolveStatus(Integer status) {
|
||||
StatusEnum statusEnum = IEnum.getByKey(StatusEnum.class, status);
|
||||
if (statusEnum == null) {
|
||||
throw new DException("用户状态不存在");
|
||||
}
|
||||
return statusEnum;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user