diff --git a/src/main/java/com/tuoheng/airport/uaa/application/assembler/UserDTOAssembler.java b/src/main/java/com/tuoheng/airport/uaa/application/assembler/UserDTOAssembler.java new file mode 100644 index 0000000..95fffb1 --- /dev/null +++ b/src/main/java/com/tuoheng/airport/uaa/application/assembler/UserDTOAssembler.java @@ -0,0 +1,86 @@ +package com.tuoheng.airport.uaa.application.assembler; + +import com.tuoheng.airport.uaa.application.dto.UserDTO; +import com.tuoheng.airport.uaa.domain.model.user.User; +import com.tuoheng.airport.uaa.domain.model.user.UserId; +import com.tuoheng.airport.uaa.domain.model.user.UserProfile; +import com.tuoheng.airport.uaa.domain.model.user.UserStatus; + +/** + * 用户DTO-DO转换器 + * + * @author Tuoheng Team + */ +public class UserDTOAssembler { + + /** + * DTO转DO + */ + public static User toDomain(UserDTO dto) { + if (dto == null) { + return null; + } + + User user = new User(); + if (dto.getUserId() != null) { + user.setUserId(UserId.of(dto.getUserId())); + } + user.setUsername(dto.getUsername()); + + // 转换用户资料 + UserProfile profile = new UserProfile(); + profile.setRealName(dto.getRealName()); + profile.setNickname(dto.getNickname()); + profile.setMobile(dto.getMobile()); + profile.setEmail(dto.getEmail()); + profile.setAvatar(dto.getAvatar()); + profile.setGender(dto.getGender()); + profile.setDeptId(dto.getDeptId()); + profile.setPositionId(dto.getPositionId()); + user.setProfile(profile); + + user.setStatus(UserStatus.of(dto.getStatus())); + user.setTenantId(dto.getTenantId()); + user.setCreateTime(dto.getCreateTime()); + user.setUpdateTime(dto.getUpdateTime()); + + return user; + } + + /** + * DO转DTO + */ + public static UserDTO toDTO(User user) { + if (user == null) { + return null; + } + + UserDTO dto = new UserDTO(); + if (user.getUserId() != null) { + dto.setUserId(user.getUserId().getId()); + } + dto.setUsername(user.getUsername()); + + // 转换用户资料 + if (user.getProfile() != null) { + UserProfile profile = user.getProfile(); + dto.setRealName(profile.getRealName()); + dto.setNickname(profile.getNickname()); + dto.setMobile(profile.getMobile()); + dto.setEmail(profile.getEmail()); + dto.setAvatar(profile.getAvatar()); + dto.setGender(profile.getGender()); + dto.setDeptId(profile.getDeptId()); + dto.setPositionId(profile.getPositionId()); + } + + if (user.getStatus() != null) { + dto.setStatus(user.getStatus().getCode()); + } + dto.setTenantId(user.getTenantId()); + dto.setCreateTime(user.getCreateTime()); + dto.setUpdateTime(user.getUpdateTime()); + + return dto; + } +} diff --git a/src/main/java/com/tuoheng/airport/uaa/application/dto/UserDTO.java b/src/main/java/com/tuoheng/airport/uaa/application/dto/UserDTO.java new file mode 100644 index 0000000..edcd3b3 --- /dev/null +++ b/src/main/java/com/tuoheng/airport/uaa/application/dto/UserDTO.java @@ -0,0 +1,84 @@ +package com.tuoheng.airport.uaa.application.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 用户DTO + * + * @author Tuoheng Team + */ +@Data +public class UserDTO { + + /** + * 用户ID + */ + private Long userId; + + /** + * 用户名 + */ + private String username; + + /** + * 真实姓名 + */ + private String realName; + + /** + * 昵称 + */ + private String nickname; + + /** + * 手机号 + */ + private String mobile; + + /** + * 邮箱 + */ + private String email; + + /** + * 头像 + */ + private String avatar; + + /** + * 性别:0-未知,1-男,2-女 + */ + private Integer gender; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 职位ID + */ + private Long positionId; + + /** + * 用户状态:0-正常,1-锁定,2-禁用 + */ + private Integer status; + + /** + * 租户ID + */ + private Long tenantId; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; +} diff --git a/src/main/java/com/tuoheng/airport/uaa/application/service/UserApplicationService.java b/src/main/java/com/tuoheng/airport/uaa/application/service/UserApplicationService.java new file mode 100644 index 0000000..ec6c4c0 --- /dev/null +++ b/src/main/java/com/tuoheng/airport/uaa/application/service/UserApplicationService.java @@ -0,0 +1,193 @@ +package com.tuoheng.airport.uaa.application.service; + +import com.tuoheng.airport.uaa.application.assembler.UserDTOAssembler; +import com.tuoheng.airport.uaa.application.dto.UserDTO; +import com.tuoheng.airport.uaa.domain.model.user.User; +import com.tuoheng.airport.uaa.domain.model.user.UserId; +import com.tuoheng.airport.uaa.domain.model.user.UserProfile; +import com.tuoheng.airport.uaa.domain.repository.UserRepository; +import com.tuoheng.airport.common.exception.BusinessException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 用户应用服务 + * + * @author Tuoheng Team + */ +@Service +public class UserApplicationService { + + @Resource + private UserRepository userRepository; + + /** + * 创建用户 + */ + @Transactional(rollbackFor = Exception.class) + public UserDTO createUser(UserDTO userDTO, String password) { + // 检查用户名是否已存在 + if (userRepository.existsByUsername(userDTO.getUsername())) { + throw new BusinessException("用户名已存在"); + } + + // 检查手机号是否已存在 + if (userDTO.getMobile() != null && userRepository.existsByMobile(userDTO.getMobile())) { + throw new BusinessException("手机号已存在"); + } + + // 检查邮箱是否已存在 + if (userDTO.getEmail() != null && userRepository.existsByEmail(userDTO.getEmail())) { + throw new BusinessException("邮箱已存在"); + } + + // 创建用户资料 + UserProfile profile = new UserProfile(); + profile.setRealName(userDTO.getRealName()); + profile.setNickname(userDTO.getNickname()); + profile.setMobile(userDTO.getMobile()); + profile.setEmail(userDTO.getEmail()); + profile.setAvatar(userDTO.getAvatar()); + profile.setGender(userDTO.getGender()); + profile.setDeptId(userDTO.getDeptId()); + profile.setPositionId(userDTO.getPositionId()); + + // 创建用户(使用工厂方法) + User user = User.create( + userDTO.getUsername(), + password, // 密码应该在这里加密 + profile, + userDTO.getTenantId() + ); + + // 保存用户 + User savedUser = userRepository.save(user); + + // 发布用户创建事件(这里可以通过事件总线发布) + // eventPublisher.publish(UserCreatedEvent.create(...)); + + return UserDTOAssembler.toDTO(savedUser); + } + + /** + * 更新用户 + */ + @Transactional(rollbackFor = Exception.class) + public UserDTO updateUser(Long userId, UserDTO userDTO) { + // 查找用户 + User user = userRepository.findById(UserId.of(userId)) + .orElseThrow(() -> new BusinessException("用户不存在")); + + // 更新用户资料 + UserProfile profile = new UserProfile(); + profile.setRealName(userDTO.getRealName()); + profile.setNickname(userDTO.getNickname()); + profile.setMobile(userDTO.getMobile()); + profile.setEmail(userDTO.getEmail()); + profile.setAvatar(userDTO.getAvatar()); + profile.setGender(userDTO.getGender()); + profile.setDeptId(userDTO.getDeptId()); + profile.setPositionId(userDTO.getPositionId()); + + user.updateProfile(profile); + + // 保存用户 + User updatedUser = userRepository.save(user); + + return UserDTOAssembler.toDTO(updatedUser); + } + + /** + * 删除用户 + */ + @Transactional(rollbackFor = Exception.class) + public void deleteUser(Long userId) { + userRepository.delete(UserId.of(userId)); + } + + /** + * 根据ID查询用户 + */ + public UserDTO getUserById(Long userId) { + User user = userRepository.findById(UserId.of(userId)) + .orElseThrow(() -> new BusinessException("用户不存在")); + return UserDTOAssembler.toDTO(user); + } + + /** + * 根据用户名查询用户 + */ + public UserDTO getUserByUsername(String username) { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new BusinessException("用户不存在")); + return UserDTOAssembler.toDTO(user); + } + + /** + * 查询所有用户 + */ + public List getAllUsers() { + List users = userRepository.findAll(); + return users.stream() + .map(UserDTOAssembler::toDTO) + .collect(Collectors.toList()); + } + + /** + * 分页查询用户 + */ + public List getUsersByPage(int pageNum, int pageSize) { + List users = userRepository.findByPage(pageNum, pageSize); + return users.stream() + .map(UserDTOAssembler::toDTO) + .collect(Collectors.toList()); + } + + /** + * 锁定用户 + */ + @Transactional(rollbackFor = Exception.class) + public void lockUser(Long userId) { + User user = userRepository.findById(UserId.of(userId)) + .orElseThrow(() -> new BusinessException("用户不存在")); + user.lock(); + userRepository.save(user); + } + + /** + * 解锁用户 + */ + @Transactional(rollbackFor = Exception.class) + public void unlockUser(Long userId) { + User user = userRepository.findById(UserId.of(userId)) + .orElseThrow(() -> new BusinessException("用户不存在")); + user.unlock(); + userRepository.save(user); + } + + /** + * 禁用用户 + */ + @Transactional(rollbackFor = Exception.class) + public void disableUser(Long userId) { + User user = userRepository.findById(UserId.of(userId)) + .orElseThrow(() -> new BusinessException("用户不存在")); + user.disable(); + userRepository.save(user); + } + + /** + * 启用用户 + */ + @Transactional(rollbackFor = Exception.class) + public void enableUser(Long userId) { + User user = userRepository.findById(UserId.of(userId)) + .orElseThrow(() -> new BusinessException("用户不存在")); + user.enable(); + userRepository.save(user); + } +} diff --git a/src/main/java/com/tuoheng/airport/uaa/domain/event/UserCreatedEvent.java b/src/main/java/com/tuoheng/airport/uaa/domain/event/UserCreatedEvent.java new file mode 100644 index 0000000..f5e2b8a --- /dev/null +++ b/src/main/java/com/tuoheng/airport/uaa/domain/event/UserCreatedEvent.java @@ -0,0 +1,56 @@ +package com.tuoheng.airport.uaa.domain.event; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 用户创建事件 + * + * @author Tuoheng Team + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class UserCreatedEvent { + + /** + * 事件ID + */ + private String eventId; + + /** + * 用户ID + */ + private Long userId; + + /** + * 用户名 + */ + private String username; + + /** + * 租户ID + */ + private Long tenantId; + + /** + * 事件时间 + */ + private LocalDateTime eventTime; + + /** + * 创建事件 + */ + public static UserCreatedEvent create(Long userId, String username, Long tenantId) { + UserCreatedEvent event = new UserCreatedEvent(); + event.setEventId(java.util.UUID.randomUUID().toString()); + event.setUserId(userId); + event.setUsername(username); + event.setTenantId(tenantId); + event.setEventTime(LocalDateTime.now()); + return event; + } +} diff --git a/src/main/java/com/tuoheng/airport/uaa/domain/model/user/User.java b/src/main/java/com/tuoheng/airport/uaa/domain/model/user/User.java new file mode 100644 index 0000000..f158eee --- /dev/null +++ b/src/main/java/com/tuoheng/airport/uaa/domain/model/user/User.java @@ -0,0 +1,132 @@ +package com.tuoheng.airport.uaa.domain.model.user; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 用户聚合根 + * + * @author Tuoheng Team + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class User { + + /** + * 用户ID(聚合根标识) + */ + private UserId userId; + + /** + * 用户名 + */ + private String username; + + /** + * 密码(加密后) + */ + private String password; + + /** + * 用户资料 + */ + private UserProfile profile; + + /** + * 用户状态 + */ + private UserStatus status; + + /** + * 租户ID + */ + private Long tenantId; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 创建用户(工厂方法) + */ + public static User create(String username, String password, UserProfile profile, Long tenantId) { + User user = new User(); + user.setUserId(UserId.generate()); + user.setUsername(username); + user.setPassword(password); + user.setProfile(profile); + user.setStatus(UserStatus.NORMAL); + user.setTenantId(tenantId); + user.setCreateTime(LocalDateTime.now()); + user.setUpdateTime(LocalDateTime.now()); + return user; + } + + /** + * 修改密码 + */ + public void changePassword(String oldPassword, String newPassword) { + if (!this.password.equals(oldPassword)) { + throw new IllegalArgumentException("原密码错误"); + } + this.password = newPassword; + this.updateTime = LocalDateTime.now(); + } + + /** + * 锁定用户 + */ + public void lock() { + this.status = UserStatus.LOCKED; + this.updateTime = LocalDateTime.now(); + } + + /** + * 解锁用户 + */ + public void unlock() { + this.status = UserStatus.NORMAL; + this.updateTime = LocalDateTime.now(); + } + + /** + * 禁用用户 + */ + public void disable() { + this.status = UserStatus.DISABLED; + this.updateTime = LocalDateTime.now(); + } + + /** + * 启用用户 + */ + public void enable() { + this.status = UserStatus.NORMAL; + this.updateTime = LocalDateTime.now(); + } + + /** + * 更新用户资料 + */ + public void updateProfile(UserProfile profile) { + this.profile = profile; + this.updateTime = LocalDateTime.now(); + } + + /** + * 检查用户是否可用 + */ + public boolean isAvailable() { + return this.status == UserStatus.NORMAL; + } +} diff --git a/src/main/java/com/tuoheng/airport/uaa/domain/model/user/UserId.java b/src/main/java/com/tuoheng/airport/uaa/domain/model/user/UserId.java new file mode 100644 index 0000000..b855640 --- /dev/null +++ b/src/main/java/com/tuoheng/airport/uaa/domain/model/user/UserId.java @@ -0,0 +1,35 @@ +package com.tuoheng.airport.uaa.domain.model.user; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +/** + * 用户ID值对象 + * + * @author Tuoheng Team + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class UserId { + + private Long id; + + /** + * 生成新的用户ID + */ + public static UserId generate() { + // 实际项目中可以使用雪花算法等分布式ID生成策略 + return new UserId(System.currentTimeMillis()); + } + + /** + * 从Long创建UserId + */ + public static UserId of(Long id) { + return new UserId(id); + } +} diff --git a/src/main/java/com/tuoheng/airport/uaa/domain/model/user/UserProfile.java b/src/main/java/com/tuoheng/airport/uaa/domain/model/user/UserProfile.java new file mode 100644 index 0000000..32b9a18 --- /dev/null +++ b/src/main/java/com/tuoheng/airport/uaa/domain/model/user/UserProfile.java @@ -0,0 +1,76 @@ +package com.tuoheng.airport.uaa.domain.model.user; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 用户资料值对象 + * + * @author Tuoheng Team + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class UserProfile { + + /** + * 真实姓名 + */ + private String realName; + + /** + * 昵称 + */ + private String nickname; + + /** + * 手机号 + */ + private String mobile; + + /** + * 邮箱 + */ + private String email; + + /** + * 头像URL + */ + private String avatar; + + /** + * 性别:0-未知,1-男,2-女 + */ + private Integer gender; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 职位ID + */ + private Long positionId; + + /** + * 验证手机号格式 + */ + public boolean isValidMobile() { + if (mobile == null || mobile.isEmpty()) { + return false; + } + return mobile.matches("^1[3-9]\\d{9}$"); + } + + /** + * 验证邮箱格式 + */ + public boolean isValidEmail() { + if (email == null || email.isEmpty()) { + return false; + } + return email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$"); + } +} diff --git a/src/main/java/com/tuoheng/airport/uaa/domain/model/user/UserStatus.java b/src/main/java/com/tuoheng/airport/uaa/domain/model/user/UserStatus.java new file mode 100644 index 0000000..4673d09 --- /dev/null +++ b/src/main/java/com/tuoheng/airport/uaa/domain/model/user/UserStatus.java @@ -0,0 +1,30 @@ +package com.tuoheng.airport.uaa.domain.model.user; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 用户状态枚举 + * + * @author Tuoheng Team + */ +@Getter +@AllArgsConstructor +public enum UserStatus { + + NORMAL(0, "正常"), + LOCKED(1, "锁定"), + DISABLED(2, "禁用"); + + private final Integer code; + private final String desc; + + public static UserStatus of(Integer code) { + for (UserStatus status : values()) { + if (status.getCode().equals(code)) { + return status; + } + } + return NORMAL; + } +} diff --git a/src/main/java/com/tuoheng/airport/uaa/domain/repository/UserRepository.java b/src/main/java/com/tuoheng/airport/uaa/domain/repository/UserRepository.java new file mode 100644 index 0000000..d9591e2 --- /dev/null +++ b/src/main/java/com/tuoheng/airport/uaa/domain/repository/UserRepository.java @@ -0,0 +1,75 @@ +package com.tuoheng.airport.uaa.domain.repository; + +import com.tuoheng.airport.uaa.domain.model.user.User; +import com.tuoheng.airport.uaa.domain.model.user.UserId; + +import java.util.List; +import java.util.Optional; + +/** + * 用户仓储接口 + * + * @author Tuoheng Team + */ +public interface UserRepository { + + /** + * 保存用户 + */ + User save(User user); + + /** + * 根据ID查找用户 + */ + Optional findById(UserId userId); + + /** + * 根据用户名查找用户 + */ + Optional findByUsername(String username); + + /** + * 根据手机号查找用户 + */ + Optional findByMobile(String mobile); + + /** + * 根据邮箱查找用户 + */ + Optional findByEmail(String email); + + /** + * 查询所有用户 + */ + List findAll(); + + /** + * 分页查询用户 + */ + List findByPage(int pageNum, int pageSize); + + /** + * 根据租户ID查询用户 + */ + List findByTenantId(Long tenantId); + + /** + * 删除用户 + */ + void delete(UserId userId); + + /** + * 检查用户名是否存在 + */ + boolean existsByUsername(String username); + + /** + * 检查手机号是否存在 + */ + boolean existsByMobile(String mobile); + + /** + * 检查邮箱是否存在 + */ + boolean existsByEmail(String email); +} diff --git a/src/main/java/com/tuoheng/airport/uaa/domain/service/AuthDomainService.java b/src/main/java/com/tuoheng/airport/uaa/domain/service/AuthDomainService.java new file mode 100644 index 0000000..3eedf7d --- /dev/null +++ b/src/main/java/com/tuoheng/airport/uaa/domain/service/AuthDomainService.java @@ -0,0 +1,41 @@ +package com.tuoheng.airport.uaa.domain.service; + +import com.tuoheng.airport.uaa.domain.model.user.User; + +/** + * 认证领域服务接口 + * + * @author Tuoheng Team + */ +public interface AuthDomainService { + + /** + * 用户登录 + */ + String login(String username, String password); + + /** + * 验证Token + */ + boolean validateToken(String token); + + /** + * 刷新Token + */ + String refreshToken(String token); + + /** + * 用户登出 + */ + void logout(String token); + + /** + * 验证密码 + */ + boolean validatePassword(User user, String password); + + /** + * 加密密码 + */ + String encryptPassword(String password); +} diff --git a/src/main/java/com/tuoheng/airport/uaa/infrastructure/persistence/converter/UserConverter.java b/src/main/java/com/tuoheng/airport/uaa/infrastructure/persistence/converter/UserConverter.java new file mode 100644 index 0000000..eaa6706 --- /dev/null +++ b/src/main/java/com/tuoheng/airport/uaa/infrastructure/persistence/converter/UserConverter.java @@ -0,0 +1,86 @@ +package com.tuoheng.airport.uaa.infrastructure.persistence.converter; + +import com.tuoheng.airport.uaa.domain.model.user.User; +import com.tuoheng.airport.uaa.domain.model.user.UserId; +import com.tuoheng.airport.uaa.domain.model.user.UserProfile; +import com.tuoheng.airport.uaa.domain.model.user.UserStatus; +import com.tuoheng.airport.uaa.infrastructure.persistence.entity.UserPO; + +/** + * 用户PO-DO转换器 + * + * @author Tuoheng Team + */ +public class UserConverter { + + /** + * PO转DO + */ + public static User toDomain(UserPO po) { + if (po == null) { + return null; + } + + User user = new User(); + user.setUserId(UserId.of(po.getId())); + user.setUsername(po.getUsername()); + user.setPassword(po.getPassword()); + + // 转换用户资料 + UserProfile profile = new UserProfile(); + profile.setRealName(po.getRealName()); + profile.setNickname(po.getNickname()); + profile.setMobile(po.getMobile()); + profile.setEmail(po.getEmail()); + profile.setAvatar(po.getAvatar()); + profile.setGender(po.getGender()); + profile.setDeptId(po.getDeptId()); + profile.setPositionId(po.getPositionId()); + user.setProfile(profile); + + user.setStatus(UserStatus.of(po.getStatus())); + user.setTenantId(po.getTenantId()); + user.setCreateTime(po.getCreateTime()); + user.setUpdateTime(po.getUpdateTime()); + + return user; + } + + /** + * DO转PO + */ + public static UserPO toPO(User user) { + if (user == null) { + return null; + } + + UserPO po = new UserPO(); + if (user.getUserId() != null) { + po.setId(user.getUserId().getId()); + } + po.setUsername(user.getUsername()); + po.setPassword(user.getPassword()); + + // 转换用户资料 + if (user.getProfile() != null) { + UserProfile profile = user.getProfile(); + po.setRealName(profile.getRealName()); + po.setNickname(profile.getNickname()); + po.setMobile(profile.getMobile()); + po.setEmail(profile.getEmail()); + po.setAvatar(profile.getAvatar()); + po.setGender(profile.getGender()); + po.setDeptId(profile.getDeptId()); + po.setPositionId(profile.getPositionId()); + } + + if (user.getStatus() != null) { + po.setStatus(user.getStatus().getCode()); + } + po.setTenantId(user.getTenantId()); + po.setCreateTime(user.getCreateTime()); + po.setUpdateTime(user.getUpdateTime()); + + return po; + } +} diff --git a/src/main/java/com/tuoheng/airport/uaa/infrastructure/persistence/entity/UserPO.java b/src/main/java/com/tuoheng/airport/uaa/infrastructure/persistence/entity/UserPO.java new file mode 100644 index 0000000..40b07c5 --- /dev/null +++ b/src/main/java/com/tuoheng/airport/uaa/infrastructure/persistence/entity/UserPO.java @@ -0,0 +1,109 @@ +package com.tuoheng.airport.uaa.infrastructure.persistence.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 用户数据库实体 + * + * @author Tuoheng Team + */ +@Data +@TableName("sys_user") +public class UserPO { + + /** + * 用户ID + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 用户名 + */ + private String username; + + /** + * 密码 + */ + private String password; + + /** + * 真实姓名 + */ + private String realName; + + /** + * 昵称 + */ + private String nickname; + + /** + * 手机号 + */ + private String mobile; + + /** + * 邮箱 + */ + private String email; + + /** + * 头像 + */ + private String avatar; + + /** + * 性别:0-未知,1-男,2-女 + */ + private Integer gender; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 职位ID + */ + private Long positionId; + + /** + * 用户状态:0-正常,1-锁定,2-禁用 + */ + private Integer status; + + /** + * 租户ID + */ + private Long tenantId; + + /** + * 删除标记:0-未删除,1-已删除 + */ + private Integer mark; + + /** + * 创建人 + */ + private Long createUser; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新人 + */ + private Long updateUser; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; +} diff --git a/src/main/java/com/tuoheng/airport/uaa/infrastructure/persistence/mapper/UserMapper.java b/src/main/java/com/tuoheng/airport/uaa/infrastructure/persistence/mapper/UserMapper.java new file mode 100644 index 0000000..a176d40 --- /dev/null +++ b/src/main/java/com/tuoheng/airport/uaa/infrastructure/persistence/mapper/UserMapper.java @@ -0,0 +1,30 @@ +package com.tuoheng.airport.uaa.infrastructure.persistence.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.tuoheng.airport.uaa.infrastructure.persistence.entity.UserPO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 用户Mapper接口 + * + * @author Tuoheng Team + */ +@Mapper +public interface UserMapper extends BaseMapper { + + /** + * 根据用户名查询用户 + */ + UserPO selectByUsername(@Param("username") String username); + + /** + * 根据手机号查询用户 + */ + UserPO selectByMobile(@Param("mobile") String mobile); + + /** + * 根据邮箱查询用户 + */ + UserPO selectByEmail(@Param("email") String email); +} diff --git a/src/main/java/com/tuoheng/airport/uaa/infrastructure/persistence/repository/UserRepositoryImpl.java b/src/main/java/com/tuoheng/airport/uaa/infrastructure/persistence/repository/UserRepositoryImpl.java new file mode 100644 index 0000000..8e97b55 --- /dev/null +++ b/src/main/java/com/tuoheng/airport/uaa/infrastructure/persistence/repository/UserRepositoryImpl.java @@ -0,0 +1,120 @@ +package com.tuoheng.airport.uaa.infrastructure.persistence.repository; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tuoheng.airport.uaa.domain.model.user.User; +import com.tuoheng.airport.uaa.domain.model.user.UserId; +import com.tuoheng.airport.uaa.domain.repository.UserRepository; +import com.tuoheng.airport.uaa.infrastructure.persistence.converter.UserConverter; +import com.tuoheng.airport.uaa.infrastructure.persistence.entity.UserPO; +import com.tuoheng.airport.uaa.infrastructure.persistence.mapper.UserMapper; +import org.springframework.stereotype.Repository; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * 用户仓储实现 + * + * @author Tuoheng Team + */ +@Repository +public class UserRepositoryImpl implements UserRepository { + + @Resource + private UserMapper userMapper; + + @Override + public User save(User user) { + UserPO po = UserConverter.toPO(user); + if (po.getId() == null) { + // 新增 + userMapper.insert(po); + } else { + // 更新 + userMapper.updateById(po); + } + return UserConverter.toDomain(po); + } + + @Override + public Optional findById(UserId userId) { + UserPO po = userMapper.selectById(userId.getId()); + return Optional.ofNullable(UserConverter.toDomain(po)); + } + + @Override + public Optional findByUsername(String username) { + UserPO po = userMapper.selectByUsername(username); + return Optional.ofNullable(UserConverter.toDomain(po)); + } + + @Override + public Optional findByMobile(String mobile) { + UserPO po = userMapper.selectByMobile(mobile); + return Optional.ofNullable(UserConverter.toDomain(po)); + } + + @Override + public Optional findByEmail(String email) { + UserPO po = userMapper.selectByEmail(email); + return Optional.ofNullable(UserConverter.toDomain(po)); + } + + @Override + public List findAll() { + List poList = userMapper.selectList(null); + return poList.stream() + .map(UserConverter::toDomain) + .collect(Collectors.toList()); + } + + @Override + public List findByPage(int pageNum, int pageSize) { + // 使用MyBatis-Plus分页 + int offset = (pageNum - 1) * pageSize; + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.last("LIMIT " + offset + "," + pageSize); + List poList = userMapper.selectList(wrapper); + return poList.stream() + .map(UserConverter::toDomain) + .collect(Collectors.toList()); + } + + @Override + public List findByTenantId(Long tenantId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(UserPO::getTenantId, tenantId); + List poList = userMapper.selectList(wrapper); + return poList.stream() + .map(UserConverter::toDomain) + .collect(Collectors.toList()); + } + + @Override + public void delete(UserId userId) { + userMapper.deleteById(userId.getId()); + } + + @Override + public boolean existsByUsername(String username) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(UserPO::getUsername, username); + return userMapper.selectCount(wrapper) > 0; + } + + @Override + public boolean existsByMobile(String mobile) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(UserPO::getMobile, mobile); + return userMapper.selectCount(wrapper) > 0; + } + + @Override + public boolean existsByEmail(String email) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(UserPO::getEmail, email); + return userMapper.selectCount(wrapper) > 0; + } +} diff --git a/src/main/java/com/tuoheng/airport/uaa/presentation/assembler/UserAssembler.java b/src/main/java/com/tuoheng/airport/uaa/presentation/assembler/UserAssembler.java new file mode 100644 index 0000000..bacda15 --- /dev/null +++ b/src/main/java/com/tuoheng/airport/uaa/presentation/assembler/UserAssembler.java @@ -0,0 +1,94 @@ +package com.tuoheng.airport.uaa.presentation.assembler; + +import com.tuoheng.airport.uaa.application.dto.UserDTO; +import com.tuoheng.airport.uaa.domain.model.user.UserStatus; +import com.tuoheng.airport.uaa.presentation.vo.request.UserCreateRequest; +import com.tuoheng.airport.uaa.presentation.vo.request.UserUpdateRequest; +import com.tuoheng.airport.uaa.presentation.vo.response.UserVO; + +/** + * 用户VO-DTO转换器 + * + * @author Tuoheng Team + */ +public class UserAssembler { + + /** + * CreateRequest转DTO + */ + public static UserDTO toDTO(UserCreateRequest request) { + if (request == null) { + return null; + } + + UserDTO dto = new UserDTO(); + dto.setUsername(request.getUsername()); + dto.setRealName(request.getRealName()); + dto.setNickname(request.getNickname()); + dto.setMobile(request.getMobile()); + dto.setEmail(request.getEmail()); + dto.setAvatar(request.getAvatar()); + dto.setGender(request.getGender()); + dto.setDeptId(request.getDeptId()); + dto.setPositionId(request.getPositionId()); + dto.setTenantId(request.getTenantId()); + + return dto; + } + + /** + * UpdateRequest转DTO + */ + public static UserDTO toDTO(UserUpdateRequest request) { + if (request == null) { + return null; + } + + UserDTO dto = new UserDTO(); + dto.setUserId(request.getUserId()); + dto.setRealName(request.getRealName()); + dto.setNickname(request.getNickname()); + dto.setMobile(request.getMobile()); + dto.setEmail(request.getEmail()); + dto.setAvatar(request.getAvatar()); + dto.setGender(request.getGender()); + dto.setDeptId(request.getDeptId()); + dto.setPositionId(request.getPositionId()); + + return dto; + } + + /** + * DTO转VO + */ + public static UserVO toVO(UserDTO dto) { + if (dto == null) { + return null; + } + + UserVO vo = new UserVO(); + vo.setUserId(dto.getUserId()); + vo.setUsername(dto.getUsername()); + vo.setRealName(dto.getRealName()); + vo.setNickname(dto.getNickname()); + vo.setMobile(dto.getMobile()); + vo.setEmail(dto.getEmail()); + vo.setAvatar(dto.getAvatar()); + vo.setGender(dto.getGender()); + vo.setDeptId(dto.getDeptId()); + vo.setPositionId(dto.getPositionId()); + vo.setStatus(dto.getStatus()); + + // 设置状态描述 + if (dto.getStatus() != null) { + UserStatus status = UserStatus.of(dto.getStatus()); + vo.setStatusDesc(status.getDesc()); + } + + vo.setTenantId(dto.getTenantId()); + vo.setCreateTime(dto.getCreateTime()); + vo.setUpdateTime(dto.getUpdateTime()); + + return vo; + } +} diff --git a/src/main/java/com/tuoheng/airport/uaa/presentation/controller/UserController.java b/src/main/java/com/tuoheng/airport/uaa/presentation/controller/UserController.java new file mode 100644 index 0000000..67bf2e8 --- /dev/null +++ b/src/main/java/com/tuoheng/airport/uaa/presentation/controller/UserController.java @@ -0,0 +1,145 @@ +package com.tuoheng.airport.uaa.presentation.controller; + +import com.tuoheng.airport.common.response.Result; +import com.tuoheng.airport.uaa.application.dto.UserDTO; +import com.tuoheng.airport.uaa.application.service.UserApplicationService; +import com.tuoheng.airport.uaa.presentation.assembler.UserAssembler; +import com.tuoheng.airport.uaa.presentation.vo.request.UserCreateRequest; +import com.tuoheng.airport.uaa.presentation.vo.request.UserUpdateRequest; +import com.tuoheng.airport.uaa.presentation.vo.response.UserVO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 用户控制器 + * + * @author Tuoheng Team + */ +@Api(tags = "用户管理") +@RestController +@RequestMapping("/api/uaa/users") +public class UserController { + + @Resource + private UserApplicationService userApplicationService; + + /** + * 创建用户 + */ + @ApiOperation("创建用户") + @PostMapping + public Result createUser(@Validated @RequestBody UserCreateRequest request) { + UserDTO userDTO = UserAssembler.toDTO(request); + UserDTO createdUser = userApplicationService.createUser(userDTO, request.getPassword()); + UserVO userVO = UserAssembler.toVO(createdUser); + return Result.success(userVO); + } + + /** + * 更新用户 + */ + @ApiOperation("更新用户") + @PutMapping("/{id}") + public Result updateUser(@PathVariable("id") Long id, + @Validated @RequestBody UserUpdateRequest request) { + request.setUserId(id); + UserDTO userDTO = UserAssembler.toDTO(request); + UserDTO updatedUser = userApplicationService.updateUser(id, userDTO); + UserVO userVO = UserAssembler.toVO(updatedUser); + return Result.success(userVO); + } + + /** + * 删除用户 + */ + @ApiOperation("删除用户") + @DeleteMapping("/{id}") + public Result deleteUser(@PathVariable("id") Long id) { + userApplicationService.deleteUser(id); + return Result.success(); + } + + /** + * 根据ID查询用户 + */ + @ApiOperation("根据ID查询用户") + @GetMapping("/{id}") + public Result getUserById(@PathVariable("id") Long id) { + UserDTO userDTO = userApplicationService.getUserById(id); + UserVO userVO = UserAssembler.toVO(userDTO); + return Result.success(userVO); + } + + /** + * 查询所有用户 + */ + @ApiOperation("查询所有用户") + @GetMapping + public Result> getAllUsers() { + List userDTOs = userApplicationService.getAllUsers(); + List userVOs = userDTOs.stream() + .map(UserAssembler::toVO) + .collect(Collectors.toList()); + return Result.success(userVOs); + } + + /** + * 分页查询用户 + */ + @ApiOperation("分页查询用户") + @GetMapping("/page") + public Result> getUsersByPage(@RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize) { + List userDTOs = userApplicationService.getUsersByPage(pageNum, pageSize); + List userVOs = userDTOs.stream() + .map(UserAssembler::toVO) + .collect(Collectors.toList()); + return Result.success(userVOs); + } + + /** + * 锁定用户 + */ + @ApiOperation("锁定用户") + @PutMapping("/{id}/lock") + public Result lockUser(@PathVariable("id") Long id) { + userApplicationService.lockUser(id); + return Result.success(); + } + + /** + * 解锁用户 + */ + @ApiOperation("解锁用户") + @PutMapping("/{id}/unlock") + public Result unlockUser(@PathVariable("id") Long id) { + userApplicationService.unlockUser(id); + return Result.success(); + } + + /** + * 禁用用户 + */ + @ApiOperation("禁用用户") + @PutMapping("/{id}/disable") + public Result disableUser(@PathVariable("id") Long id) { + userApplicationService.disableUser(id); + return Result.success(); + } + + /** + * 启用用户 + */ + @ApiOperation("启用用户") + @PutMapping("/{id}/enable") + public Result enableUser(@PathVariable("id") Long id) { + userApplicationService.enableUser(id); + return Result.success(); + } +} diff --git a/src/main/java/com/tuoheng/airport/uaa/presentation/vo/request/UserCreateRequest.java b/src/main/java/com/tuoheng/airport/uaa/presentation/vo/request/UserCreateRequest.java new file mode 100644 index 0000000..f764e56 --- /dev/null +++ b/src/main/java/com/tuoheng/airport/uaa/presentation/vo/request/UserCreateRequest.java @@ -0,0 +1,71 @@ +package com.tuoheng.airport.uaa.presentation.vo.request; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 用户创建请求VO + * + * @author Tuoheng Team + */ +@Data +public class UserCreateRequest { + + /** + * 用户名 + */ + @NotBlank(message = "用户名不能为空") + private String username; + + /** + * 密码 + */ + @NotBlank(message = "密码不能为空") + private String password; + + /** + * 真实姓名 + */ + private String realName; + + /** + * 昵称 + */ + private String nickname; + + /** + * 手机号 + */ + private String mobile; + + /** + * 邮箱 + */ + private String email; + + /** + * 头像 + */ + private String avatar; + + /** + * 性别:0-未知,1-男,2-女 + */ + private Integer gender; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 职位ID + */ + private Long positionId; + + /** + * 租户ID + */ + private Long tenantId; +} diff --git a/src/main/java/com/tuoheng/airport/uaa/presentation/vo/request/UserUpdateRequest.java b/src/main/java/com/tuoheng/airport/uaa/presentation/vo/request/UserUpdateRequest.java new file mode 100644 index 0000000..eea60a8 --- /dev/null +++ b/src/main/java/com/tuoheng/airport/uaa/presentation/vo/request/UserUpdateRequest.java @@ -0,0 +1,60 @@ +package com.tuoheng.airport.uaa.presentation.vo.request; + +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * 用户更新请求VO + * + * @author Tuoheng Team + */ +@Data +public class UserUpdateRequest { + + /** + * 用户ID + */ + @NotNull(message = "用户ID不能为空") + private Long userId; + + /** + * 真实姓名 + */ + private String realName; + + /** + * 昵称 + */ + private String nickname; + + /** + * 手机号 + */ + private String mobile; + + /** + * 邮箱 + */ + private String email; + + /** + * 头像 + */ + private String avatar; + + /** + * 性别:0-未知,1-男,2-女 + */ + private Integer gender; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 职位ID + */ + private Long positionId; +} diff --git a/src/main/java/com/tuoheng/airport/uaa/presentation/vo/response/UserVO.java b/src/main/java/com/tuoheng/airport/uaa/presentation/vo/response/UserVO.java new file mode 100644 index 0000000..c2d53ab --- /dev/null +++ b/src/main/java/com/tuoheng/airport/uaa/presentation/vo/response/UserVO.java @@ -0,0 +1,89 @@ +package com.tuoheng.airport.uaa.presentation.vo.response; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 用户响应VO + * + * @author Tuoheng Team + */ +@Data +public class UserVO { + + /** + * 用户ID + */ + private Long userId; + + /** + * 用户名 + */ + private String username; + + /** + * 真实姓名 + */ + private String realName; + + /** + * 昵称 + */ + private String nickname; + + /** + * 手机号 + */ + private String mobile; + + /** + * 邮箱 + */ + private String email; + + /** + * 头像 + */ + private String avatar; + + /** + * 性别:0-未知,1-男,2-女 + */ + private Integer gender; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 职位ID + */ + private Long positionId; + + /** + * 用户状态:0-正常,1-锁定,2-禁用 + */ + private Integer status; + + /** + * 用户状态描述 + */ + private String statusDesc; + + /** + * 租户ID + */ + private Long tenantId; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; +}