1314 lines
54 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.designhub.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.designhub.dto.LoginDTO;
import com.designhub.dto.RegisterDTO;
import com.designhub.entity.Department;
import com.designhub.entity.User;
import com.designhub.entity.UserDepartment;
import com.designhub.exception.BusinessException;
import com.designhub.mapper.UserMapper;
import com.designhub.mapper.DepartmentMapper;
import com.designhub.mapper.UserDepartmentMapper;
import com.designhub.common.ResultCode;
import com.designhub.util.JwtUtil;
import com.designhub.vo.LoginVO;
import com.designhub.vo.UserInfoVO;
import com.designhub.vo.UserDepartmentVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.HashMap;
import java.util.stream.Collectors;
/**
* 用户管理Service
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class UserService {
private final UserMapper userMapper;
private final DepartmentMapper departmentMapper;
private final UserDepartmentMapper userDepartmentMapper;
private final TeamScorePoolService teamScorePoolService;
private final RoleService roleService;
private final DingTalkService dingTalkService;
private final DepartmentService departmentService;
private final JwtUtil jwtUtil;
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@Autowired(required = false)
private com.designhub.mapper.TaskMapper taskMapper;
private Department getDepartmentOrThrow(Long deptId) {
if (deptId == null) {
return null;
}
Department department = departmentMapper.selectById(deptId);
if (department == null) {
throw new BusinessException("部门不存在");
}
if (department.getStatus() != null && department.getStatus() == 0) {
throw new BusinessException("该部门已被禁用");
}
return department;
}
/**
* 分页查询用户列表
*/
public Page<User> getUserList(int pageNum, int pageSize, String keyword, Long deptId, Integer status) {
Page<User> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(keyword)) {
queryWrapper.and(wrapper -> wrapper
.like(User::getUsername, keyword)
.or()
.like(User::getRealName, keyword)
.or()
.like(User::getPhone, keyword)
.or()
.like(User::getEmail, keyword)
);
}
if (deptId != null) {
queryWrapper.eq(User::getDeptId, deptId);
}
if (status != null) {
queryWrapper.eq(User::getStatus, status);
}
queryWrapper.orderByDesc(User::getCreateTime);
return userMapper.selectPage(page, queryWrapper);
}
/**
* 根据ID获取用户
*/
public User getUserById(Long id) {
User user = userMapper.selectById(id);
if (user != null) {
// 不返回密码
user.setPassword(null);
}
return user;
}
/**
* 根据用户名获取用户
*/
public User getUserByUsername(String username) {
return userMapper.selectOne(
new LambdaQueryWrapper<User>()
.eq(User::getUsername, username)
);
}
/**
* 用户注册
*/
@Transactional(rollbackFor = Exception.class)
public User register(RegisterDTO registerDTO) {
log.info("用户注册: username={}, roleId={}, deptId={}",
registerDTO.getUsername(), registerDTO.getRoleId(), registerDTO.getDeptId());
// 验证用户名唯一性
User existUser = getUserByUsername(registerDTO.getUsername());
if (existUser != null) {
throw new BusinessException("用户名已存在");
}
// 验证角色ID(只允许注册为设计师或需求方)
if (registerDTO.getRoleId() != 5L && registerDTO.getRoleId() != 6L) {
throw new BusinessException("无效的角色选择");
}
// 验证部门存在
Department department = departmentMapper.selectById(registerDTO.getDeptId());
if (department == null) {
throw new BusinessException("部门不存在");
}
// 验证角色和部门类型匹配
String roleCode = registerDTO.getRoleId() == 5L ? "DESIGNER" : "REQUESTER";
String expectedDeptType = roleCode.equals("DESIGNER") ? "receiving" : "demand";
if (!expectedDeptType.equals(department.getDeptType())) {
throw new BusinessException("角色与部门类型不匹配");
}
// 创建用户
User user = new User();
user.setUsername(registerDTO.getUsername());
user.setPassword(passwordEncoder.encode(registerDTO.getPassword()));
user.setRealName(registerDTO.getRealName());
user.setEmail(registerDTO.getEmail());
user.setPhone(registerDTO.getPhone());
user.setDeptId(registerDTO.getDeptId());
user.setDeptName(department.getDeptName());
user.setRoleIds(String.valueOf(registerDTO.getRoleId()));
user.setStatus(1);
user.setDeleted(0);
userMapper.insert(user);
log.info("用户注册成功: userId={}, username={}", user.getId(), user.getUsername());
// 不返回密码
user.setPassword(null);
return user;
}
/**
* 创建用户
*/
@Transactional(rollbackFor = Exception.class)
public User createUser(User user) {
// 验证用户名唯一性
User existUser = getUserByUsername(user.getUsername());
if (existUser != null) {
throw new BusinessException("用户名已存在");
}
// 验证手机号唯一性
if (StringUtils.hasText(user.getPhone())) {
long count = userMapper.selectCount(
new LambdaQueryWrapper<User>()
.eq(User::getPhone, user.getPhone())
);
if (count > 0) {
throw new BusinessException("手机号已存在");
}
}
// 验证邮箱唯一性
if (StringUtils.hasText(user.getEmail())) {
long count = userMapper.selectCount(
new LambdaQueryWrapper<User>()
.eq(User::getEmail, user.getEmail())
);
if (count > 0) {
throw new BusinessException("邮箱已存在");
}
}
// 加密密码
if (StringUtils.hasText(user.getPassword())) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
} else {
// 默认密码
user.setPassword(passwordEncoder.encode("123456"));
}
// 设置部门名称
if (user.getDeptId() != null) {
Department department = getDepartmentOrThrow(user.getDeptId());
user.setDeptName(department.getDeptName());
}
// 设置默认状态
if (user.getStatus() == null) {
user.setStatus(1);
}
userMapper.insert(user);
return user;
}
/**
* 更新用户
*/
@Transactional(rollbackFor = Exception.class)
public boolean updateUser(User user) {
User existUser = getUserById(user.getId());
if (existUser == null) {
throw new BusinessException("用户不存在");
}
// 如果修改了用户名,验证唯一性
if (StringUtils.hasText(user.getUsername()) &&
!user.getUsername().equals(existUser.getUsername())) {
User sameUsernameUser = getUserByUsername(user.getUsername());
if (sameUsernameUser != null) {
throw new BusinessException("用户名已存在");
}
}
// 如果更新了部门
if (user.getDeptId() != null) {
Department department = getDepartmentOrThrow(user.getDeptId());
user.setDeptName(department != null ? department.getDeptName() : null);
}
// 不允许通过此接口修改密码
user.setPassword(null);
return userMapper.updateById(user) > 0;
}
/**
* 删除用户(逻辑删除)
*/
@Transactional(rollbackFor = Exception.class)
public boolean deleteUser(Long id) {
User user = getUserById(id);
if (user == null) {
throw new BusinessException("用户不存在");
}
// 不允许删除自己
// TODO: 获取当前登录用户ID
// if (id.equals(currentUserId)) {
// throw new BusinessException("不能删除自己");
// }
// 注意:已移除删除用户角色关联的代码,因为sys_user_role表不存在
// 系统使用sys_user表中的role_id字段管理角色,不使用关联表
return userMapper.deleteById(id) > 0;
}
/**
* 重置密码
*/
@Transactional(rollbackFor = Exception.class)
public boolean resetPassword(Long id, String newPassword) {
User user = getUserById(id);
if (user == null) {
throw new BusinessException("用户不存在");
}
user.setPassword(passwordEncoder.encode(newPassword));
return userMapper.updateById(user) > 0;
}
/**
* 修改密码
*/
@Transactional(rollbackFor = Exception.class)
public boolean changePassword(Long id, String oldPassword, String newPassword) {
User user = userMapper.selectById(id);
if (user == null) {
throw new BusinessException("用户不存在");
}
// 验证旧密码
if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
throw new BusinessException("原密码不正确");
}
user.setPassword(passwordEncoder.encode(newPassword));
return userMapper.updateById(user) > 0;
}
/**
* 为用户分配角色
*/
@Transactional(rollbackFor = Exception.class)
public boolean assignRoles(Long userId, List<Long> roleIds) {
User user = getUserById(userId);
if (user == null) {
throw new BusinessException("用户不存在");
}
// 将角色ID列表转换为逗号分隔的字符串
if (roleIds != null && !roleIds.isEmpty()) {
String roleIdsStr = roleIds.stream()
.map(String::valueOf)
.reduce((a, b) -> a + "," + b)
.orElse("");
user.setRoleIds(roleIdsStr);
userMapper.updateById(user);
} else {
user.setRoleIds(null);
userMapper.updateById(user);
}
return true;
}
/**
* 获取用户的角色ID列表
*/
public List<Long> getUserRoleIds(Long userId) {
User user = getUserById(userId);
if (user == null || user.getRoleIds() == null || user.getRoleIds().isEmpty()) {
return List.of();
}
// 将逗号分隔的字符串转换为Long列表
return java.util.Arrays.stream(user.getRoleIds().split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())
.map(Long::parseLong)
.toList();
}
/**
* 启用/禁用用户
*/
@Transactional(rollbackFor = Exception.class)
public boolean toggleUserStatus(Long id, Integer status) {
User user = new User();
user.setId(id);
user.setStatus(status);
return userMapper.updateById(user) > 0;
}
/**
* 获取全部用户列表(不分页)
*/
public List<User> getAllUsers() {
return userMapper.selectList(
new LambdaQueryWrapper<User>()
.orderByAsc(User::getDeptId)
.orderByDesc(User::getCreateTime)
);
}
/**
* 调整用户所属部门
*/
@Transactional(rollbackFor = Exception.class)
public boolean updateUserDepartment(Long userId, Long deptId) {
log.info("开始调整用户部门: userId={}, 新deptId={}", userId, deptId);
User user = getUserById(userId);
if (user == null) {
log.error("用户不存在: userId={}", userId);
throw new BusinessException("用户不存在");
}
log.info("当前用户信息: username={}, 原deptId={}, 原deptName={}",
user.getUsername(), user.getDeptId(), user.getDeptName());
// 使用 LambdaUpdateWrapper 强制更新,包括 null 值
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(User::getId, userId);
if (deptId == null || deptId == 0) {
// 清除部门
log.info("清除用户部门: userId={}", userId);
updateWrapper.set(User::getDeptId, null);
updateWrapper.set(User::getDeptName, null);
} else {
Department department = getDepartmentOrThrow(deptId);
log.info("设置新部门: deptId={}, deptName={}", department.getId(), department.getDeptName());
updateWrapper.set(User::getDeptId, department.getId());
updateWrapper.set(User::getDeptName, department.getDeptName());
// 确保新部门有积分池(如果不存在则自动创建)
teamScorePoolService.upsertPool(department.getId(), department.getDeptName());
log.info("已确保部门积分池存在: deptId={}, deptName={}", department.getId(), department.getDeptName());
}
int rows = userMapper.update(null, updateWrapper);
log.info("更新结果: 影响行数={}", rows);
return rows > 0;
}
/**
* 尝试匹配系统部门(根据钉钉部门名称)
*/
private Long matchSystemDepartment(List<Map<String, Object>> dingDepartments) {
if (dingDepartments == null || dingDepartments.isEmpty()) {
return null;
}
// 获取所有系统部门
List<Department> systemDepts = departmentMapper.selectList(
new LambdaQueryWrapper<Department>()
.eq(Department::getStatus, 1)
.eq(Department::getDeleted, 0)
);
for (Map<String, Object> dingDept : dingDepartments) {
String dingDeptName = (String) dingDept.get("name");
Object dingDeptIdObj = dingDept.get("dept_id");
String dingDeptId = dingDeptIdObj != null ? dingDeptIdObj.toString() : null;
// 1. 优先通过钉钉部门ID精确匹配
if (dingDeptId != null) {
Department matched = systemDepts.stream()
.filter(d -> dingDeptId.equals(d.getDingDeptId()))
.findFirst()
.orElse(null);
if (matched != null) {
log.info("通过钉钉部门ID精确匹配: {} -> {}", dingDeptId, matched.getDeptName());
return matched.getId();
}
}
// 2. 通过部门名称模糊匹配
if (dingDeptName != null) {
Department matched = systemDepts.stream()
.filter(d -> d.getDeptName().contains(dingDeptName) ||
dingDeptName.contains(d.getDeptName()))
.findFirst()
.orElse(null);
if (matched != null) {
log.info("通过部门名称模糊匹配: {} -> {}", dingDeptName, matched.getDeptName());
return matched.getId();
}
}
}
return null;
}
/**
* 同步钉钉用户信息到系统
*/
private void syncUserFromDingTalk(User user, Map<String, Object> detailedInfo,
String dingtalkUserId, List<Map<String, Object>> dingDepartments) {
boolean needUpdate = false;
// 1. 同步基本信息
if (detailedInfo != null) {
if (!StringUtils.hasText(user.getRealName()) && detailedInfo.get("name") != null) {
user.setRealName((String) detailedInfo.get("name"));
needUpdate = true;
}
if (!StringUtils.hasText(user.getAvatar()) && detailedInfo.get("avatar") != null) {
user.setAvatar((String) detailedInfo.get("avatar"));
needUpdate = true;
}
if (!StringUtils.hasText(user.getPhone()) && detailedInfo.get("mobile") != null) {
user.setPhone((String) detailedInfo.get("mobile"));
needUpdate = true;
}
}
// 2. 同步钉钉userId
if (!StringUtils.hasText(user.getDingUserId()) && dingtalkUserId != null) {
user.setDingUserId(dingtalkUserId);
needUpdate = true;
}
// 3. 同步部门(支持多部门)
if (dingDepartments != null && !dingDepartments.isEmpty()) {
// 匹配多个部门
List<Long> matchedDeptIds = matchSystemDepartments(dingDepartments);
if (!matchedDeptIds.isEmpty()) {
// 同步多个部门到sys_user_department表
// 第一个匹配的部门作为主部门
Long primaryDeptId = matchedDeptIds.get(0);
setUserDepartments(user.getId(), matchedDeptIds, primaryDeptId);
// 更新sys_user表的主部门(向后兼容)
Department primaryDept = departmentMapper.selectById(primaryDeptId);
if (primaryDept != null) {
user.setDeptId(primaryDeptId);
user.setDeptName(primaryDept.getDeptName());
// 根据主部门类型设置角色(但如果用户已经是管理员,保留管理员角色)
String currentRoleIds = user.getRoleIds();
log.info("syncUserFromDingTalk - 检查管理员角色: userId={}, currentRoleIds={}",
user.getId(), currentRoleIds);
boolean isAdmin = currentRoleIds != null &&
(currentRoleIds.contains("1") || currentRoleIds.contains("2"));
log.info("syncUserFromDingTalk - isAdmin={}, currentRoleIds.contains('1')={}, currentRoleIds.contains('2')={}",
isAdmin, currentRoleIds != null && currentRoleIds.contains("1"),
currentRoleIds != null && currentRoleIds.contains("2"));
if (!isAdmin) {
// 只有非管理员用户才根据部门类型设置角色
String newRoleIds = "receiving".equals(primaryDept.getDeptType()) ? "5" : "6";
log.info("syncUserFromDingTalk - 非管理员用户,设置角色: userId={}, newRoleIds={}",
user.getId(), newRoleIds);
user.setRoleIds(newRoleIds);
} else {
log.info("用户已是管理员,保留管理员角色: userId={}, roleIds={}",
user.getId(), currentRoleIds);
}
user.setStatus(1); // 启用用户
needUpdate = true;
log.info("同步用户多个部门: userId={}, 部门数量={}, 主部门={}",
user.getId(), matchedDeptIds.size(), primaryDept.getDeptName());
}
} else {
// 无法匹配,使用第一个可用部门作为默认值
List<Department> availableDepts = departmentMapper.selectList(
new LambdaQueryWrapper<Department>()
.eq(Department::getStatus, 1)
.eq(Department::getDeleted, 0)
.orderByAsc(Department::getSortOrder)
.orderByAsc(Department::getId)
);
if (!availableDepts.isEmpty()) {
Department defaultDept = availableDepts.get(0);
setUserDepartments(user.getId(), List.of(defaultDept.getId()), defaultDept.getId());
user.setDeptId(defaultDept.getId());
user.setDeptName(defaultDept.getDeptName());
// 根据部门类型设置角色(但如果用户已经是管理员,保留管理员角色)
String currentRoleIds = user.getRoleIds();
log.info("syncUserFromDingTalk - 检查管理员角色(默认部门): userId={}, currentRoleIds={}",
user.getId(), currentRoleIds);
boolean isAdmin = currentRoleIds != null &&
(currentRoleIds.contains("1") || currentRoleIds.contains("2"));
log.info("syncUserFromDingTalk - isAdmin={}", isAdmin);
if (!isAdmin) {
// 只有非管理员用户才根据部门类型设置角色
String newRoleIds = "receiving".equals(defaultDept.getDeptType()) ? "5" : "6";
log.info("syncUserFromDingTalk - 非管理员用户,设置角色: userId={}, newRoleIds={}",
user.getId(), newRoleIds);
user.setRoleIds(newRoleIds);
} else {
log.info("用户已是管理员,保留管理员角色: userId={}, roleIds={}",
user.getId(), currentRoleIds);
}
user.setStatus(1); // 启用用户
needUpdate = true;
log.warn("已有用户无法匹配,使用默认部门: userId={}, deptId={}, deptName={}, 钉钉部门: {}",
user.getId(), defaultDept.getId(), defaultDept.getDeptName(),
dingDepartments.stream()
.map(d -> String.valueOf(d.get("name")))
.collect(Collectors.joining(", ")));
}
}
}
if (needUpdate) {
userMapper.updateById(user);
log.info("钉钉用户信息已同步: userId={}", user.getId());
}
}
/**
* 处理钉钉OAuth登录
*/
@Transactional(rollbackFor = Exception.class)
private LoginVO handleDingTalkLogin(String code) {
log.info("开始钉钉OAuth登录流程, code: {}...", code.substring(0, Math.min(10, code.length())));
try {
// 1. 获取用户访问令牌
Map<String, Object> tokenData = dingTalkService.getUserAccessToken(code);
String accessToken = (String) tokenData.get("accessToken");
if (accessToken == null) {
throw new BusinessException(ResultCode.DINGTALK_AUTH_ERROR, "获取钉钉访问令牌失败");
}
// 2. 获取用户信息(包含unionId)
Map<String, Object> userMeData = dingTalkService.getUserInfo(accessToken);
String unionId = (String) userMeData.get("unionId");
String nick = (String) userMeData.get("nick");
String avatarUrl = (String) userMeData.get("avatarUrl");
if (unionId == null) {
throw new BusinessException(ResultCode.DINGTALK_AUTH_ERROR, "获取钉钉用户unionId失败");
}
log.info("钉钉用户信息: unionId={}, nick={}", unionId, nick);
// 3. 根据unionId获取userId(用于获取更详细信息)
String dingtalkUserId = dingTalkService.getUserIdByUnionId(unionId);
Map<String, Object> detailedInfo = null;
List<Map<String, Object>> dingDepartments = new ArrayList<>();
if (dingtalkUserId != null) {
detailedInfo = dingTalkService.getDetailedUserInfo(dingtalkUserId);
if (detailedInfo != null) {
log.info("获取到详细用户信息: userId={}", dingtalkUserId);
// 获取钉钉部门信息
dingDepartments = (List<Map<String, Object>>) detailedInfo.get("departments");
if (dingDepartments != null && !dingDepartments.isEmpty()) {
String deptNames = dingDepartments.stream()
.map(d -> String.valueOf(d.get("name")))
.collect(Collectors.joining(", "));
log.info("钉钉用户所属部门: {}", deptNames);
}
}
}
// 4. 查找或创建用户
// 使用unionId作为唯一标识
String email = "dingtalk_" + unionId + "@dingtalk.local";
User user = userMapper.selectOne(
new LambdaQueryWrapper<User>()
.eq(User::getEmail, email)
);
if (user == null) {
// 首次登录:创建用户但不分配部门
// 需要用户手动选择部门类型和具体部门
log.info("钉钉用户首次登录,创建临时账号: email={}", email);
// 尝试匹配系统部门(支持多部门)
List<Long> matchedDeptIds = matchSystemDepartments(dingDepartments);
String username = detailedInfo != null && detailedInfo.get("job_number") != null
? (String) detailedInfo.get("job_number") : "DT_" + unionId.substring(0, 8);
user = new User();
user.setUsername(username);
user.setEmail(email);
user.setPassword(passwordEncoder.encode(UUID.randomUUID().toString()));
user.setRealName(detailedInfo != null && detailedInfo.get("name") != null
? (String) detailedInfo.get("name") : nick);
user.setAvatar(detailedInfo != null && detailedInfo.get("avatar") != null
? (String) detailedInfo.get("avatar") : avatarUrl);
user.setPhone(detailedInfo != null ? (String) detailedInfo.get("mobile") : null);
user.setDingUserId(dingtalkUserId);
user.setStatus(1);
user.setDeleted(0);
if (!matchedDeptIds.isEmpty()) {
// 自动匹配到多个部门,第一个作为主部门
Long primaryDeptId = matchedDeptIds.get(0);
Department primaryDept = departmentMapper.selectById(primaryDeptId);
user.setDeptId(primaryDeptId);
user.setDeptName(primaryDept.getDeptName());
// 根据主部门类型设置默认角色
user.setRoleIds("receiving".equals(primaryDept.getDeptType()) ? "5" : "6");
log.info("自动匹配到系统部门(多部门): userId={}, 部门数量={}, 主部门={}",
username, matchedDeptIds.size(), primaryDept.getDeptName());
} else {
// 无法自动匹配,使用第一个可用部门作为默认值
List<Department> availableDepts = departmentMapper.selectList(
new LambdaQueryWrapper<Department>()
.eq(Department::getStatus, 1)
.eq(Department::getDeleted, 0)
.orderByAsc(Department::getSortOrder)
.orderByAsc(Department::getId)
);
if (!availableDepts.isEmpty()) {
// 使用第一个可用部门作为默认值
Department defaultDept = availableDepts.get(0);
user.setDeptId(defaultDept.getId());
user.setDeptName(defaultDept.getDeptName());
user.setRoleIds("receiving".equals(defaultDept.getDeptType()) ? "5" : "6");
matchedDeptIds = List.of(defaultDept.getId());
log.warn("无法自动匹配系统部门,使用默认部门: deptId={}, deptName={}, 钉钉部门: {}",
defaultDept.getId(), defaultDept.getDeptName(),
dingDepartments.stream()
.map(d -> String.valueOf(d.get("name")))
.collect(Collectors.joining(", ")));
} else {
// 如果系统没有任何部门,才需要用户手动选择
String deptNamesStr = dingDepartments.stream()
.map(d -> String.valueOf(d.get("name")))
.collect(Collectors.joining(", "));
log.warn("无法自动匹配系统部门,且系统无可用部门,钉钉部门: {}", deptNamesStr);
user.setDeptId(null);
user.setDeptName(null);
user.setRoleIds(null);
user.setStatus(0); // 暂时禁用,待选择部门后启用
}
}
userMapper.insert(user);
// 同步多个部门到sys_user_department表
if (!matchedDeptIds.isEmpty()) {
Long primaryDeptId = matchedDeptIds.get(0);
setUserDepartments(user.getId(), matchedDeptIds, primaryDeptId);
}
log.info("钉钉用户创建成功: userId={}, username={}, needSelectDept={}",
user.getId(), user.getUsername(), user.getDeptId() == null);
// 如果需要选择部门(系统无任何可用部门的情况),抛出特殊异常
if (user.getDeptId() == null) {
String deptInfo = dingDepartments.isEmpty() ? "无钉钉部门信息" :
dingDepartments.stream()
.map(d -> String.valueOf(d.get("name")))
.collect(Collectors.joining(","));
throw new BusinessException(ResultCode.PARAM_ERROR,
"NEED_SELECT_DEPT:" + user.getId() + ":" + deptInfo);
}
} else {
// 已有用户:同步更新信息
log.info("钉钉用户已存在,同步更新: userId={}, 当前roleIds={}", user.getId(), user.getRoleIds());
syncUserFromDingTalk(user, detailedInfo, dingtalkUserId, dingDepartments);
log.info("钉钉用户同步完成: userId={}, 最终roleIds={}", user.getId(), user.getRoleIds());
}
// 5. 生成Token
String token = jwtUtil.generateToken(user.getId(), user.getUsername());
String refreshToken = jwtUtil.generateRefreshToken(user.getId(), user.getUsername());
return LoginVO.builder()
.token(token)
.refreshToken(refreshToken)
.userId(user.getId())
.username(user.getUsername())
.realName(user.getRealName())
.avatar(user.getAvatar())
.roleIds(user.getRoleIds())
.build();
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
log.error("钉钉登录失败", e);
throw new BusinessException(ResultCode.DINGTALK_AUTH_ERROR, "钉钉登录失败: " + e.getMessage());
}
}
/**
* 完成钉钉用户设置(选择部门)
*/
@Transactional(rollbackFor = Exception.class)
public LoginVO completeDingTalkUserSetup(Long userId, Long deptId) {
log.info("完成钉钉用户设置: userId={}, deptId={}", userId, deptId);
// 1. 获取用户
User user = userMapper.selectById(userId);
if (user == null) {
throw new BusinessException(ResultCode.USER_NOT_EXIST);
}
// 2. 获取部门
Department dept = departmentMapper.selectById(deptId);
if (dept == null) {
throw new BusinessException(ResultCode.DEPT_NOT_EXIST);
}
// 3. 更新用户部门和角色
user.setDeptId(deptId);
user.setDeptName(dept.getDeptName());
// 根据部门类型设置角色
user.setRoleIds("receiving".equals(dept.getDeptType()) ? "5" : "6");
user.setStatus(1); // 启用用户
userMapper.updateById(user);
log.info("钉钉用户部门设置完成: userId={}, deptName={}, roleIds={}",
userId, dept.getDeptName(), user.getRoleIds());
// 4. 生成Token并返回
String token = jwtUtil.generateToken(user.getId(), user.getUsername());
String refreshToken = jwtUtil.generateRefreshToken(user.getId(), user.getUsername());
return LoginVO.builder()
.token(token)
.refreshToken(refreshToken)
.userId(user.getId())
.username(user.getUsername())
.realName(user.getRealName())
.avatar(user.getAvatar())
.roleIds(user.getRoleIds())
.build();
}
/**
* 用户登录
*/
public LoginVO login(LoginDTO loginDTO) {
String loginType = loginDTO.getLoginType();
if ("password".equals(loginType)) {
// 用户名密码登录
User user = getUserByUsername(loginDTO.getUsername());
if (user == null) {
throw new BusinessException(ResultCode.USER_NOT_EXIST);
}
if (!passwordEncoder.matches(loginDTO.getPassword(), user.getPassword())) {
throw new BusinessException(ResultCode.PASSWORD_ERROR);
}
if (user.getStatus() == 0) {
throw new BusinessException(ResultCode.USER_DISABLED);
}
// 生成Token
String token = jwtUtil.generateToken(user.getId(), user.getUsername());
String refreshToken = jwtUtil.generateRefreshToken(user.getId(), user.getUsername());
return LoginVO.builder()
.token(token)
.refreshToken(refreshToken)
.userId(user.getId())
.username(user.getUsername())
.realName(user.getRealName())
.avatar(user.getAvatar())
.roleIds(user.getRoleIds())
.build();
} else if ("dingtalk".equals(loginType)) {
// 钉钉OAuth 2.0登录
return handleDingTalkLogin(loginDTO.getAuthCode());
} else if ("casdoor".equals(loginType)) {
// Casdoor登录 - 暂时返回错误,待后续实现
throw new BusinessException(ResultCode.PARAM_ERROR, "Casdoor登录功能尚未开放");
} else {
throw new BusinessException(ResultCode.PARAM_ERROR, "不支持的登录类型");
}
}
/**
* 根据Token获取用户信息
*/
public UserInfoVO getUserInfoByToken(String token) {
if (!StringUtils.hasText(token)) {
throw new BusinessException(ResultCode.UNAUTHORIZED);
}
if (!jwtUtil.validateToken(token)) {
throw new BusinessException(ResultCode.UNAUTHORIZED, "Token无效或已过期");
}
String username = jwtUtil.getUsernameFromToken(token);
log.info("getUserInfoByToken - username: {}", username);
User user = getUserByUsername(username);
if (user == null) {
throw new BusinessException(ResultCode.USER_NOT_EXIST);
}
log.info("getUserInfoByToken - 用户: id={}, username={}, deptId={}, deptName={}",
user.getId(), user.getUsername(), user.getDeptId(), user.getDeptName());
// 获取用户角色代码列表
List<String> roleCodes = List.of();
try {
var roles = roleService.getRolesByUserId(user.getId());
roleCodes = roles.stream()
.map(role -> role.getRoleCode())
.toList();
log.info("getUserInfoByToken - 用户角色: {}", roleCodes);
} catch (Exception e) {
log.warn("获取用户角色失败: userId={}, error={}", user.getId(), e.getMessage());
}
// 检查是否为部门负责人
Integer isDepartmentLeader = 0;
try {
boolean isLeader = departmentService.isUserDepartmentLeader(user.getId());
isDepartmentLeader = isLeader ? 1 : 0;
log.info("getUserInfoByToken - 是否为部门负责人: userId={}, isLeader={}", user.getId(), isLeader);
} catch (Exception e) {
log.warn("检查部门负责人状态失败: userId={}, error={}", user.getId(), e.getMessage());
}
// 获取用户的所有部门列表
List<UserDepartmentVO> departments = List.of();
try {
List<UserDepartment> userDepts = userDepartmentMapper.selectList(
new LambdaQueryWrapper<UserDepartment>()
.eq(UserDepartment::getUserId, user.getId())
);
if (!userDepts.isEmpty()) {
List<Long> deptIds = userDepts.stream()
.map(UserDepartment::getDeptId)
.collect(Collectors.toList());
List<Department> depts = departmentMapper.selectBatchIds(deptIds);
// 构建部门VO列表,保持isPrimary顺序
Map<Long, Department> deptMap = depts.stream()
.collect(Collectors.toMap(Department::getId, d -> d));
departments = userDepts.stream()
.map(ud -> {
Department dept = deptMap.get(ud.getDeptId());
if (dept != null) {
return UserDepartmentVO.builder()
.deptId(dept.getId())
.deptName(dept.getDeptName())
.isPrimary(ud.getIsPrimary())
.build();
}
return null;
})
.filter(d -> d != null)
.collect(Collectors.toList());
}
} catch (Exception e) {
log.warn("获取用户部门列表失败: userId={}, error={}", user.getId(), e.getMessage());
}
UserInfoVO userInfoVO = UserInfoVO.builder()
.id(user.getId())
.username(user.getUsername())
.realName(user.getRealName())
.email(user.getEmail())
.phone(user.getPhone())
.avatar(user.getAvatar())
.deptId(user.getDeptId())
.deptName(user.getDeptName())
.departments(departments)
.roleIds(user.getRoleIds())
.roles(roleCodes)
.status(user.getStatus())
.isDepartmentLeader(isDepartmentLeader)
.build();
log.info("getUserInfoByToken - 返回VO: id={}, deptId={}, deptName={}, roleIds={}, roles={}, isDepartmentLeader={}",
userInfoVO.getId(), userInfoVO.getDeptId(), userInfoVO.getDeptName(), userInfoVO.getRoleIds(), roleCodes, isDepartmentLeader);
return userInfoVO;
}
/**
* 刷新Token
*/
public LoginVO refreshToken(String authHeader) {
String token = jwtUtil.extractTokenFromHeader(authHeader);
if (!StringUtils.hasText(token)) {
throw new BusinessException(ResultCode.UNAUTHORIZED);
}
if (!jwtUtil.validateToken(token)) {
throw new BusinessException(ResultCode.UNAUTHORIZED, "Token无效或已过期");
}
String username = jwtUtil.getUsernameFromToken(token);
Long userId = jwtUtil.getUserIdFromToken(token);
User user = getUserById(userId);
if (user == null) {
throw new BusinessException(ResultCode.USER_NOT_EXIST);
}
// 生成新的Token
String newToken = jwtUtil.generateToken(userId, username);
String newRefreshToken = jwtUtil.generateRefreshToken(userId, username);
return LoginVO.builder()
.token(newToken)
.refreshToken(newRefreshToken)
.userId(user.getId())
.username(user.getUsername())
.realName(user.getRealName())
.avatar(user.getAvatar())
.roleIds(user.getRoleIds())
.build();
}
// ==================== 多部门支持方法 ====================
/**
* 获取用户的所有部门ID列表
*/
public List<Long> getUserDepartmentIds(Long userId) {
List<UserDepartment> userDepts = userDepartmentMapper.selectList(
new LambdaQueryWrapper<UserDepartment>()
.eq(UserDepartment::getUserId, userId)
);
return userDepts.stream()
.map(UserDepartment::getDeptId)
.collect(Collectors.toList());
}
/**
* 获取用户的所有部门信息
*/
public List<Department> getUserDepartments(Long userId) {
List<Long> deptIds = getUserDepartmentIds(userId);
if (deptIds.isEmpty()) {
return List.of();
}
return departmentMapper.selectBatchIds(deptIds);
}
/**
* 添加用户到部门(如果不存在则添加)
*/
@Transactional(rollbackFor = Exception.class)
public void addUserToDepartment(Long userId, Long deptId, boolean isPrimary) {
// 检查是否已存在
UserDepartment exist = userDepartmentMapper.selectOne(
new LambdaQueryWrapper<UserDepartment>()
.eq(UserDepartment::getUserId, userId)
.eq(UserDepartment::getDeptId, deptId)
);
if (exist == null) {
UserDepartment userDept = new UserDepartment();
userDept.setUserId(userId);
userDept.setDeptId(deptId);
userDept.setIsPrimary(isPrimary ? 1 : 0);
userDepartmentMapper.insert(userDept);
log.info("添加用户到部门: userId={}, deptId={}, isPrimary={}", userId, deptId, isPrimary);
} else if (isPrimary && exist.getIsPrimary() == 0) {
// 更新为主部门
exist.setIsPrimary(1);
userDepartmentMapper.updateById(exist);
log.info("设置为主部门: userId={}, deptId={}", userId, deptId);
}
// 如果设置为主部门,需要更新sys_user表的dept_id
if (isPrimary) {
Department dept = departmentMapper.selectById(deptId);
if (dept != null) {
User user = userMapper.selectById(userId);
if (user != null) {
user.setDeptId(deptId);
user.setDeptName(dept.getDeptName());
userMapper.updateById(user);
}
}
// 将其他部门设置为非主部门
userDepartmentMapper.update(null,
new LambdaUpdateWrapper<UserDepartment>()
.eq(UserDepartment::getUserId, userId)
.ne(UserDepartment::getDeptId, deptId)
.set(UserDepartment::getIsPrimary, 0)
);
}
}
/**
* 从部门中移除用户
*/
@Transactional(rollbackFor = Exception.class)
public void removeUserFromDepartment(Long userId, Long deptId) {
// 检查是否为主部门
UserDepartment userDept = userDepartmentMapper.selectOne(
new LambdaQueryWrapper<UserDepartment>()
.eq(UserDepartment::getUserId, userId)
.eq(UserDepartment::getDeptId, deptId)
);
if (userDept != null && userDept.getIsPrimary() == 1) {
// 如果是主部门,需要设置另一个部门为主部门,或清空主部门
List<UserDepartment> otherDepts = userDepartmentMapper.selectList(
new LambdaQueryWrapper<UserDepartment>()
.eq(UserDepartment::getUserId, userId)
.ne(UserDepartment::getDeptId, deptId)
.last("LIMIT 1")
);
if (!otherDepts.isEmpty()) {
// 设置第一个其他部门为主部门
UserDepartment newPrimary = otherDepts.get(0);
newPrimary.setIsPrimary(1);
userDepartmentMapper.updateById(newPrimary);
Department newDept = departmentMapper.selectById(newPrimary.getDeptId());
if (newDept != null) {
User user = userMapper.selectById(userId);
if (user != null) {
user.setDeptId(newDept.getId());
user.setDeptName(newDept.getDeptName());
userMapper.updateById(user);
}
}
} else {
// 没有其他部门,清空主部门
User user = userMapper.selectById(userId);
if (user != null) {
user.setDeptId(null);
user.setDeptName(null);
userMapper.updateById(user);
}
}
}
// 删除关联
userDepartmentMapper.delete(
new LambdaQueryWrapper<UserDepartment>()
.eq(UserDepartment::getUserId, userId)
.eq(UserDepartment::getDeptId, deptId)
);
log.info("从部门移除用户: userId={}, deptId={}", userId, deptId);
}
/**
* 设置用户的部门列表(替换现有部门)
*/
@Transactional(rollbackFor = Exception.class)
public void setUserDepartments(Long userId, List<Long> deptIds, Long primaryDeptId) {
// 删除所有现有关联
userDepartmentMapper.delete(
new LambdaQueryWrapper<UserDepartment>()
.eq(UserDepartment::getUserId, userId)
);
// 添加新关联
if (deptIds != null && !deptIds.isEmpty()) {
for (Long deptId : deptIds) {
boolean isPrimary = deptId.equals(primaryDeptId);
addUserToDepartment(userId, deptId, isPrimary);
}
} else {
// 清空部门
User user = userMapper.selectById(userId);
if (user != null) {
user.setDeptId(null);
user.setDeptName(null);
userMapper.updateById(user);
}
}
}
/**
* 匹配多个系统部门(根据钉钉部门列表)
*/
private List<Long> matchSystemDepartments(List<Map<String, Object>> dingDepartments) {
if (dingDepartments == null || dingDepartments.isEmpty()) {
return List.of();
}
// 获取所有系统部门
List<Department> systemDepts = departmentMapper.selectList(
new LambdaQueryWrapper<Department>()
.eq(Department::getStatus, 1)
.eq(Department::getDeleted, 0)
);
List<Long> matchedDeptIds = new ArrayList<>();
for (Map<String, Object> dingDept : dingDepartments) {
String dingDeptName = (String) dingDept.get("name");
Object dingDeptIdObj = dingDept.get("dept_id");
String dingDeptId = dingDeptIdObj != null ? dingDeptIdObj.toString() : null;
Department matched = null;
// 1. 优先通过钉钉部门ID精确匹配
if (dingDeptId != null) {
matched = systemDepts.stream()
.filter(d -> dingDeptId.equals(d.getDingDeptId()))
.findFirst()
.orElse(null);
if (matched != null) {
log.info("通过钉钉部门ID精确匹配: {} -> {}", dingDeptId, matched.getDeptName());
}
}
// 2. 通过部门名称模糊匹配
if (matched == null && dingDeptName != null) {
matched = systemDepts.stream()
.filter(d -> d.getDeptName().contains(dingDeptName) ||
dingDeptName.contains(d.getDeptName()))
.findFirst()
.orElse(null);
if (matched != null) {
log.info("通过部门名称模糊匹配: {} -> {}", dingDeptName, matched.getDeptName());
}
}
if (matched != null && !matchedDeptIds.contains(matched.getId())) {
matchedDeptIds.add(matched.getId());
}
}
return matchedDeptIds;
}
/**
* 获取设计师列表及工作量
* @param deptId 部门ID(可选,如果不传则返回所有设计师)
* @return 设计师列表,包含每个设计师的进行中任务数量
*/
public List<Map<String, Object>> getDesignersWithWorkload(Long deptId) {
log.info("获取设计师列表及工作量: deptId={}", deptId);
// 查询用户列表
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getStatus, 1); // 只查询启用的用户
queryWrapper.eq(User::getDeleted, 0); // 未删除
if (deptId != null) {
queryWrapper.eq(User::getDeptId, deptId);
}
queryWrapper.orderByAsc(User::getRealName);
List<User> users = userMapper.selectList(queryWrapper);
// 构建返回结果
List<Map<String, Object>> result = new ArrayList<>();
for (User user : users) {
Map<String, Object> designerInfo = new HashMap<>();
designerInfo.put("id", user.getId());
designerInfo.put("username", user.getUsername());
designerInfo.put("realName", user.getRealName());
designerInfo.put("deptId", user.getDeptId());
// 查询进行中的任务数量(包括普通任务和子任务)
int inProgressCount = 0;
if (taskMapper != null) {
// 查询普通任务
Long normalTaskCount = taskMapper.selectCount(
new LambdaQueryWrapper<com.designhub.entity.Task>()
.eq(com.designhub.entity.Task::getReceiverId, user.getId())
.eq(com.designhub.entity.Task::getStatus, "in_progress")
.eq(com.designhub.entity.Task::getDeleted, 0)
);
// 查询子任务(TaskItem)
Long subTaskCount = taskMapper.countSubTasksByReceiverId(user.getId());
inProgressCount = (normalTaskCount != null ? normalTaskCount.intValue() : 0) +
(subTaskCount != null ? subTaskCount.intValue() : 0);
}
designerInfo.put("inProgressCount", inProgressCount);
// 获取部门名称
if (user.getDeptId() != null) {
Department dept = departmentMapper.selectById(user.getDeptId());
if (dept != null) {
designerInfo.put("deptName", dept.getDeptName());
}
}
// 获取最早预计完成时间(查询该设计师进行中任务的最早deadline)
if (taskMapper != null && inProgressCount > 0) {
// 查询普通任务的最早deadline
LambdaQueryWrapper<com.designhub.entity.Task> taskQuery = new LambdaQueryWrapper<>();
taskQuery.eq(com.designhub.entity.Task::getReceiverId, user.getId())
.eq(com.designhub.entity.Task::getStatus, "in_progress")
.eq(com.designhub.entity.Task::getDeleted, 0)
.isNotNull(com.designhub.entity.Task::getDeadline)
.orderByAsc(com.designhub.entity.Task::getDeadline)
.last("LIMIT 1");
com.designhub.entity.Task earliestTask = taskMapper.selectOne(taskQuery);
if (earliestTask != null && earliestTask.getDeadline() != null) {
designerInfo.put("earliestDeadline", earliestTask.getDeadline());
}
}
result.add(designerInfo);
}
log.info("查询到{}个设计师", result.size());
return result;
}
/**
* 设置/取消管理员
*/
@Transactional(rollbackFor = Exception.class)
public boolean setAdmin(Long userId, Integer isAdmin) {
log.info("设置用户管理员身份: userId={}, isAdmin={}", userId, isAdmin);
User user = getUserById(userId);
if (user == null) {
throw new BusinessException(ResultCode.NOT_FOUND, "用户不存在");
}
// 更新管理员标识
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(User::getId, userId);
updateWrapper.set(User::getIsAdmin, isAdmin != null ? isAdmin : 0);
boolean result = userMapper.update(null, updateWrapper) > 0;
log.info("设置管理员身份结果: userId={}, isAdmin={}, result={}", userId, isAdmin, result);
return result;
}
/**
* 检查用户是否是管理员
*/
public boolean isAdminUser(Long userId) {
User user = userMapper.selectById(userId);
return user != null && user.getIsAdmin() != null && user.getIsAdmin() == 1;
}
}