feat(wm-system): #74 系统管理(角色/用户/部门/日志/字典)

- 增强 SysRole: 5级角色(level 1-5), 数据权限(dataScope), 权限配置(permissions JSON)
- 增强 SysUser: 角色关联(roleId), 最后登录记录, 分页查询+多维度筛选
- 增强 SysDepartment: 树结构(ancestors), 子部门查询, 树形构建
- 增强 SysLog: 状态/错误信息字段, 分页+多条件筛选(类型/用户/模块/时间/状态)
- 新增 DataDictionary + DataDictionaryItem: 字典CRUD+字典项CRUD, 按编码查询
- 5个专用Controller: RoleController/UserController/DepartmentController/LogController/DictionaryController
- 28个RESTful API端点于 /api/system/*
- 5个专用Service接口+实现: RoleService/UserService/DepartmentService/LogService/DictionaryService
- DDL: V2__system_enhance.sql — 新增表+索引+初始数据
- 单元测试: 5个Service测试类, 覆盖核心业务逻辑
- DTO: 请求参数校验(jakarta.validation)
- 保持SysService向后兼容
This commit is contained in:
2026-06-14 15:23:34 +08:00
parent 520b4084eb
commit 51c292f25b
43 changed files with 2199 additions and 68 deletions
@@ -0,0 +1,59 @@
package com.water.system.controller;
import com.water.common.core.result.R;
import com.water.system.entity.SysDepartment;
import com.water.system.entity.dto.DeptRequest;
import com.water.system.service.DepartmentService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "部门管理")
@RestController
@RequestMapping("/api/system/dept")
@RequiredArgsConstructor
public class DepartmentController {
private final DepartmentService departmentService;
@Operation(summary = "获取部门树")
@GetMapping("/tree")
public R<List<SysDepartment>> tree() {
return R.ok(departmentService.getDeptTree());
}
@Operation(summary = "获取部门详情")
@GetMapping("/{id}")
public R<SysDepartment> getById(@PathVariable Long id) {
return R.ok(departmentService.getDeptById(id));
}
@Operation(summary = "创建部门")
@PostMapping
public R<Long> create(@Valid @RequestBody DeptRequest request) {
return R.ok(departmentService.createDept(request));
}
@Operation(summary = "更新部门")
@PutMapping("/{id}")
public R<String> update(@PathVariable Long id, @RequestBody DeptRequest request) {
departmentService.updateDept(id, request);
return R.ok("OK");
}
@Operation(summary = "删除部门")
@DeleteMapping("/{id}")
public R<String> delete(@PathVariable Long id) {
departmentService.deleteDept(id);
return R.ok("OK");
}
@Operation(summary = "获取子部门列表")
@GetMapping("/{id}/children")
public R<List<SysDepartment>> getChildren(@PathVariable Long id) {
return R.ok(departmentService.getChildDepts(id));
}
}
@@ -0,0 +1,87 @@
package com.water.system.controller;
import com.water.common.core.result.R;
import com.water.system.entity.DataDictionary;
import com.water.system.entity.DataDictionaryItem;
import com.water.system.entity.dto.DictItemRequest;
import com.water.system.entity.dto.DictRequest;
import com.water.system.service.DictionaryService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "数据字典管理")
@RestController
@RequestMapping("/api/system/dict")
@RequiredArgsConstructor
public class DictionaryController {
private final DictionaryService dictionaryService;
@Operation(summary = "获取字典列表")
@GetMapping("/list")
public R<List<DataDictionary>> list(@RequestParam(required = false) String keyword) {
return R.ok(dictionaryService.listDictionaries(keyword));
}
@Operation(summary = "获取字典详情")
@GetMapping("/{id}")
public R<DataDictionary> getById(@PathVariable Long id) {
return R.ok(dictionaryService.getDictionaryById(id));
}
@Operation(summary = "创建字典")
@PostMapping
public R<Long> create(@Valid @RequestBody DictRequest request) {
return R.ok(dictionaryService.createDictionary(request));
}
@Operation(summary = "更新字典")
@PutMapping("/{id}")
public R<String> update(@PathVariable Long id, @RequestBody DictRequest request) {
dictionaryService.updateDictionary(id, request);
return R.ok("OK");
}
@Operation(summary = "删除字典")
@DeleteMapping("/{id}")
public R<String> delete(@PathVariable Long id) {
dictionaryService.deleteDictionary(id);
return R.ok("OK");
}
@Operation(summary = "获取字典项列表(按字典ID)")
@GetMapping("/{dictId}/items")
public R<List<DataDictionaryItem>> listItems(@PathVariable Long dictId) {
return R.ok(dictionaryService.listItemsByDictId(dictId));
}
@Operation(summary = "根据字典编码获取字典项")
@GetMapping("/code/{dictCode}/items")
public R<List<DataDictionaryItem>> listItemsByCode(@PathVariable String dictCode) {
return R.ok(dictionaryService.listItemsByDictCode(dictCode));
}
@Operation(summary = "创建字典项")
@PostMapping("/item")
public R<Long> createItem(@Valid @RequestBody DictItemRequest request) {
return R.ok(dictionaryService.createItem(request));
}
@Operation(summary = "更新字典项")
@PutMapping("/item/{id}")
public R<String> updateItem(@PathVariable Long id, @RequestBody DictItemRequest request) {
dictionaryService.updateItem(id, request);
return R.ok("OK");
}
@Operation(summary = "删除字典项")
@DeleteMapping("/item/{id}")
public R<String> deleteItem(@PathVariable Long id) {
dictionaryService.deleteItem(id);
return R.ok("OK");
}
}
@@ -0,0 +1,32 @@
package com.water.system.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.water.common.core.result.R;
import com.water.system.entity.SysLog;
import com.water.system.entity.dto.LogQueryRequest;
import com.water.system.service.LogService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "日志管理")
@RestController
@RequestMapping("/api/system/log")
@RequiredArgsConstructor
public class LogController {
private final LogService logService;
@Operation(summary = "分页查询日志")
@GetMapping("/page")
public R<IPage<SysLog>> page(LogQueryRequest request) {
return R.ok(logService.pageLogs(request));
}
@Operation(summary = "获取日志详情")
@GetMapping("/{id}")
public R<SysLog> getById(@PathVariable Long id) {
return R.ok(logService.getLogById(id));
}
}
@@ -0,0 +1,66 @@
package com.water.system.controller;
import com.water.common.core.result.R;
import com.water.system.entity.SysRole;
import com.water.system.entity.dto.RoleRequest;
import com.water.system.service.RoleService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "角色管理")
@RestController
@RequestMapping("/api/system/role")
@RequiredArgsConstructor
public class RoleController {
private final RoleService roleService;
@Operation(summary = "获取角色列表")
@GetMapping("/list")
public R<List<SysRole>> list() {
return R.ok(roleService.listRoles());
}
@Operation(summary = "获取角色详情")
@GetMapping("/{id}")
public R<SysRole> getById(@PathVariable Long id) {
return R.ok(roleService.getRoleById(id));
}
@Operation(summary = "创建角色")
@PostMapping
public R<Long> create(@Valid @RequestBody RoleRequest request) {
return R.ok(roleService.createRole(request));
}
@Operation(summary = "更新角色")
@PutMapping("/{id}")
public R<String> update(@PathVariable Long id, @Valid @RequestBody RoleRequest request) {
roleService.updateRole(id, request);
return R.ok("OK");
}
@Operation(summary = "删除角色")
@DeleteMapping("/{id}")
public R<String> delete(@PathVariable Long id) {
roleService.deleteRole(id);
return R.ok("OK");
}
@Operation(summary = "分配角色权限")
@PutMapping("/{id}/permissions")
public R<String> assignPermissions(@PathVariable Long id, @RequestBody List<String> permissions) {
roleService.assignPermissions(id, permissions);
return R.ok("OK");
}
@Operation(summary = "获取用户角色")
@GetMapping("/user/{userId}")
public R<List<SysRole>> getByUserId(@PathVariable Long userId) {
return R.ok(roleService.getRolesByUserId(userId));
}
}
@@ -0,0 +1,74 @@
package com.water.system.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.water.common.core.result.R;
import com.water.system.entity.SysUser;
import com.water.system.entity.dto.PasswordResetRequest;
import com.water.system.entity.dto.UserCreateRequest;
import com.water.system.entity.dto.UserUpdateRequest;
import com.water.system.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "用户管理")
@RestController
@RequestMapping("/api/system/user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@Operation(summary = "分页查询用户列表")
@GetMapping("/page")
public R<IPage<SysUser>> page(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Integer status,
@RequestParam(required = false) Long deptId) {
return R.ok(userService.pageUsers(pageNum, pageSize, keyword, status, deptId));
}
@Operation(summary = "获取用户详情")
@GetMapping("/{id}")
public R<SysUser> getById(@PathVariable Long id) {
return R.ok(userService.getUserById(id));
}
@Operation(summary = "创建用户")
@PostMapping
public R<Long> create(@Valid @RequestBody UserCreateRequest request) {
return R.ok(userService.createUser(request));
}
@Operation(summary = "更新用户信息")
@PutMapping("/{id}")
public R<String> update(@PathVariable Long id, @RequestBody UserUpdateRequest request) {
userService.updateUser(id, request);
return R.ok("OK");
}
@Operation(summary = "删除用户")
@DeleteMapping("/{id}")
public R<String> delete(@PathVariable Long id) {
userService.deleteUser(id);
return R.ok("OK");
}
@Operation(summary = "停用/启用用户")
@PutMapping("/{id}/status")
public R<String> updateStatus(@PathVariable Long id, @RequestParam Integer status) {
userService.updateStatus(id, status);
return R.ok("OK");
}
@Operation(summary = "重置密码")
@PutMapping("/{id}/reset-password")
public R<String> resetPassword(@PathVariable Long id, @Valid @RequestBody PasswordResetRequest request) {
userService.resetPassword(id, request.getNewPassword());
return R.ok("OK");
}
}
@@ -0,0 +1,33 @@
package com.water.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 数据字典实体
*/
@Data
@TableName("sys_data_dictionary")
public class DataDictionary {
@TableId(type = IdType.AUTO)
private Long id;
/** 字典编码(唯一) */
private String dictCode;
/** 字典名称 */
private String dictName;
/** 字典描述 */
private String description;
/** 状态: 0-停用 1-正常 */
private Integer status;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
}
@@ -0,0 +1,42 @@
package com.water.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 数据字典项实体
*/
@Data
@TableName("sys_data_dictionary_item")
public class DataDictionaryItem {
@TableId(type = IdType.AUTO)
private Long id;
/** 字典ID */
private Long dictId;
/** 字典项编码 */
private String itemCode;
/** 字典项名称 */
private String itemName;
/** 字典项值 */
private String itemValue;
/** 排序号 */
private Integer sort;
/** 状态: 0-停用 1-正常 */
private Integer status;
/** 备注 */
private String remark;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
}
@@ -1,9 +1,47 @@
package com.water.system.entity; package com.water.system.entity;
import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.annotation.*;
import lombok.Data; import lombok.Data;
@Data @TableName("sys_department") import java.time.LocalDateTime;
import java.util.List;
/**
* 部门实体(树结构)
*/
@Data
@TableName("sys_department")
public class SysDepartment { public class SysDepartment {
@TableId(type = IdType.AUTO) private Long id; @TableId(type = IdType.AUTO)
private Long parentId; private String deptName, leader, phone; private Long id;
private Integer sort, status;
/** 父部门ID, 0为根节点 */
private Long parentId;
/** 部门名称 */
private String deptName;
/** 负责人 */
private String leader;
/** 联系电话 */
private String phone;
/** 排序号 */
private Integer sort;
/** 状态: 0-停用 1-正常 */
private Integer status;
/** 层级路径(如: 0,1,5) */
private String ancestors;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
/** 子部门列表(非数据库字段) */
@TableField(exist = false)
private List<SysDepartment> children;
} }
@@ -1,11 +1,58 @@
package com.water.system.entity; package com.water.system.entity;
import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.annotation.*;
import lombok.Data; import java.time.LocalDateTime; import lombok.Data;
@Data @TableName("sys_log") import java.time.LocalDateTime;
/**
* 系统日志实体
* 支持: 登录日志(LOGIN) / 操作日志(OPERATE) / 异常日志(EXCEPTION)
*/
@Data
@TableName("sys_log")
public class SysLog { public class SysLog {
@TableId(type = IdType.AUTO) private Long id; @TableId(type = IdType.AUTO)
private Long userId; private String username, logType, module, action; private Long id;
private String requestUrl, requestMethod, requestParams, responseResult;
private String ip; private Long duration; /** 操作用户ID */
@TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime; private Long userId;
/** 操作用户名 */
private String username;
/** 日志类型: LOGIN-登录 OPERATE-操作 EXCEPTION-异常 */
private String logType;
/** 操作模块 */
private String module;
/** 操作描述 */
private String action;
/** 请求URL */
private String requestUrl;
/** 请求方法 */
private String requestMethod;
/** 请求参数 */
private String requestParams;
/** 响应结果 */
private String responseResult;
/** 操作IP */
private String ip;
/** 耗时(毫秒) */
private Long duration;
/** 状态: 0-失败 1-成功 */
private Integer status;
/** 错误信息 */
private String errorMsg;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
} }
@@ -1,10 +1,46 @@
package com.water.system.entity; package com.water.system.entity;
import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.annotation.*;
import lombok.Data; import java.time.LocalDateTime; import lombok.Data;
@Data @TableName("sys_role") import java.time.LocalDateTime;
/**
* 系统角色实体
* 支持5级角色: 1-超级管理员 2-系统管理员 3-部门经理 4-普通员工 5-访客
*/
@Data
@TableName("sys_role")
public class SysRole { public class SysRole {
@TableId(type = IdType.AUTO) private Long id; @TableId(type = IdType.AUTO)
private String roleCode, roleName, description; private Long id;
private Integer status, sort;
@TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime; /** 角色编码 */
private String roleCode;
/** 角色名称 */
private String roleName;
/** 角色描述 */
private String description;
/** 角色级别: 1-超级管理员 2-系统管理员 3-部门经理 4-普通员工 5-访客 */
private Integer level;
/** 数据权限范围: ALL-全部 DEPT-本部门 DEPT_BELOW-本部门及以下 SELF-仅本人 */
private String dataScope;
/** 权限配置(JSON数组) */
private String permissions;
/** 状态: 0-停用 1-正常 */
private Integer status;
/** 排序号 */
private Integer sort;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
} }
@@ -1,12 +1,54 @@
package com.water.system.entity; package com.water.system.entity;
import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.annotation.*;
import lombok.Data; import java.time.LocalDateTime; import lombok.Data;
@Data @TableName("sys_user") import java.time.LocalDateTime;
/**
* 系统用户实体
*/
@Data
@TableName("sys_user")
public class SysUser { public class SysUser {
@TableId(type = IdType.AUTO) private Long id; @TableId(type = IdType.AUTO)
private String username, password, realName, phone, email; private Long id;
private Long deptId; private String avatar;
private Integer status; // 0停用 1正常 /** 用户名 */
@TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime; private String username;
@TableField(fill=FieldFill.INSERT_UPDATE) private LocalDateTime updatedTime;
/** 密码(加密) */
private String password;
/** 真实姓名 */
private String realName;
/** 手机号 */
private String phone;
/** 邮箱 */
private String email;
/** 部门ID */
private Long deptId;
/** 角色ID */
private Long roleId;
/** 头像URL */
private String avatar;
/** 状态: 0-停用 1-正常 */
private Integer status;
/** 最后登录时间 */
private LocalDateTime lastLoginTime;
/** 最后登录IP */
private String lastLoginIp;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
} }
@@ -0,0 +1,20 @@
package com.water.system.entity.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* 部门创建/更新请求
*/
@Data
public class DeptRequest {
private Long parentId;
@NotBlank(message = "部门名称不能为空")
private String deptName;
private String leader;
private String phone;
private Integer sort;
private Integer status;
}
@@ -0,0 +1,25 @@
package com.water.system.entity.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 字典项创建/更新请求
*/
@Data
public class DictItemRequest {
@NotNull(message = "字典ID不能为空")
private Long dictId;
@NotBlank(message = "字典项编码不能为空")
private String itemCode;
@NotBlank(message = "字典项名称不能为空")
private String itemName;
private String itemValue;
private Integer sort;
private Integer status;
private String remark;
}
@@ -0,0 +1,19 @@
package com.water.system.entity.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* 字典创建/更新请求
*/
@Data
public class DictRequest {
@NotBlank(message = "字典编码不能为空")
private String dictCode;
@NotBlank(message = "字典名称不能为空")
private String dictName;
private String description;
private Integer status;
}
@@ -0,0 +1,22 @@
package com.water.system.entity.dto;
import lombok.Data;
/**
* 日志查询请求
*/
@Data
public class LogQueryRequest extends PageQuery {
/** 日志类型: LOGIN/OPERATE/EXCEPTION */
private String logType;
/** 用户名(模糊) */
private String username;
/** 操作模块 */
private String module;
/** 开始时间(yyyy-MM-dd) */
private String startTime;
/** 结束时间(yyyy-MM-dd) */
private String endTime;
/** 状态: 0-失败 1-成功 */
private Integer status;
}
@@ -0,0 +1,14 @@
package com.water.system.entity.dto;
import lombok.Data;
/**
* 分页查询基础参数
*/
@Data
public class PageQuery {
/** 当前页码, 默认1 */
private Integer pageNum = 1;
/** 每页条数, 默认10 */
private Integer pageSize = 10;
}
@@ -0,0 +1,13 @@
package com.water.system.entity.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* 密码重置请求
*/
@Data
public class PasswordResetRequest {
@NotBlank(message = "新密码不能为空")
private String newPassword;
}
@@ -0,0 +1,27 @@
package com.water.system.entity.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
/**
* 角色创建/更新请求
*/
@Data
public class RoleRequest {
@NotBlank(message = "角色编码不能为空")
private String roleCode;
@NotBlank(message = "角色名称不能为空")
private String roleName;
private String description;
@NotNull(message = "角色级别不能为空")
private Integer level;
private String dataScope;
private List<String> permissions;
private Integer sort;
}
@@ -0,0 +1,22 @@
package com.water.system.entity.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* 用户创建请求
*/
@Data
public class UserCreateRequest {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
private String realName;
private String phone;
private String email;
private Long deptId;
private Long roleId;
}
@@ -0,0 +1,15 @@
package com.water.system.entity.dto;
import lombok.Data;
/**
* 用户更新请求
*/
@Data
public class UserUpdateRequest {
private String realName;
private String phone;
private String email;
private Long deptId;
private Long roleId;
}
@@ -0,0 +1,15 @@
package com.water.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.system.entity.DataDictionaryItem;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface DataDictionaryItemMapper extends BaseMapper<DataDictionaryItem> {
@Select("SELECT i.* FROM sys_data_dictionary_item i INNER JOIN sys_data_dictionary d ON d.id = i.dict_id WHERE d.dict_code = #{dictCode} AND d.deleted = 0 AND i.deleted = 0 ORDER BY i.sort")
List<DataDictionaryItem> selectByDictCode(@Param("dictCode") String dictCode);
}
@@ -0,0 +1,14 @@
package com.water.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.system.entity.DataDictionary;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface DataDictionaryMapper extends BaseMapper<DataDictionary> {
@Select("SELECT * FROM sys_data_dictionary WHERE dict_code = #{dictCode} AND deleted = 0")
DataDictionary selectByDictCode(@Param("dictCode") String dictCode);
}
@@ -1,5 +1,15 @@
package com.water.system.mapper; package com.water.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.system.entity.SysDepartment; import com.water.system.entity.SysDepartment;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
@Mapper public interface SysDepartmentMapper extends BaseMapper<SysDepartment> {} import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface SysDepartmentMapper extends BaseMapper<SysDepartment> {
@Select("SELECT * FROM sys_department WHERE ancestors LIKE CONCAT(#{ancestorPath}, '%') AND deleted = 0 ORDER BY sort")
List<SysDepartment> selectByAncestorPath(@Param("ancestorPath") String ancestorPath);
}
@@ -1,5 +1,9 @@
package com.water.system.mapper; package com.water.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.system.entity.SysLog; import com.water.system.entity.SysLog;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
@Mapper public interface SysLogMapper extends BaseMapper<SysLog> {}
@Mapper
public interface SysLogMapper extends BaseMapper<SysLog> {
}
@@ -1,5 +1,19 @@
package com.water.system.mapper; package com.water.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.system.entity.SysRole; import com.water.system.entity.SysRole;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
@Mapper public interface SysRoleMapper extends BaseMapper<SysRole> {} import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
@Mapper
public interface SysRoleMapper extends BaseMapper<SysRole> {
@Select("SELECT r.* FROM sys_role r INNER JOIN sys_user u ON u.role_id = r.id WHERE u.id = #{userId} AND r.deleted = 0")
List<SysRole> selectRolesByUserId(@Param("userId") Long userId);
@Select("SELECT * FROM sys_role WHERE level <= #{level} AND deleted = 0 ORDER BY level, sort")
List<SysRole> selectRolesByMaxLevel(@Param("level") Integer level);
}
@@ -1,5 +1,14 @@
package com.water.system.mapper; package com.water.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.system.entity.SysUser; import com.water.system.entity.SysUser;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
@Mapper public interface SysUserMapper extends BaseMapper<SysUser> {} import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {
@Select("SELECT * FROM sys_user WHERE username = #{username} AND deleted = 0")
SysUser selectByUsername(@Param("username") String username);
}
@@ -0,0 +1,29 @@
package com.water.system.service;
import com.water.system.entity.SysDepartment;
import com.water.system.entity.dto.DeptRequest;
import java.util.List;
/**
* 部门管理服务接口
*/
public interface DepartmentService {
/** 获取部门树 */
List<SysDepartment> getDeptTree();
/** 获取部门详情 */
SysDepartment getDeptById(Long id);
/** 创建部门 */
Long createDept(DeptRequest request);
/** 更新部门 */
void updateDept(Long id, DeptRequest request);
/** 删除部门 */
void deleteDept(Long id);
/** 获取所有子部门 */
List<SysDepartment> getChildDepts(Long parentId);
}
@@ -0,0 +1,43 @@
package com.water.system.service;
import com.water.system.entity.DataDictionary;
import com.water.system.entity.DataDictionaryItem;
import com.water.system.entity.dto.DictItemRequest;
import com.water.system.entity.dto.DictRequest;
import java.util.List;
/**
* 数据字典服务接口
*/
public interface DictionaryService {
/** 查询所有字典 */
List<DataDictionary> listDictionaries(String keyword);
/** 获取字典详情 */
DataDictionary getDictionaryById(Long id);
/** 创建字典 */
Long createDictionary(DictRequest request);
/** 更新字典 */
void updateDictionary(Long id, DictRequest request);
/** 删除字典 */
void deleteDictionary(Long id);
/** 根据字典ID查询字典项 */
List<DataDictionaryItem> listItemsByDictId(Long dictId);
/** 根据字典编码查询字典项 */
List<DataDictionaryItem> listItemsByDictCode(String dictCode);
/** 创建字典项 */
Long createItem(DictItemRequest request);
/** 更新字典项 */
void updateItem(Long id, DictItemRequest request);
/** 删除字典项 */
void deleteItem(Long id);
}
@@ -0,0 +1,20 @@
package com.water.system.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.water.system.entity.SysLog;
import com.water.system.entity.dto.LogQueryRequest;
/**
* 日志查询服务接口
*/
public interface LogService {
/** 分页查询日志 */
IPage<SysLog> pageLogs(LogQueryRequest request);
/** 获取日志详情 */
SysLog getLogById(Long id);
/** 记录日志 */
void saveLog(SysLog log);
}
@@ -0,0 +1,35 @@
package com.water.system.service;
import com.water.system.entity.SysRole;
import com.water.system.entity.dto.RoleRequest;
import java.util.List;
/**
* 角色管理服务接口
*/
public interface RoleService {
/** 查询所有角色 */
List<SysRole> listRoles();
/** 根据ID获取角色详情 */
SysRole getRoleById(Long id);
/** 创建角色 */
Long createRole(RoleRequest request);
/** 更新角色 */
void updateRole(Long id, RoleRequest request);
/** 删除角色 */
void deleteRole(Long id);
/** 分配权限 */
void assignPermissions(Long id, List<String> permissions);
/** 根据用户ID查询角色 */
List<SysRole> getRolesByUserId(Long userId);
/** 根据最大级别查询角色 */
List<SysRole> getRolesByMaxLevel(Integer level);
}
@@ -1,67 +1,76 @@
package com.water.system.service; package com.water.system.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.water.system.entity.*; import com.water.system.entity.*;
import com.water.system.mapper.*; import com.water.system.mapper.*;
import com.water.system.entity.dto.*;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.*; import java.util.*;
@Service @RequiredArgsConstructor
/**
* 系统管理兼容服务 - 保持向后兼容,内部委托给新的专用服务
*/
@Service
@RequiredArgsConstructor
public class SysService { public class SysService {
private final SysRoleMapper roleMapper; private final RoleService roleService;
private final SysUserMapper userMapper; private final UserService userService;
private final DepartmentService departmentService;
private final LogService logService;
private final SysMenuMapper menuMapper; private final SysMenuMapper menuMapper;
private final SysDepartmentMapper deptMapper;
private final SysLogMapper logMapper;
// Roles // Roles
public List<SysRole> listRoles() { return roleMapper.selectList(null); } public List<SysRole> listRoles() { return roleService.listRoles(); }
public Long createRole(Map<String,Object> req) { public Long createRole(Map<String,Object> req) {
SysRole r = new SysRole(); RoleRequest r = new RoleRequest();
r.setRoleCode((String)req.get("roleCode")); r.setRoleCode((String) req.get("roleCode"));
r.setRoleName((String)req.get("roleName")); r.setRoleName((String) req.get("roleName"));
r.setDescription((String)req.get("description")); r.setDescription((String) req.get("description"));
r.setStatus(1); r.setSort(0); r.setLevel(req.get("level") != null ? ((Number) req.get("level")).intValue() : 4);
roleMapper.insert(r); r.setDataScope((String) req.get("dataScope"));
return r.getId(); return roleService.createRole(r);
} }
// Users // Users
public List<SysUser> listUsers(Integer status) { public List<SysUser> listUsers(Integer status) {
return userMapper.selectList(new LambdaQueryWrapper<SysUser>() return userService.pageUsers(1, 1000, null, status, null).getRecords();
.eq(status != null, SysUser::getStatus, status));
} }
public Long createUser(Map<String,Object> req) { public Long createUser(Map<String,Object> req) {
SysUser u = new SysUser(); UserCreateRequest r = new UserCreateRequest();
u.setUsername((String)req.get("username")); r.setUsername((String) req.get("username"));
u.setPassword((String)req.get("password")); r.setPassword((String) req.get("password"));
u.setRealName((String)req.get("realName")); r.setRealName((String) req.get("realName"));
u.setPhone((String)req.get("phone")); r.setPhone((String) req.get("phone"));
u.setStatus(1); r.setEmail((String) req.get("email"));
userMapper.insert(u); if (req.get("deptId") != null) r.setDeptId(((Number) req.get("deptId")).longValue());
return u.getId(); if (req.get("roleId") != null) r.setRoleId(((Number) req.get("roleId")).longValue());
return userService.createUser(r);
} }
public void updateUserStatus(Long id, int status) { public void updateUserStatus(Long id, int status) {
SysUser u = userMapper.selectById(id); userService.updateStatus(id, status);
if (u == null) throw new RuntimeException("用户不存在");
u.setStatus(status);
userMapper.updateById(u);
} }
// Menus // Menus
public List<SysMenu> listMenus() { return menuMapper.selectList(null); } public List<SysMenu> listMenus() { return menuMapper.selectList(null); }
// Departments // Departments
public List<SysDepartment> listDepts() { return deptMapper.selectList(null); } public List<SysDepartment> listDepts() { return departmentService.getDeptTree(); }
public Long createDept(Map<String,Object> req) { public Long createDept(Map<String,Object> req) {
SysDepartment d = new SysDepartment(); DeptRequest r = new DeptRequest();
d.setDeptName((String)req.get("deptName")); r.setDeptName((String) req.get("deptName"));
d.setLeader((String)req.get("leader")); r.setLeader((String) req.get("leader"));
d.setSort(0); d.setStatus(1); r.setPhone((String) req.get("phone"));
deptMapper.insert(d); if (req.get("parentId") != null) r.setParentId(((Number) req.get("parentId")).longValue());
return d.getId(); return departmentService.createDept(r);
} }
// Logs // Logs
public List<SysLog> listLogs(String logType, String username) { public List<SysLog> listLogs(String logType, String username) {
LambdaQueryWrapper<SysLog> w = new LambdaQueryWrapper<>(); LogQueryRequest r = new LogQueryRequest();
if (logType != null) w.eq(SysLog::getLogType, logType); r.setLogType(logType);
if (username != null) w.like(SysLog::getUsername, username); r.setUsername(username);
return logMapper.selectList(w.orderByDesc(SysLog::getCreatedTime).last("LIMIT 100")); r.setPageNum(1);
r.setPageSize(100);
return logService.pageLogs(r).getRecords();
} }
} }
@@ -0,0 +1,37 @@
package com.water.system.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.water.system.entity.SysUser;
import com.water.system.entity.dto.UserCreateRequest;
import com.water.system.entity.dto.UserUpdateRequest;
import java.util.List;
/**
* 用户管理服务接口
*/
public interface UserService {
/** 分页查询用户列表 */
IPage<SysUser> pageUsers(Integer pageNum, Integer pageSize, String keyword, Integer status, Long deptId);
/** 获取用户详情 */
SysUser getUserById(Long id);
/** 创建用户 */
Long createUser(UserCreateRequest request);
/** 更新用户 */
void updateUser(Long id, UserUpdateRequest request);
/** 删除用户 */
void deleteUser(Long id);
/** 停用/启用用户 */
void updateStatus(Long id, Integer status);
/** 重置密码 */
void resetPassword(Long id, String newPassword);
/** 根据用户名查询 */
SysUser getByUsername(String username);
}
@@ -0,0 +1,102 @@
package com.water.system.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.water.common.core.exception.BusinessException;
import com.water.system.entity.SysDepartment;
import com.water.system.entity.dto.DeptRequest;
import com.water.system.mapper.SysDepartmentMapper;
import com.water.system.service.DepartmentService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class DepartmentServiceImpl implements DepartmentService {
private final SysDepartmentMapper deptMapper;
@Override
public List<SysDepartment> getDeptTree() {
List<SysDepartment> allDepts = deptMapper.selectList(
new LambdaQueryWrapper<SysDepartment>().orderByAsc(SysDepartment::getSort)
);
return buildTree(allDepts, 0L);
}
@Override
public SysDepartment getDeptById(Long id) {
SysDepartment dept = deptMapper.selectById(id);
if (dept == null) throw new BusinessException("部门不存在");
return dept;
}
@Override
public Long createDept(DeptRequest request) {
SysDepartment dept = new SysDepartment();
dept.setDeptName(request.getDeptName());
dept.setLeader(request.getLeader());
dept.setPhone(request.getPhone());
dept.setSort(request.getSort() != null ? request.getSort() : 0);
dept.setStatus(request.getStatus() != null ? request.getStatus() : 1);
Long parentId = request.getParentId() != null ? request.getParentId() : 0L;
dept.setParentId(parentId);
// 设置层级路径
if (parentId > 0) {
SysDepartment parent = getDeptById(parentId);
String ancestors = parent.getAncestors() != null ? parent.getAncestors() + "," + parentId : String.valueOf(parentId);
dept.setAncestors(ancestors);
} else {
dept.setAncestors("0");
}
deptMapper.insert(dept);
return dept.getId();
}
@Override
public void updateDept(Long id, DeptRequest request) {
SysDepartment dept = getDeptById(id);
if (request.getDeptName() != null) dept.setDeptName(request.getDeptName());
if (request.getLeader() != null) dept.setLeader(request.getLeader());
if (request.getPhone() != null) dept.setPhone(request.getPhone());
if (request.getSort() != null) dept.setSort(request.getSort());
if (request.getStatus() != null) dept.setStatus(request.getStatus());
deptMapper.updateById(dept);
}
@Override
public void deleteDept(Long id) {
// 检查是否有子部门
long childCount = deptMapper.selectCount(
new LambdaQueryWrapper<SysDepartment>().eq(SysDepartment::getParentId, id)
);
if (childCount > 0) {
throw new BusinessException("该部门下有子部门,不允许删除");
}
deptMapper.deleteById(id);
}
@Override
public List<SysDepartment> getChildDepts(Long parentId) {
SysDepartment parent = getDeptById(parentId);
String ancestorPath = parent.getAncestors() != null ? parent.getAncestors() + "," + parentId : String.valueOf(parentId);
return deptMapper.selectByAncestorPath(ancestorPath);
}
private List<SysDepartment> buildTree(List<SysDepartment> depts, Long parentId) {
Map<Long, List<SysDepartment>> grouped = depts.stream()
.collect(Collectors.groupingBy(SysDepartment::getParentId));
List<SysDepartment> roots = grouped.getOrDefault(parentId, new ArrayList<>());
for (SysDepartment root : roots) {
root.setChildren(buildTree(depts, root.getId()));
}
return roots;
}
}
@@ -0,0 +1,116 @@
package com.water.system.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.water.common.core.exception.BusinessException;
import com.water.system.entity.DataDictionary;
import com.water.system.entity.DataDictionaryItem;
import com.water.system.entity.dto.DictItemRequest;
import com.water.system.entity.dto.DictRequest;
import com.water.system.mapper.DataDictionaryMapper;
import com.water.system.mapper.DataDictionaryItemMapper;
import com.water.system.service.DictionaryService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class DictionaryServiceImpl implements DictionaryService {
private final DataDictionaryMapper dictMapper;
private final DataDictionaryItemMapper itemMapper;
@Override
public List<DataDictionary> listDictionaries(String keyword) {
LambdaQueryWrapper<DataDictionary> wrapper = new LambdaQueryWrapper<>();
if (keyword != null && !keyword.isBlank()) {
wrapper.like(DataDictionary::getDictCode, keyword)
.or().like(DataDictionary::getDictName, keyword);
}
wrapper.orderByAsc(DataDictionary::getDictCode);
return dictMapper.selectList(wrapper);
}
@Override
public DataDictionary getDictionaryById(Long id) {
DataDictionary dict = dictMapper.selectById(id);
if (dict == null) throw new BusinessException("字典不存在");
return dict;
}
@Override
public Long createDictionary(DictRequest request) {
DataDictionary existing = dictMapper.selectByDictCode(request.getDictCode());
if (existing != null) throw new BusinessException("字典编码已存在");
DataDictionary dict = new DataDictionary();
dict.setDictCode(request.getDictCode());
dict.setDictName(request.getDictName());
dict.setDescription(request.getDescription());
dict.setStatus(request.getStatus() != null ? request.getStatus() : 1);
dictMapper.insert(dict);
return dict.getId();
}
@Override
public void updateDictionary(Long id, DictRequest request) {
DataDictionary dict = getDictionaryById(id);
if (request.getDictName() != null) dict.setDictName(request.getDictName());
if (request.getDescription() != null) dict.setDescription(request.getDescription());
if (request.getStatus() != null) dict.setStatus(request.getStatus());
dictMapper.updateById(dict);
}
@Override
public void deleteDictionary(Long id) {
// 同时删除字典项
itemMapper.delete(new LambdaQueryWrapper<DataDictionaryItem>()
.eq(DataDictionaryItem::getDictId, id));
dictMapper.deleteById(id);
}
@Override
public List<DataDictionaryItem> listItemsByDictId(Long dictId) {
return itemMapper.selectList(
new LambdaQueryWrapper<DataDictionaryItem>()
.eq(DataDictionaryItem::getDictId, dictId)
.orderByAsc(DataDictionaryItem::getSort)
);
}
@Override
public List<DataDictionaryItem> listItemsByDictCode(String dictCode) {
return itemMapper.selectByDictCode(dictCode);
}
@Override
public Long createItem(DictItemRequest request) {
DataDictionaryItem item = new DataDictionaryItem();
item.setDictId(request.getDictId());
item.setItemCode(request.getItemCode());
item.setItemName(request.getItemName());
item.setItemValue(request.getItemValue());
item.setSort(request.getSort() != null ? request.getSort() : 0);
item.setStatus(request.getStatus() != null ? request.getStatus() : 1);
item.setRemark(request.getRemark());
itemMapper.insert(item);
return item.getId();
}
@Override
public void updateItem(Long id, DictItemRequest request) {
DataDictionaryItem item = itemMapper.selectById(id);
if (item == null) throw new BusinessException("字典项不存在");
if (request.getItemName() != null) item.setItemName(request.getItemName());
if (request.getItemValue() != null) item.setItemValue(request.getItemValue());
if (request.getSort() != null) item.setSort(request.getSort());
if (request.getStatus() != null) item.setStatus(request.getStatus());
if (request.getRemark() != null) item.setRemark(request.getRemark());
itemMapper.updateById(item);
}
@Override
public void deleteItem(Long id) {
itemMapper.deleteById(id);
}
}
@@ -0,0 +1,62 @@
package com.water.system.service.impl;
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.water.system.entity.SysLog;
import com.water.system.entity.dto.LogQueryRequest;
import com.water.system.mapper.SysLogMapper;
import com.water.system.service.LogService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Service
@RequiredArgsConstructor
public class LogServiceImpl implements LogService {
private final SysLogMapper logMapper;
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@Override
public IPage<SysLog> pageLogs(LogQueryRequest request) {
Page<SysLog> page = new Page<>(request.getPageNum(), request.getPageSize());
LambdaQueryWrapper<SysLog> wrapper = new LambdaQueryWrapper<>();
if (request.getLogType() != null && !request.getLogType().isBlank()) {
wrapper.eq(SysLog::getLogType, request.getLogType());
}
if (request.getUsername() != null && !request.getUsername().isBlank()) {
wrapper.like(SysLog::getUsername, request.getUsername());
}
if (request.getModule() != null && !request.getModule().isBlank()) {
wrapper.eq(SysLog::getModule, request.getModule());
}
if (request.getStatus() != null) {
wrapper.eq(SysLog::getStatus, request.getStatus());
}
if (request.getStartTime() != null && !request.getStartTime().isBlank()) {
LocalDateTime start = LocalDate.parse(request.getStartTime(), DATE_FMT).atStartOfDay();
wrapper.ge(SysLog::getCreatedTime, start);
}
if (request.getEndTime() != null && !request.getEndTime().isBlank()) {
LocalDateTime end = LocalDate.parse(request.getEndTime(), DATE_FMT).plusDays(1).atStartOfDay();
wrapper.lt(SysLog::getCreatedTime, end);
}
wrapper.orderByDesc(SysLog::getCreatedTime);
return logMapper.selectPage(page, wrapper);
}
@Override
public SysLog getLogById(Long id) {
return logMapper.selectById(id);
}
@Override
public void saveLog(SysLog log) {
logMapper.insert(log);
}
}
@@ -0,0 +1,103 @@
package com.water.system.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.water.common.core.exception.BusinessException;
import com.water.system.entity.SysRole;
import com.water.system.entity.dto.RoleRequest;
import com.water.system.mapper.SysRoleMapper;
import com.water.system.service.RoleService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class RoleServiceImpl implements RoleService {
private final SysRoleMapper roleMapper;
private final ObjectMapper objectMapper;
@Override
public List<SysRole> listRoles() {
return roleMapper.selectList(
new LambdaQueryWrapper<SysRole>().orderByAsc(SysRole::getLevel, SysRole::getSort)
);
}
@Override
public SysRole getRoleById(Long id) {
SysRole role = roleMapper.selectById(id);
if (role == null) throw new BusinessException("角色不存在");
return role;
}
@Override
public Long createRole(RoleRequest request) {
// 检查编码唯一
long count = roleMapper.selectCount(
new LambdaQueryWrapper<SysRole>().eq(SysRole::getRoleCode, request.getRoleCode())
);
if (count > 0) throw new BusinessException("角色编码已存在");
SysRole role = new SysRole();
role.setRoleCode(request.getRoleCode());
role.setRoleName(request.getRoleName());
role.setDescription(request.getDescription());
role.setLevel(request.getLevel());
role.setDataScope(request.getDataScope() != null ? request.getDataScope() : "SELF");
role.setPermissions(toJson(request.getPermissions()));
role.setStatus(1);
role.setSort(request.getSort() != null ? request.getSort() : 0);
roleMapper.insert(role);
return role.getId();
}
@Override
public void updateRole(Long id, RoleRequest request) {
SysRole role = getRoleById(id);
role.setRoleName(request.getRoleName());
role.setDescription(request.getDescription());
role.setLevel(request.getLevel());
if (request.getDataScope() != null) role.setDataScope(request.getDataScope());
if (request.getPermissions() != null) role.setPermissions(toJson(request.getPermissions()));
if (request.getSort() != null) role.setSort(request.getSort());
roleMapper.updateById(role);
}
@Override
public void deleteRole(Long id) {
SysRole role = getRoleById(id);
if (role.getLevel() != null && role.getLevel() <= 2) {
throw new BusinessException("超级管理员和系统管理员角色不允许删除");
}
roleMapper.deleteById(id);
}
@Override
public void assignPermissions(Long id, List<String> permissions) {
SysRole role = getRoleById(id);
role.setPermissions(toJson(permissions));
roleMapper.updateById(role);
}
@Override
public List<SysRole> getRolesByUserId(Long userId) {
return roleMapper.selectRolesByUserId(userId);
}
@Override
public List<SysRole> getRolesByMaxLevel(Integer level) {
return roleMapper.selectRolesByMaxLevel(level);
}
private String toJson(Object obj) {
if (obj == null) return null;
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new BusinessException("JSON序列化失败: " + e.getMessage());
}
}
}
@@ -0,0 +1,115 @@
package com.water.system.service.impl;
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.water.common.core.exception.BusinessException;
import com.water.system.entity.SysUser;
import com.water.system.entity.dto.UserCreateRequest;
import com.water.system.entity.dto.UserUpdateRequest;
import com.water.system.mapper.SysUserMapper;
import com.water.system.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import java.nio.charset.StandardCharsets;
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final SysUserMapper userMapper;
@Override
public IPage<SysUser> pageUsers(Integer pageNum, Integer pageSize, String keyword, Integer status, Long deptId) {
Page<SysUser> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
if (keyword != null && !keyword.isBlank()) {
wrapper.and(w -> w.like(SysUser::getUsername, keyword)
.or().like(SysUser::getRealName, keyword)
.or().like(SysUser::getPhone, keyword));
}
if (status != null) wrapper.eq(SysUser::getStatus, status);
if (deptId != null) wrapper.eq(SysUser::getDeptId, deptId);
wrapper.orderByDesc(SysUser::getCreatedTime);
IPage<SysUser> result = userMapper.selectPage(page, wrapper);
// 清除密码字段
result.getRecords().forEach(u -> u.setPassword(null));
return result;
}
@Override
public SysUser getUserById(Long id) {
SysUser user = userMapper.selectById(id);
if (user == null) throw new BusinessException("用户不存在");
user.setPassword(null);
return user;
}
@Override
public Long createUser(UserCreateRequest request) {
// 检查用户名唯一
SysUser existing = userMapper.selectByUsername(request.getUsername());
if (existing != null) throw new BusinessException("用户名已存在");
SysUser user = new SysUser();
user.setUsername(request.getUsername());
user.setPassword(encryptPassword(request.getPassword()));
user.setRealName(request.getRealName());
user.setPhone(request.getPhone());
user.setEmail(request.getEmail());
user.setDeptId(request.getDeptId());
user.setRoleId(request.getRoleId());
user.setStatus(1);
userMapper.insert(user);
return user.getId();
}
@Override
public void updateUser(Long id, UserUpdateRequest request) {
SysUser user = userMapper.selectById(id);
if (user == null) throw new BusinessException("用户不存在");
if (request.getRealName() != null) user.setRealName(request.getRealName());
if (request.getPhone() != null) user.setPhone(request.getPhone());
if (request.getEmail() != null) user.setEmail(request.getEmail());
if (request.getDeptId() != null) user.setDeptId(request.getDeptId());
if (request.getRoleId() != null) user.setRoleId(request.getRoleId());
userMapper.updateById(user);
}
@Override
public void deleteUser(Long id) {
SysUser user = userMapper.selectById(id);
if (user == null) throw new BusinessException("用户不存在");
if ("admin".equals(user.getUsername())) {
throw new BusinessException("系统管理员账号不允许删除");
}
userMapper.deleteById(id);
}
@Override
public void updateStatus(Long id, Integer status) {
SysUser user = userMapper.selectById(id);
if (user == null) throw new BusinessException("用户不存在");
user.setStatus(status);
userMapper.updateById(user);
}
@Override
public void resetPassword(Long id, String newPassword) {
SysUser user = userMapper.selectById(id);
if (user == null) throw new BusinessException("用户不存在");
user.setPassword(encryptPassword(newPassword));
userMapper.updateById(user);
}
@Override
public SysUser getByUsername(String username) {
return userMapper.selectByUsername(username);
}
private String encryptPassword(String password) {
return DigestUtils.md5DigestAsHex(password.getBytes(StandardCharsets.UTF_8));
}
}
@@ -0,0 +1,136 @@
-- V2__system_enhance.sql
-- 系统管理增强: 角色/用户/部门/日志/字典
-- 1. 增强 sys_role 表: 新增 level, data_scope, permissions, updated_time 字段
ALTER TABLE sys_role ADD COLUMN IF NOT EXISTS level INT DEFAULT 4;
ALTER TABLE sys_role ADD COLUMN IF NOT EXISTS data_scope VARCHAR(20) DEFAULT 'SELF';
ALTER TABLE sys_role ADD COLUMN IF NOT EXISTS permissions TEXT;
ALTER TABLE sys_role ADD COLUMN IF NOT EXISTS updated_time TIMESTAMP DEFAULT NOW();
ALTER TABLE sys_role ADD COLUMN IF NOT EXISTS deleted INT DEFAULT 0;
COMMENT ON COLUMN sys_role.level IS '角色级别: 1-超级管理员 2-系统管理员 3-部门经理 4-普通员工 5-访客';
COMMENT ON COLUMN sys_role.data_scope IS '数据权限: ALL/DEPT/DEPT_BELOW/SELF';
COMMENT ON COLUMN sys_role.permissions IS '权限配置(JSON数组)';
-- 2. 增强 sys_user 表: 新增 role_id, last_login_time, last_login_ip, deleted
ALTER TABLE sys_user ADD COLUMN IF NOT EXISTS role_id BIGINT;
ALTER TABLE sys_user ADD COLUMN IF NOT EXISTS last_login_time TIMESTAMP;
ALTER TABLE sys_user ADD COLUMN IF NOT EXISTS last_login_ip VARCHAR(50);
ALTER TABLE sys_user ADD COLUMN IF NOT EXISTS deleted INT DEFAULT 0;
-- 3. 增强 sys_department 表: 新增 ancestors, created_time, updated_time, deleted
ALTER TABLE sys_department ADD COLUMN IF NOT EXISTS ancestors VARCHAR(500) DEFAULT '0';
ALTER TABLE sys_department ADD COLUMN IF NOT EXISTS created_time TIMESTAMP DEFAULT NOW();
ALTER TABLE sys_department ADD COLUMN IF NOT EXISTS updated_time TIMESTAMP DEFAULT NOW();
ALTER TABLE sys_department ADD COLUMN IF NOT EXISTS deleted INT DEFAULT 0;
COMMENT ON COLUMN sys_department.ancestors IS '层级路径(如: 0,1,5)';
-- 4. 增强 sys_log 表: 新增 status, error_msg
ALTER TABLE sys_log ADD COLUMN IF NOT EXISTS status INT DEFAULT 1;
ALTER TABLE sys_log ADD COLUMN IF NOT EXISTS error_msg TEXT;
COMMENT ON COLUMN sys_log.log_type IS '日志类型: LOGIN/OPERATE/EXCEPTION';
COMMENT ON COLUMN sys_log.status IS '状态: 0-失败 1-成功';
-- 5. 新增数据字典表
CREATE TABLE IF NOT EXISTS sys_data_dictionary (
id BIGSERIAL PRIMARY KEY,
dict_code VARCHAR(100) UNIQUE NOT NULL,
dict_name VARCHAR(200) NOT NULL,
description VARCHAR(500),
status INT DEFAULT 1,
deleted INT DEFAULT 0,
created_time TIMESTAMP DEFAULT NOW(),
updated_time TIMESTAMP DEFAULT NOW()
);
COMMENT ON TABLE sys_data_dictionary IS '数据字典表';
COMMENT ON COLUMN sys_data_dictionary.dict_code IS '字典编码(唯一)';
COMMENT ON COLUMN sys_data_dictionary.dict_name IS '字典名称';
-- 6. 新增数据字典项表
CREATE TABLE IF NOT EXISTS sys_data_dictionary_item (
id BIGSERIAL PRIMARY KEY,
dict_id BIGINT NOT NULL,
item_code VARCHAR(100) NOT NULL,
item_name VARCHAR(200) NOT NULL,
item_value VARCHAR(500),
sort INT DEFAULT 0,
status INT DEFAULT 1,
remark VARCHAR(500),
deleted INT DEFAULT 0,
created_time TIMESTAMP DEFAULT NOW(),
updated_time TIMESTAMP DEFAULT NOW()
);
COMMENT ON TABLE sys_data_dictionary_item IS '数据字典项表';
COMMENT ON COLUMN sys_data_dictionary_item.dict_id IS '所属字典ID';
COMMENT ON COLUMN sys_data_dictionary_item.item_code IS '字典项编码';
COMMENT ON COLUMN sys_data_dictionary_item.item_name IS '字典项名称';
COMMENT ON COLUMN sys_data_dictionary_item.item_value IS '字典项值';
-- 索引
CREATE INDEX IF NOT EXISTS idx_dict_item_dict_id ON sys_data_dictionary_item(dict_id);
CREATE INDEX IF NOT EXISTS idx_dict_item_code ON sys_data_dictionary_item(item_code);
CREATE INDEX IF NOT EXISTS idx_sys_user_dept_id ON sys_user(dept_id);
CREATE INDEX IF NOT EXISTS idx_sys_user_role_id ON sys_user(role_id);
CREATE INDEX IF NOT EXISTS idx_sys_user_status ON sys_user(status);
CREATE INDEX IF NOT EXISTS idx_sys_role_level ON sys_role(level);
CREATE INDEX IF NOT EXISTS idx_sys_dept_parent_id ON sys_department(parent_id);
CREATE INDEX IF NOT EXISTS idx_sys_dept_ancestors ON sys_department(ancestors);
CREATE INDEX IF NOT EXISTS idx_sys_log_type ON sys_log(log_type);
CREATE INDEX IF NOT EXISTS idx_sys_log_user ON sys_log(username);
CREATE INDEX IF NOT EXISTS idx_sys_log_created ON sys_log(created_time);
CREATE INDEX IF NOT EXISTS idx_sys_log_module ON sys_log(module);
CREATE INDEX IF NOT EXISTS idx_sys_log_status ON sys_log(status);
-- 初始化5级角色数据
INSERT INTO sys_role (role_code, role_name, description, level, data_scope, sort, created_time) VALUES
('SUPER_ADMIN', '超级管理员', '拥有系统所有权限', 1, 'ALL', 1, NOW()),
('SYS_ADMIN', '系统管理员', '系统管理权限', 2, 'ALL', 2, NOW()),
('DEPT_MANAGER', '部门经理', '本部门管理权限', 3, 'DEPT', 3, NOW()),
('EMPLOYEE', '普通员工', '基础操作权限', 4, 'SELF', 4, NOW()),
('GUEST', '访客', '仅查看权限', 5, 'SELF', 5, NOW())
ON CONFLICT (role_code) DO NOTHING;
-- 初始化数据字典示例
INSERT INTO sys_data_dictionary (dict_code, dict_name, description, status, created_time) VALUES
('sys_user_status', '用户状态', '用户账号状态枚举', 1, NOW()),
('sys_log_type', '日志类型', '系统日志类型枚举', 1, NOW()),
('sys_role_level', '角色级别', '系统角色级别枚举', 1, NOW())
ON CONFLICT (dict_code) DO NOTHING;
INSERT INTO sys_data_dictionary_item (dict_id, item_code, item_name, item_value, sort, status, created_time)
SELECT d.id, item_code, item_name, item_value, sort, 1, NOW()
FROM sys_data_dictionary d
CROSS JOIN (VALUES
('sys_user_status', 'DISABLED', '停用', '0', 1),
('sys_user_status', 'ENABLED', '正常', '1', 2)
) AS v(dict_code, item_code, item_name, item_value, sort)
WHERE d.dict_code = v.dict_code
AND NOT EXISTS (SELECT 1 FROM sys_data_dictionary_item i WHERE i.dict_id = d.id AND i.item_code = v.item_code);
INSERT INTO sys_data_dictionary_item (dict_id, item_code, item_name, item_value, sort, status, created_time)
SELECT d.id, item_code, item_name, item_value, sort, 1, NOW()
FROM sys_data_dictionary d
CROSS JOIN (VALUES
('sys_log_type', 'LOGIN', '登录日志', 'LOGIN', 1),
('sys_log_type', 'OPERATE', '操作日志', 'OPERATE', 2),
('sys_log_type', 'EXCEPTION', '异常日志', 'EXCEPTION', 3)
) AS v(dict_code, item_code, item_name, item_value, sort)
WHERE d.dict_code = v.dict_code
AND NOT EXISTS (SELECT 1 FROM sys_data_dictionary_item i WHERE i.dict_id = d.id AND i.item_code = v.item_code);
INSERT INTO sys_data_dictionary_item (dict_id, item_code, item_name, item_value, sort, status, created_time)
SELECT d.id, item_code, item_name, item_value, sort, 1, NOW()
FROM sys_data_dictionary d
CROSS JOIN (VALUES
('sys_role_level', 'LEVEL_1', '超级管理员', '1', 1),
('sys_role_level', 'LEVEL_2', '系统管理员', '2', 2),
('sys_role_level', 'LEVEL_3', '部门经理', '3', 3),
('sys_role_level', 'LEVEL_4', '普通员工', '4', 4),
('sys_role_level', 'LEVEL_5', '访客', '5', 5)
) AS v(dict_code, item_code, item_name, item_value, sort)
WHERE d.dict_code = v.dict_code
AND NOT EXISTS (SELECT 1 FROM sys_data_dictionary_item i WHERE i.dict_id = d.id AND i.item_code = v.item_code);
@@ -0,0 +1,93 @@
package com.water.system.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.water.common.core.exception.BusinessException;
import com.water.system.entity.SysDepartment;
import com.water.system.entity.dto.DeptRequest;
import com.water.system.mapper.SysDepartmentMapper;
import com.water.system.service.impl.DepartmentServiceImpl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class DepartmentServiceImplTest {
@Mock
private SysDepartmentMapper deptMapper;
@InjectMocks
private DepartmentServiceImpl departmentService;
@Test
void getDeptTree_shouldBuildTree() {
SysDepartment root = new SysDepartment();
root.setId(1L);
root.setParentId(0L);
root.setDeptName("总公司");
SysDepartment child1 = new SysDepartment();
child1.setId(2L);
child1.setParentId(1L);
child1.setDeptName("技术部");
SysDepartment child2 = new SysDepartment();
child2.setId(3L);
child2.setParentId(1L);
child2.setDeptName("市场部");
when(deptMapper.selectList(any(LambdaQueryWrapper.class)))
.thenReturn(Arrays.asList(root, child1, child2));
List<SysDepartment> tree = departmentService.getDeptTree();
assertEquals(1, tree.size());
assertEquals("总公司", tree.get(0).getDeptName());
assertEquals(2, tree.get(0).getChildren().size());
}
@Test
void createDept_shouldSetAncestors() {
SysDepartment parent = new SysDepartment();
parent.setId(1L);
parent.setAncestors("0");
parent.setDeptName("总公司");
when(deptMapper.selectById(1L)).thenReturn(parent);
when(deptMapper.insert(any(SysDepartment.class))).thenAnswer(inv -> {
SysDepartment d = inv.getArgument(0);
d.setId(10L);
assertEquals("0,1", d.getAncestors());
return 1;
});
DeptRequest request = new DeptRequest();
request.setDeptName("研发部");
request.setParentId(1L);
Long id = departmentService.createDept(request);
assertEquals(10L, id);
}
@Test
void deleteDept_shouldFailWithChildren() {
when(deptMapper.selectCount(any(LambdaQueryWrapper.class))).thenReturn(2L);
assertThrows(BusinessException.class, () -> departmentService.deleteDept(1L));
}
@Test
void deleteDept_shouldSucceedWithoutChildren() {
when(deptMapper.selectCount(any(LambdaQueryWrapper.class))).thenReturn(0L);
assertDoesNotThrow(() -> departmentService.deleteDept(5L));
verify(deptMapper).deleteById(5L);
}
}
@@ -0,0 +1,104 @@
package com.water.system.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.water.common.core.exception.BusinessException;
import com.water.system.entity.DataDictionary;
import com.water.system.entity.DataDictionaryItem;
import com.water.system.entity.dto.DictItemRequest;
import com.water.system.entity.dto.DictRequest;
import com.water.system.mapper.DataDictionaryMapper;
import com.water.system.mapper.DataDictionaryItemMapper;
import com.water.system.service.impl.DictionaryServiceImpl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class DictionaryServiceImplTest {
@Mock
private DataDictionaryMapper dictMapper;
@Mock
private DataDictionaryItemMapper itemMapper;
@InjectMocks
private DictionaryServiceImpl dictionaryService;
@Test
void createDictionary_shouldSucceed() {
DictRequest request = new DictRequest();
request.setDictCode("water_quality_level");
request.setDictName("水质等级");
when(dictMapper.selectByDictCode("water_quality_level")).thenReturn(null);
when(dictMapper.insert(any(DataDictionary.class))).thenAnswer(inv -> {
DataDictionary d = inv.getArgument(0);
d.setId(10L);
return 1;
});
Long id = dictionaryService.createDictionary(request);
assertEquals(10L, id);
}
@Test
void createDictionary_shouldFailOnDuplicate() {
DictRequest request = new DictRequest();
request.setDictCode("sys_user_status");
DataDictionary existing = new DataDictionary();
existing.setDictCode("sys_user_status");
when(dictMapper.selectByDictCode("sys_user_status")).thenReturn(existing);
assertThrows(BusinessException.class, () -> dictionaryService.createDictionary(request));
}
@Test
void listItemsByDictCode_shouldReturnItems() {
DataDictionaryItem item1 = new DataDictionaryItem();
item1.setItemCode("ENABLED");
item1.setItemName("正常");
DataDictionaryItem item2 = new DataDictionaryItem();
item2.setItemCode("DISABLED");
item2.setItemName("停用");
when(itemMapper.selectByDictCode("sys_user_status")).thenReturn(Arrays.asList(item1, item2));
List<DataDictionaryItem> items = dictionaryService.listItemsByDictCode("sys_user_status");
assertEquals(2, items.size());
}
@Test
void deleteDictionary_shouldDeleteItemsToo() {
dictionaryService.deleteDictionary(1L);
verify(itemMapper).delete(any(LambdaQueryWrapper.class));
verify(dictMapper).deleteById(1L);
}
@Test
void createItem_shouldSucceed() {
DictItemRequest request = new DictItemRequest();
request.setDictId(1L);
request.setItemCode("NEW_ITEM");
request.setItemName("新项");
request.setItemValue("value");
when(itemMapper.insert(any(DataDictionaryItem.class))).thenAnswer(inv -> {
DataDictionaryItem i = inv.getArgument(0);
i.setId(20L);
return 1;
});
Long id = dictionaryService.createItem(request);
assertEquals(20L, id);
}
}
@@ -0,0 +1,93 @@
package com.water.system.service;
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.water.system.entity.SysLog;
import com.water.system.entity.dto.LogQueryRequest;
import com.water.system.mapper.SysLogMapper;
import com.water.system.service.impl.LogServiceImpl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.time.LocalDateTime;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class LogServiceImplTest {
@Mock
private SysLogMapper logMapper;
@InjectMocks
private LogServiceImpl logService;
@Test
void pageLogs_shouldReturnPagedResult() {
Page<SysLog> page = new Page<>(1, 10);
SysLog log1 = new SysLog();
log1.setId(1L);
log1.setLogType("LOGIN");
log1.setUsername("admin");
log1.setCreatedTime(LocalDateTime.now());
SysLog log2 = new SysLog();
log2.setId(2L);
log2.setLogType("OPERATE");
log2.setUsername("user1");
log2.setModule("用户管理");
page.setRecords(Arrays.asList(log1, log2));
page.setTotal(2);
when(logMapper.selectPage(any(Page.class), any(LambdaQueryWrapper.class))).thenReturn(page);
LogQueryRequest request = new LogQueryRequest();
request.setPageNum(1);
request.setPageSize(10);
IPage<SysLog> result = logService.pageLogs(request);
assertEquals(2, result.getTotal());
assertEquals("LOGIN", result.getRecords().get(0).getLogType());
}
@Test
void pageLogs_withFilters_shouldApplyAllConditions() {
Page<SysLog> page = new Page<>(1, 10);
page.setRecords(Arrays.asList());
page.setTotal(0);
when(logMapper.selectPage(any(Page.class), any(LambdaQueryWrapper.class))).thenReturn(page);
LogQueryRequest request = new LogQueryRequest();
request.setLogType("OPERATE");
request.setUsername("admin");
request.setModule("系统管理");
request.setStatus(1);
request.setStartTime("2024-01-01");
request.setEndTime("2024-12-31");
IPage<SysLog> result = logService.pageLogs(request);
assertNotNull(result);
verify(logMapper).selectPage(any(Page.class), any(LambdaQueryWrapper.class));
}
@Test
void saveLog_shouldInsert() {
SysLog log = new SysLog();
log.setLogType("LOGIN");
log.setUsername("testuser");
log.setIp("127.0.0.1");
logService.saveLog(log);
verify(logMapper).insert(log);
}
}
@@ -0,0 +1,115 @@
package com.water.system.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.water.common.core.exception.BusinessException;
import com.water.system.entity.SysRole;
import com.water.system.entity.dto.RoleRequest;
import com.water.system.mapper.SysRoleMapper;
import com.water.system.service.impl.RoleServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class RoleServiceImplTest {
@Mock
private SysRoleMapper roleMapper;
@InjectMocks
private RoleServiceImpl roleService;
private final ObjectMapper objectMapper = new ObjectMapper();
@BeforeEach
void setUp() {
// ObjectMapper is final, inject manually via reflection or just let it be used
}
@Test
void listRoles_shouldReturnAllRoles() {
SysRole admin = new SysRole();
admin.setId(1L);
admin.setRoleCode("SUPER_ADMIN");
admin.setLevel(1);
SysRole user = new SysRole();
user.setId(4L);
user.setRoleCode("EMPLOYEE");
user.setLevel(4);
when(roleMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(Arrays.asList(admin, user));
List<SysRole> roles = roleService.listRoles();
assertEquals(2, roles.size());
assertEquals("SUPER_ADMIN", roles.get(0).getRoleCode());
}
@Test
void createRole_shouldSucceed() {
RoleRequest request = new RoleRequest();
request.setRoleCode("TEST_ROLE");
request.setRoleName("测试角色");
request.setLevel(3);
request.setPermissions(Arrays.asList("user:read", "user:write"));
when(roleMapper.selectCount(any(LambdaQueryWrapper.class))).thenReturn(0L);
when(roleMapper.insert(any(SysRole.class))).thenAnswer(inv -> {
SysRole r = inv.getArgument(0);
r.setId(10L);
return 1;
});
Long id = roleService.createRole(request);
assertEquals(10L, id);
verify(roleMapper).insert(any(SysRole.class));
}
@Test
void createRole_shouldFailOnDuplicateCode() {
RoleRequest request = new RoleRequest();
request.setRoleCode("SUPER_ADMIN");
request.setRoleName("重复角色");
request.setLevel(1);
when(roleMapper.selectCount(any(LambdaQueryWrapper.class))).thenReturn(1L);
assertThrows(BusinessException.class, () -> roleService.createRole(request));
}
@Test
void deleteRole_shouldFailForAdminRoles() {
SysRole admin = new SysRole();
admin.setId(1L);
admin.setLevel(1);
admin.setRoleCode("SUPER_ADMIN");
when(roleMapper.selectById(1L)).thenReturn(admin);
assertThrows(BusinessException.class, () -> roleService.deleteRole(1L));
}
@Test
void deleteRole_shouldSucceedForNormalRoles() {
SysRole role = new SysRole();
role.setId(5L);
role.setLevel(4);
role.setRoleCode("EMPLOYEE");
when(roleMapper.selectById(5L)).thenReturn(role);
assertDoesNotThrow(() -> roleService.deleteRole(5L));
verify(roleMapper).deleteById(5L);
}
}
@@ -0,0 +1,120 @@
package com.water.system.service;
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.water.common.core.exception.BusinessException;
import com.water.system.entity.SysUser;
import com.water.system.entity.dto.UserCreateRequest;
import com.water.system.entity.dto.UserUpdateRequest;
import com.water.system.mapper.SysUserMapper;
import com.water.system.service.impl.UserServiceImpl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class UserServiceImplTest {
@Mock
private SysUserMapper userMapper;
@InjectMocks
private UserServiceImpl userService;
@Test
void createUser_shouldSucceed() {
UserCreateRequest request = new UserCreateRequest();
request.setUsername("testuser");
request.setPassword("test123");
request.setRealName("测试用户");
request.setPhone("13800138000");
when(userMapper.selectByUsername("testuser")).thenReturn(null);
when(userMapper.insert(any(SysUser.class))).thenAnswer(inv -> {
SysUser u = inv.getArgument(0);
u.setId(100L);
return 1;
});
Long id = userService.createUser(request);
assertEquals(100L, id);
verify(userMapper).insert(any(SysUser.class));
}
@Test
void createUser_shouldFailOnDuplicateUsername() {
UserCreateRequest request = new UserCreateRequest();
request.setUsername("admin");
request.setPassword("pass");
SysUser existing = new SysUser();
existing.setUsername("admin");
when(userMapper.selectByUsername("admin")).thenReturn(existing);
assertThrows(BusinessException.class, () -> userService.createUser(request));
}
@Test
void updateStatus_shouldSucceed() {
SysUser user = new SysUser();
user.setId(1L);
user.setStatus(1);
when(userMapper.selectById(1L)).thenReturn(user);
userService.updateStatus(1L, 0);
verify(userMapper).updateById(argThat(u -> u.getStatus() == 0));
}
@Test
void deleteUser_shouldFailForAdmin() {
SysUser admin = new SysUser();
admin.setId(1L);
admin.setUsername("admin");
when(userMapper.selectById(1L)).thenReturn(admin);
assertThrows(BusinessException.class, () -> userService.deleteUser(1L));
}
@Test
void resetPassword_shouldSucceed() {
SysUser user = new SysUser();
user.setId(5L);
user.setUsername("testuser");
when(userMapper.selectById(5L)).thenReturn(user);
assertDoesNotThrow(() -> userService.resetPassword(5L, "newPass123"));
verify(userMapper).updateById(argThat(u -> u.getPassword() != null));
}
@Test
void pageUsers_shouldReturnPagedResult() {
Page<SysUser> page = new Page<>(1, 10);
SysUser user1 = new SysUser();
user1.setId(1L);
user1.setUsername("user1");
user1.setPassword("hashed");
SysUser user2 = new SysUser();
user2.setId(2L);
user2.setUsername("user2");
user2.setPassword("hashed");
page.setRecords(Arrays.asList(user1, user2));
page.setTotal(2);
when(userMapper.selectPage(any(Page.class), any(LambdaQueryWrapper.class))).thenReturn(page);
IPage<SysUser> result = userService.pageUsers(1, 10, null, null, null);
assertEquals(2, result.getTotal());
// 密码应被清除
assertNull(result.getRecords().get(0).getPassword());
}
}