From 51c292f25b588f95189c3e34895a47290bb3d10f Mon Sep 17 00:00:00 2001 From: bot_dev2 Date: Sun, 14 Jun 2026 15:23:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(wm-system):=20#74=20=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E7=AE=A1=E7=90=86=EF=BC=88=E8=A7=92=E8=89=B2/=E7=94=A8?= =?UTF-8?q?=E6=88=B7/=E9=83=A8=E9=97=A8/=E6=97=A5=E5=BF=97/=E5=AD=97?= =?UTF-8?q?=E5=85=B8=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增强 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向后兼容 --- .../controller/DepartmentController.java | 59 ++++++++ .../controller/DictionaryController.java | 87 +++++++++++ .../system/controller/LogController.java | 32 +++++ .../system/controller/RoleController.java | 66 +++++++++ .../system/controller/UserController.java | 74 ++++++++++ .../water/system/entity/DataDictionary.java | 33 +++++ .../system/entity/DataDictionaryItem.java | 42 ++++++ .../water/system/entity/SysDepartment.java | 46 +++++- .../java/com/water/system/entity/SysLog.java | 61 +++++++- .../java/com/water/system/entity/SysRole.java | 48 ++++++- .../java/com/water/system/entity/SysUser.java | 58 ++++++-- .../water/system/entity/dto/DeptRequest.java | 20 +++ .../system/entity/dto/DictItemRequest.java | 25 ++++ .../water/system/entity/dto/DictRequest.java | 19 +++ .../system/entity/dto/LogQueryRequest.java | 22 +++ .../water/system/entity/dto/PageQuery.java | 14 ++ .../entity/dto/PasswordResetRequest.java | 13 ++ .../water/system/entity/dto/RoleRequest.java | 27 ++++ .../system/entity/dto/UserCreateRequest.java | 22 +++ .../system/entity/dto/UserUpdateRequest.java | 15 ++ .../mapper/DataDictionaryItemMapper.java | 15 ++ .../system/mapper/DataDictionaryMapper.java | 14 ++ .../system/mapper/SysDepartmentMapper.java | 12 +- .../com/water/system/mapper/SysLogMapper.java | 6 +- .../water/system/mapper/SysRoleMapper.java | 16 ++- .../water/system/mapper/SysUserMapper.java | 11 +- .../system/service/DepartmentService.java | 29 ++++ .../system/service/DictionaryService.java | 43 ++++++ .../com/water/system/service/LogService.java | 20 +++ .../com/water/system/service/RoleService.java | 35 +++++ .../com/water/system/service/SysService.java | 87 ++++++----- .../com/water/system/service/UserService.java | 37 +++++ .../service/impl/DepartmentServiceImpl.java | 102 +++++++++++++ .../service/impl/DictionaryServiceImpl.java | 116 +++++++++++++++ .../system/service/impl/LogServiceImpl.java | 62 ++++++++ .../system/service/impl/RoleServiceImpl.java | 103 +++++++++++++ .../system/service/impl/UserServiceImpl.java | 115 +++++++++++++++ .../main/resources/db/V2__system_enhance.sql | 136 ++++++++++++++++++ .../service/DepartmentServiceImplTest.java | 93 ++++++++++++ .../service/DictionaryServiceImplTest.java | 104 ++++++++++++++ .../system/service/LogServiceImplTest.java | 93 ++++++++++++ .../system/service/RoleServiceImplTest.java | 115 +++++++++++++++ .../system/service/UserServiceImplTest.java | 120 ++++++++++++++++ 43 files changed, 2199 insertions(+), 68 deletions(-) create mode 100644 wm-system/src/main/java/com/water/system/controller/DepartmentController.java create mode 100644 wm-system/src/main/java/com/water/system/controller/DictionaryController.java create mode 100644 wm-system/src/main/java/com/water/system/controller/LogController.java create mode 100644 wm-system/src/main/java/com/water/system/controller/RoleController.java create mode 100644 wm-system/src/main/java/com/water/system/controller/UserController.java create mode 100644 wm-system/src/main/java/com/water/system/entity/DataDictionary.java create mode 100644 wm-system/src/main/java/com/water/system/entity/DataDictionaryItem.java create mode 100644 wm-system/src/main/java/com/water/system/entity/dto/DeptRequest.java create mode 100644 wm-system/src/main/java/com/water/system/entity/dto/DictItemRequest.java create mode 100644 wm-system/src/main/java/com/water/system/entity/dto/DictRequest.java create mode 100644 wm-system/src/main/java/com/water/system/entity/dto/LogQueryRequest.java create mode 100644 wm-system/src/main/java/com/water/system/entity/dto/PageQuery.java create mode 100644 wm-system/src/main/java/com/water/system/entity/dto/PasswordResetRequest.java create mode 100644 wm-system/src/main/java/com/water/system/entity/dto/RoleRequest.java create mode 100644 wm-system/src/main/java/com/water/system/entity/dto/UserCreateRequest.java create mode 100644 wm-system/src/main/java/com/water/system/entity/dto/UserUpdateRequest.java create mode 100644 wm-system/src/main/java/com/water/system/mapper/DataDictionaryItemMapper.java create mode 100644 wm-system/src/main/java/com/water/system/mapper/DataDictionaryMapper.java create mode 100644 wm-system/src/main/java/com/water/system/service/DepartmentService.java create mode 100644 wm-system/src/main/java/com/water/system/service/DictionaryService.java create mode 100644 wm-system/src/main/java/com/water/system/service/LogService.java create mode 100644 wm-system/src/main/java/com/water/system/service/RoleService.java create mode 100644 wm-system/src/main/java/com/water/system/service/UserService.java create mode 100644 wm-system/src/main/java/com/water/system/service/impl/DepartmentServiceImpl.java create mode 100644 wm-system/src/main/java/com/water/system/service/impl/DictionaryServiceImpl.java create mode 100644 wm-system/src/main/java/com/water/system/service/impl/LogServiceImpl.java create mode 100644 wm-system/src/main/java/com/water/system/service/impl/RoleServiceImpl.java create mode 100644 wm-system/src/main/java/com/water/system/service/impl/UserServiceImpl.java create mode 100644 wm-system/src/main/resources/db/V2__system_enhance.sql create mode 100644 wm-system/src/test/java/com/water/system/service/DepartmentServiceImplTest.java create mode 100644 wm-system/src/test/java/com/water/system/service/DictionaryServiceImplTest.java create mode 100644 wm-system/src/test/java/com/water/system/service/LogServiceImplTest.java create mode 100644 wm-system/src/test/java/com/water/system/service/RoleServiceImplTest.java create mode 100644 wm-system/src/test/java/com/water/system/service/UserServiceImplTest.java diff --git a/wm-system/src/main/java/com/water/system/controller/DepartmentController.java b/wm-system/src/main/java/com/water/system/controller/DepartmentController.java new file mode 100644 index 00000000..d8b28706 --- /dev/null +++ b/wm-system/src/main/java/com/water/system/controller/DepartmentController.java @@ -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> tree() { + return R.ok(departmentService.getDeptTree()); + } + + @Operation(summary = "获取部门详情") + @GetMapping("/{id}") + public R getById(@PathVariable Long id) { + return R.ok(departmentService.getDeptById(id)); + } + + @Operation(summary = "创建部门") + @PostMapping + public R create(@Valid @RequestBody DeptRequest request) { + return R.ok(departmentService.createDept(request)); + } + + @Operation(summary = "更新部门") + @PutMapping("/{id}") + public R update(@PathVariable Long id, @RequestBody DeptRequest request) { + departmentService.updateDept(id, request); + return R.ok("OK"); + } + + @Operation(summary = "删除部门") + @DeleteMapping("/{id}") + public R delete(@PathVariable Long id) { + departmentService.deleteDept(id); + return R.ok("OK"); + } + + @Operation(summary = "获取子部门列表") + @GetMapping("/{id}/children") + public R> getChildren(@PathVariable Long id) { + return R.ok(departmentService.getChildDepts(id)); + } +} diff --git a/wm-system/src/main/java/com/water/system/controller/DictionaryController.java b/wm-system/src/main/java/com/water/system/controller/DictionaryController.java new file mode 100644 index 00000000..60846122 --- /dev/null +++ b/wm-system/src/main/java/com/water/system/controller/DictionaryController.java @@ -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(@RequestParam(required = false) String keyword) { + return R.ok(dictionaryService.listDictionaries(keyword)); + } + + @Operation(summary = "获取字典详情") + @GetMapping("/{id}") + public R getById(@PathVariable Long id) { + return R.ok(dictionaryService.getDictionaryById(id)); + } + + @Operation(summary = "创建字典") + @PostMapping + public R create(@Valid @RequestBody DictRequest request) { + return R.ok(dictionaryService.createDictionary(request)); + } + + @Operation(summary = "更新字典") + @PutMapping("/{id}") + public R update(@PathVariable Long id, @RequestBody DictRequest request) { + dictionaryService.updateDictionary(id, request); + return R.ok("OK"); + } + + @Operation(summary = "删除字典") + @DeleteMapping("/{id}") + public R delete(@PathVariable Long id) { + dictionaryService.deleteDictionary(id); + return R.ok("OK"); + } + + @Operation(summary = "获取字典项列表(按字典ID)") + @GetMapping("/{dictId}/items") + public R> listItems(@PathVariable Long dictId) { + return R.ok(dictionaryService.listItemsByDictId(dictId)); + } + + @Operation(summary = "根据字典编码获取字典项") + @GetMapping("/code/{dictCode}/items") + public R> listItemsByCode(@PathVariable String dictCode) { + return R.ok(dictionaryService.listItemsByDictCode(dictCode)); + } + + @Operation(summary = "创建字典项") + @PostMapping("/item") + public R createItem(@Valid @RequestBody DictItemRequest request) { + return R.ok(dictionaryService.createItem(request)); + } + + @Operation(summary = "更新字典项") + @PutMapping("/item/{id}") + public R updateItem(@PathVariable Long id, @RequestBody DictItemRequest request) { + dictionaryService.updateItem(id, request); + return R.ok("OK"); + } + + @Operation(summary = "删除字典项") + @DeleteMapping("/item/{id}") + public R deleteItem(@PathVariable Long id) { + dictionaryService.deleteItem(id); + return R.ok("OK"); + } +} diff --git a/wm-system/src/main/java/com/water/system/controller/LogController.java b/wm-system/src/main/java/com/water/system/controller/LogController.java new file mode 100644 index 00000000..700328cb --- /dev/null +++ b/wm-system/src/main/java/com/water/system/controller/LogController.java @@ -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> page(LogQueryRequest request) { + return R.ok(logService.pageLogs(request)); + } + + @Operation(summary = "获取日志详情") + @GetMapping("/{id}") + public R getById(@PathVariable Long id) { + return R.ok(logService.getLogById(id)); + } +} diff --git a/wm-system/src/main/java/com/water/system/controller/RoleController.java b/wm-system/src/main/java/com/water/system/controller/RoleController.java new file mode 100644 index 00000000..be6a3e78 --- /dev/null +++ b/wm-system/src/main/java/com/water/system/controller/RoleController.java @@ -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() { + return R.ok(roleService.listRoles()); + } + + @Operation(summary = "获取角色详情") + @GetMapping("/{id}") + public R getById(@PathVariable Long id) { + return R.ok(roleService.getRoleById(id)); + } + + @Operation(summary = "创建角色") + @PostMapping + public R create(@Valid @RequestBody RoleRequest request) { + return R.ok(roleService.createRole(request)); + } + + @Operation(summary = "更新角色") + @PutMapping("/{id}") + public R update(@PathVariable Long id, @Valid @RequestBody RoleRequest request) { + roleService.updateRole(id, request); + return R.ok("OK"); + } + + @Operation(summary = "删除角色") + @DeleteMapping("/{id}") + public R delete(@PathVariable Long id) { + roleService.deleteRole(id); + return R.ok("OK"); + } + + @Operation(summary = "分配角色权限") + @PutMapping("/{id}/permissions") + public R assignPermissions(@PathVariable Long id, @RequestBody List permissions) { + roleService.assignPermissions(id, permissions); + return R.ok("OK"); + } + + @Operation(summary = "获取用户角色") + @GetMapping("/user/{userId}") + public R> getByUserId(@PathVariable Long userId) { + return R.ok(roleService.getRolesByUserId(userId)); + } +} diff --git a/wm-system/src/main/java/com/water/system/controller/UserController.java b/wm-system/src/main/java/com/water/system/controller/UserController.java new file mode 100644 index 00000000..fee339d3 --- /dev/null +++ b/wm-system/src/main/java/com/water/system/controller/UserController.java @@ -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> 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 getById(@PathVariable Long id) { + return R.ok(userService.getUserById(id)); + } + + @Operation(summary = "创建用户") + @PostMapping + public R create(@Valid @RequestBody UserCreateRequest request) { + return R.ok(userService.createUser(request)); + } + + @Operation(summary = "更新用户信息") + @PutMapping("/{id}") + public R update(@PathVariable Long id, @RequestBody UserUpdateRequest request) { + userService.updateUser(id, request); + return R.ok("OK"); + } + + @Operation(summary = "删除用户") + @DeleteMapping("/{id}") + public R delete(@PathVariable Long id) { + userService.deleteUser(id); + return R.ok("OK"); + } + + @Operation(summary = "停用/启用用户") + @PutMapping("/{id}/status") + public R updateStatus(@PathVariable Long id, @RequestParam Integer status) { + userService.updateStatus(id, status); + return R.ok("OK"); + } + + @Operation(summary = "重置密码") + @PutMapping("/{id}/reset-password") + public R resetPassword(@PathVariable Long id, @Valid @RequestBody PasswordResetRequest request) { + userService.resetPassword(id, request.getNewPassword()); + return R.ok("OK"); + } +} diff --git a/wm-system/src/main/java/com/water/system/entity/DataDictionary.java b/wm-system/src/main/java/com/water/system/entity/DataDictionary.java new file mode 100644 index 00000000..1354af3a --- /dev/null +++ b/wm-system/src/main/java/com/water/system/entity/DataDictionary.java @@ -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; +} diff --git a/wm-system/src/main/java/com/water/system/entity/DataDictionaryItem.java b/wm-system/src/main/java/com/water/system/entity/DataDictionaryItem.java new file mode 100644 index 00000000..6df1517e --- /dev/null +++ b/wm-system/src/main/java/com/water/system/entity/DataDictionaryItem.java @@ -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; +} diff --git a/wm-system/src/main/java/com/water/system/entity/SysDepartment.java b/wm-system/src/main/java/com/water/system/entity/SysDepartment.java index af3f40cc..7d816a82 100644 --- a/wm-system/src/main/java/com/water/system/entity/SysDepartment.java +++ b/wm-system/src/main/java/com/water/system/entity/SysDepartment.java @@ -1,9 +1,47 @@ package com.water.system.entity; + import com.baomidou.mybatisplus.annotation.*; import lombok.Data; -@Data @TableName("sys_department") +import java.time.LocalDateTime; +import java.util.List; + +/** + * 部门实体(树结构) + */ +@Data +@TableName("sys_department") public class SysDepartment { - @TableId(type = IdType.AUTO) private Long id; - private Long parentId; private String deptName, leader, phone; - private Integer sort, status; + @TableId(type = IdType.AUTO) + private Long id; + + /** 父部门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 children; } diff --git a/wm-system/src/main/java/com/water/system/entity/SysLog.java b/wm-system/src/main/java/com/water/system/entity/SysLog.java index 1c8c4783..a1c6cdf0 100644 --- a/wm-system/src/main/java/com/water/system/entity/SysLog.java +++ b/wm-system/src/main/java/com/water/system/entity/SysLog.java @@ -1,11 +1,58 @@ package com.water.system.entity; + import com.baomidou.mybatisplus.annotation.*; -import lombok.Data; import java.time.LocalDateTime; -@Data @TableName("sys_log") +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 系统日志实体 + * 支持: 登录日志(LOGIN) / 操作日志(OPERATE) / 异常日志(EXCEPTION) + */ +@Data +@TableName("sys_log") public class SysLog { - @TableId(type = IdType.AUTO) private Long id; - private Long userId; private String username, logType, module, action; - private String requestUrl, requestMethod, requestParams, responseResult; - private String ip; private Long duration; - @TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime; + @TableId(type = IdType.AUTO) + private Long id; + + /** 操作用户ID */ + 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; } diff --git a/wm-system/src/main/java/com/water/system/entity/SysRole.java b/wm-system/src/main/java/com/water/system/entity/SysRole.java index 5a146e2e..baf89bcd 100644 --- a/wm-system/src/main/java/com/water/system/entity/SysRole.java +++ b/wm-system/src/main/java/com/water/system/entity/SysRole.java @@ -1,10 +1,46 @@ package com.water.system.entity; + import com.baomidou.mybatisplus.annotation.*; -import lombok.Data; import java.time.LocalDateTime; -@Data @TableName("sys_role") +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 系统角色实体 + * 支持5级角色: 1-超级管理员 2-系统管理员 3-部门经理 4-普通员工 5-访客 + */ +@Data +@TableName("sys_role") public class SysRole { - @TableId(type = IdType.AUTO) private Long id; - private String roleCode, roleName, description; - private Integer status, sort; - @TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime; + @TableId(type = IdType.AUTO) + private Long id; + + /** 角色编码 */ + 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; } diff --git a/wm-system/src/main/java/com/water/system/entity/SysUser.java b/wm-system/src/main/java/com/water/system/entity/SysUser.java index eb9fa7fb..63cc2466 100644 --- a/wm-system/src/main/java/com/water/system/entity/SysUser.java +++ b/wm-system/src/main/java/com/water/system/entity/SysUser.java @@ -1,12 +1,54 @@ package com.water.system.entity; + import com.baomidou.mybatisplus.annotation.*; -import lombok.Data; import java.time.LocalDateTime; -@Data @TableName("sys_user") +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 系统用户实体 + */ +@Data +@TableName("sys_user") public class SysUser { - @TableId(type = IdType.AUTO) private Long id; - private String username, password, realName, phone, email; - private Long deptId; private String avatar; - private Integer status; // 0停用 1正常 - @TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime; - @TableField(fill=FieldFill.INSERT_UPDATE) private LocalDateTime updatedTime; + @TableId(type = IdType.AUTO) + private Long id; + + /** 用户名 */ + private String username; + + /** 密码(加密) */ + 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; } diff --git a/wm-system/src/main/java/com/water/system/entity/dto/DeptRequest.java b/wm-system/src/main/java/com/water/system/entity/dto/DeptRequest.java new file mode 100644 index 00000000..2092df88 --- /dev/null +++ b/wm-system/src/main/java/com/water/system/entity/dto/DeptRequest.java @@ -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; +} diff --git a/wm-system/src/main/java/com/water/system/entity/dto/DictItemRequest.java b/wm-system/src/main/java/com/water/system/entity/dto/DictItemRequest.java new file mode 100644 index 00000000..e6660b1e --- /dev/null +++ b/wm-system/src/main/java/com/water/system/entity/dto/DictItemRequest.java @@ -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; +} diff --git a/wm-system/src/main/java/com/water/system/entity/dto/DictRequest.java b/wm-system/src/main/java/com/water/system/entity/dto/DictRequest.java new file mode 100644 index 00000000..0e01cc68 --- /dev/null +++ b/wm-system/src/main/java/com/water/system/entity/dto/DictRequest.java @@ -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; +} diff --git a/wm-system/src/main/java/com/water/system/entity/dto/LogQueryRequest.java b/wm-system/src/main/java/com/water/system/entity/dto/LogQueryRequest.java new file mode 100644 index 00000000..db17e717 --- /dev/null +++ b/wm-system/src/main/java/com/water/system/entity/dto/LogQueryRequest.java @@ -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; +} diff --git a/wm-system/src/main/java/com/water/system/entity/dto/PageQuery.java b/wm-system/src/main/java/com/water/system/entity/dto/PageQuery.java new file mode 100644 index 00000000..ff4f9c22 --- /dev/null +++ b/wm-system/src/main/java/com/water/system/entity/dto/PageQuery.java @@ -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; +} diff --git a/wm-system/src/main/java/com/water/system/entity/dto/PasswordResetRequest.java b/wm-system/src/main/java/com/water/system/entity/dto/PasswordResetRequest.java new file mode 100644 index 00000000..d38c77f2 --- /dev/null +++ b/wm-system/src/main/java/com/water/system/entity/dto/PasswordResetRequest.java @@ -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; +} diff --git a/wm-system/src/main/java/com/water/system/entity/dto/RoleRequest.java b/wm-system/src/main/java/com/water/system/entity/dto/RoleRequest.java new file mode 100644 index 00000000..da5fb7e2 --- /dev/null +++ b/wm-system/src/main/java/com/water/system/entity/dto/RoleRequest.java @@ -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 permissions; + private Integer sort; +} diff --git a/wm-system/src/main/java/com/water/system/entity/dto/UserCreateRequest.java b/wm-system/src/main/java/com/water/system/entity/dto/UserCreateRequest.java new file mode 100644 index 00000000..fff8ad4a --- /dev/null +++ b/wm-system/src/main/java/com/water/system/entity/dto/UserCreateRequest.java @@ -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; +} diff --git a/wm-system/src/main/java/com/water/system/entity/dto/UserUpdateRequest.java b/wm-system/src/main/java/com/water/system/entity/dto/UserUpdateRequest.java new file mode 100644 index 00000000..86ebf9af --- /dev/null +++ b/wm-system/src/main/java/com/water/system/entity/dto/UserUpdateRequest.java @@ -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; +} diff --git a/wm-system/src/main/java/com/water/system/mapper/DataDictionaryItemMapper.java b/wm-system/src/main/java/com/water/system/mapper/DataDictionaryItemMapper.java new file mode 100644 index 00000000..9b68f4c9 --- /dev/null +++ b/wm-system/src/main/java/com/water/system/mapper/DataDictionaryItemMapper.java @@ -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 { + + @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 selectByDictCode(@Param("dictCode") String dictCode); +} diff --git a/wm-system/src/main/java/com/water/system/mapper/DataDictionaryMapper.java b/wm-system/src/main/java/com/water/system/mapper/DataDictionaryMapper.java new file mode 100644 index 00000000..d847e23e --- /dev/null +++ b/wm-system/src/main/java/com/water/system/mapper/DataDictionaryMapper.java @@ -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 { + + @Select("SELECT * FROM sys_data_dictionary WHERE dict_code = #{dictCode} AND deleted = 0") + DataDictionary selectByDictCode(@Param("dictCode") String dictCode); +} diff --git a/wm-system/src/main/java/com/water/system/mapper/SysDepartmentMapper.java b/wm-system/src/main/java/com/water/system/mapper/SysDepartmentMapper.java index 1122a709..107e026b 100644 --- a/wm-system/src/main/java/com/water/system/mapper/SysDepartmentMapper.java +++ b/wm-system/src/main/java/com/water/system/mapper/SysDepartmentMapper.java @@ -1,5 +1,15 @@ package com.water.system.mapper; + import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.water.system.entity.SysDepartment; import org.apache.ibatis.annotations.Mapper; -@Mapper public interface SysDepartmentMapper extends BaseMapper {} +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import java.util.List; + +@Mapper +public interface SysDepartmentMapper extends BaseMapper { + + @Select("SELECT * FROM sys_department WHERE ancestors LIKE CONCAT(#{ancestorPath}, '%') AND deleted = 0 ORDER BY sort") + List selectByAncestorPath(@Param("ancestorPath") String ancestorPath); +} diff --git a/wm-system/src/main/java/com/water/system/mapper/SysLogMapper.java b/wm-system/src/main/java/com/water/system/mapper/SysLogMapper.java index 650c6150..31a5b4ae 100644 --- a/wm-system/src/main/java/com/water/system/mapper/SysLogMapper.java +++ b/wm-system/src/main/java/com/water/system/mapper/SysLogMapper.java @@ -1,5 +1,9 @@ package com.water.system.mapper; + import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.water.system.entity.SysLog; import org.apache.ibatis.annotations.Mapper; -@Mapper public interface SysLogMapper extends BaseMapper {} + +@Mapper +public interface SysLogMapper extends BaseMapper { +} diff --git a/wm-system/src/main/java/com/water/system/mapper/SysRoleMapper.java b/wm-system/src/main/java/com/water/system/mapper/SysRoleMapper.java index d2cc8c92..b9c5e98f 100644 --- a/wm-system/src/main/java/com/water/system/mapper/SysRoleMapper.java +++ b/wm-system/src/main/java/com/water/system/mapper/SysRoleMapper.java @@ -1,5 +1,19 @@ package com.water.system.mapper; + import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.water.system.entity.SysRole; import org.apache.ibatis.annotations.Mapper; -@Mapper public interface SysRoleMapper extends BaseMapper {} +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 { + + @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 selectRolesByUserId(@Param("userId") Long userId); + + @Select("SELECT * FROM sys_role WHERE level <= #{level} AND deleted = 0 ORDER BY level, sort") + List selectRolesByMaxLevel(@Param("level") Integer level); +} diff --git a/wm-system/src/main/java/com/water/system/mapper/SysUserMapper.java b/wm-system/src/main/java/com/water/system/mapper/SysUserMapper.java index 8cd04f51..66decd69 100644 --- a/wm-system/src/main/java/com/water/system/mapper/SysUserMapper.java +++ b/wm-system/src/main/java/com/water/system/mapper/SysUserMapper.java @@ -1,5 +1,14 @@ package com.water.system.mapper; + import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.water.system.entity.SysUser; import org.apache.ibatis.annotations.Mapper; -@Mapper public interface SysUserMapper extends BaseMapper {} +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +@Mapper +public interface SysUserMapper extends BaseMapper { + + @Select("SELECT * FROM sys_user WHERE username = #{username} AND deleted = 0") + SysUser selectByUsername(@Param("username") String username); +} diff --git a/wm-system/src/main/java/com/water/system/service/DepartmentService.java b/wm-system/src/main/java/com/water/system/service/DepartmentService.java new file mode 100644 index 00000000..dec20de5 --- /dev/null +++ b/wm-system/src/main/java/com/water/system/service/DepartmentService.java @@ -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 getDeptTree(); + + /** 获取部门详情 */ + SysDepartment getDeptById(Long id); + + /** 创建部门 */ + Long createDept(DeptRequest request); + + /** 更新部门 */ + void updateDept(Long id, DeptRequest request); + + /** 删除部门 */ + void deleteDept(Long id); + + /** 获取所有子部门 */ + List getChildDepts(Long parentId); +} diff --git a/wm-system/src/main/java/com/water/system/service/DictionaryService.java b/wm-system/src/main/java/com/water/system/service/DictionaryService.java new file mode 100644 index 00000000..73c82f12 --- /dev/null +++ b/wm-system/src/main/java/com/water/system/service/DictionaryService.java @@ -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 listDictionaries(String keyword); + + /** 获取字典详情 */ + DataDictionary getDictionaryById(Long id); + + /** 创建字典 */ + Long createDictionary(DictRequest request); + + /** 更新字典 */ + void updateDictionary(Long id, DictRequest request); + + /** 删除字典 */ + void deleteDictionary(Long id); + + /** 根据字典ID查询字典项 */ + List listItemsByDictId(Long dictId); + + /** 根据字典编码查询字典项 */ + List listItemsByDictCode(String dictCode); + + /** 创建字典项 */ + Long createItem(DictItemRequest request); + + /** 更新字典项 */ + void updateItem(Long id, DictItemRequest request); + + /** 删除字典项 */ + void deleteItem(Long id); +} diff --git a/wm-system/src/main/java/com/water/system/service/LogService.java b/wm-system/src/main/java/com/water/system/service/LogService.java new file mode 100644 index 00000000..c166160a --- /dev/null +++ b/wm-system/src/main/java/com/water/system/service/LogService.java @@ -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 pageLogs(LogQueryRequest request); + + /** 获取日志详情 */ + SysLog getLogById(Long id); + + /** 记录日志 */ + void saveLog(SysLog log); +} diff --git a/wm-system/src/main/java/com/water/system/service/RoleService.java b/wm-system/src/main/java/com/water/system/service/RoleService.java new file mode 100644 index 00000000..7a0173b9 --- /dev/null +++ b/wm-system/src/main/java/com/water/system/service/RoleService.java @@ -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 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 permissions); + + /** 根据用户ID查询角色 */ + List getRolesByUserId(Long userId); + + /** 根据最大级别查询角色 */ + List getRolesByMaxLevel(Integer level); +} diff --git a/wm-system/src/main/java/com/water/system/service/SysService.java b/wm-system/src/main/java/com/water/system/service/SysService.java index a666324e..b4764061 100644 --- a/wm-system/src/main/java/com/water/system/service/SysService.java +++ b/wm-system/src/main/java/com/water/system/service/SysService.java @@ -1,67 +1,76 @@ package com.water.system.service; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; + import com.water.system.entity.*; import com.water.system.mapper.*; +import com.water.system.entity.dto.*; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.*; -@Service @RequiredArgsConstructor + +/** + * 系统管理兼容服务 - 保持向后兼容,内部委托给新的专用服务 + */ +@Service +@RequiredArgsConstructor public class SysService { - private final SysRoleMapper roleMapper; - private final SysUserMapper userMapper; + private final RoleService roleService; + private final UserService userService; + private final DepartmentService departmentService; + private final LogService logService; private final SysMenuMapper menuMapper; - private final SysDepartmentMapper deptMapper; - private final SysLogMapper logMapper; // Roles - public List listRoles() { return roleMapper.selectList(null); } + public List listRoles() { return roleService.listRoles(); } public Long createRole(Map req) { - SysRole r = new SysRole(); - r.setRoleCode((String)req.get("roleCode")); - r.setRoleName((String)req.get("roleName")); - r.setDescription((String)req.get("description")); - r.setStatus(1); r.setSort(0); - roleMapper.insert(r); - return r.getId(); + RoleRequest r = new RoleRequest(); + r.setRoleCode((String) req.get("roleCode")); + r.setRoleName((String) req.get("roleName")); + r.setDescription((String) req.get("description")); + r.setLevel(req.get("level") != null ? ((Number) req.get("level")).intValue() : 4); + r.setDataScope((String) req.get("dataScope")); + return roleService.createRole(r); } + // Users public List listUsers(Integer status) { - return userMapper.selectList(new LambdaQueryWrapper() - .eq(status != null, SysUser::getStatus, status)); + return userService.pageUsers(1, 1000, null, status, null).getRecords(); } public Long createUser(Map req) { - SysUser u = new SysUser(); - u.setUsername((String)req.get("username")); - u.setPassword((String)req.get("password")); - u.setRealName((String)req.get("realName")); - u.setPhone((String)req.get("phone")); - u.setStatus(1); - userMapper.insert(u); - return u.getId(); + UserCreateRequest r = new UserCreateRequest(); + r.setUsername((String) req.get("username")); + r.setPassword((String) req.get("password")); + r.setRealName((String) req.get("realName")); + r.setPhone((String) req.get("phone")); + r.setEmail((String) req.get("email")); + if (req.get("deptId") != null) r.setDeptId(((Number) req.get("deptId")).longValue()); + if (req.get("roleId") != null) r.setRoleId(((Number) req.get("roleId")).longValue()); + return userService.createUser(r); } public void updateUserStatus(Long id, int status) { - SysUser u = userMapper.selectById(id); - if (u == null) throw new RuntimeException("用户不存在"); - u.setStatus(status); - userMapper.updateById(u); + userService.updateStatus(id, status); } + // Menus public List listMenus() { return menuMapper.selectList(null); } + // Departments - public List listDepts() { return deptMapper.selectList(null); } + public List listDepts() { return departmentService.getDeptTree(); } public Long createDept(Map req) { - SysDepartment d = new SysDepartment(); - d.setDeptName((String)req.get("deptName")); - d.setLeader((String)req.get("leader")); - d.setSort(0); d.setStatus(1); - deptMapper.insert(d); - return d.getId(); + DeptRequest r = new DeptRequest(); + r.setDeptName((String) req.get("deptName")); + r.setLeader((String) req.get("leader")); + r.setPhone((String) req.get("phone")); + if (req.get("parentId") != null) r.setParentId(((Number) req.get("parentId")).longValue()); + return departmentService.createDept(r); } + // Logs public List listLogs(String logType, String username) { - LambdaQueryWrapper w = new LambdaQueryWrapper<>(); - if (logType != null) w.eq(SysLog::getLogType, logType); - if (username != null) w.like(SysLog::getUsername, username); - return logMapper.selectList(w.orderByDesc(SysLog::getCreatedTime).last("LIMIT 100")); + LogQueryRequest r = new LogQueryRequest(); + r.setLogType(logType); + r.setUsername(username); + r.setPageNum(1); + r.setPageSize(100); + return logService.pageLogs(r).getRecords(); } } diff --git a/wm-system/src/main/java/com/water/system/service/UserService.java b/wm-system/src/main/java/com/water/system/service/UserService.java new file mode 100644 index 00000000..ae50fa3d --- /dev/null +++ b/wm-system/src/main/java/com/water/system/service/UserService.java @@ -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 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); +} diff --git a/wm-system/src/main/java/com/water/system/service/impl/DepartmentServiceImpl.java b/wm-system/src/main/java/com/water/system/service/impl/DepartmentServiceImpl.java new file mode 100644 index 00000000..0a35edf8 --- /dev/null +++ b/wm-system/src/main/java/com/water/system/service/impl/DepartmentServiceImpl.java @@ -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 getDeptTree() { + List allDepts = deptMapper.selectList( + new LambdaQueryWrapper().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().eq(SysDepartment::getParentId, id) + ); + if (childCount > 0) { + throw new BusinessException("该部门下有子部门,不允许删除"); + } + deptMapper.deleteById(id); + } + + @Override + public List getChildDepts(Long parentId) { + SysDepartment parent = getDeptById(parentId); + String ancestorPath = parent.getAncestors() != null ? parent.getAncestors() + "," + parentId : String.valueOf(parentId); + return deptMapper.selectByAncestorPath(ancestorPath); + } + + private List buildTree(List depts, Long parentId) { + Map> grouped = depts.stream() + .collect(Collectors.groupingBy(SysDepartment::getParentId)); + + List roots = grouped.getOrDefault(parentId, new ArrayList<>()); + for (SysDepartment root : roots) { + root.setChildren(buildTree(depts, root.getId())); + } + return roots; + } +} diff --git a/wm-system/src/main/java/com/water/system/service/impl/DictionaryServiceImpl.java b/wm-system/src/main/java/com/water/system/service/impl/DictionaryServiceImpl.java new file mode 100644 index 00000000..e4c20b38 --- /dev/null +++ b/wm-system/src/main/java/com/water/system/service/impl/DictionaryServiceImpl.java @@ -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 listDictionaries(String keyword) { + LambdaQueryWrapper 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() + .eq(DataDictionaryItem::getDictId, id)); + dictMapper.deleteById(id); + } + + @Override + public List listItemsByDictId(Long dictId) { + return itemMapper.selectList( + new LambdaQueryWrapper() + .eq(DataDictionaryItem::getDictId, dictId) + .orderByAsc(DataDictionaryItem::getSort) + ); + } + + @Override + public List 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); + } +} diff --git a/wm-system/src/main/java/com/water/system/service/impl/LogServiceImpl.java b/wm-system/src/main/java/com/water/system/service/impl/LogServiceImpl.java new file mode 100644 index 00000000..2e454f6a --- /dev/null +++ b/wm-system/src/main/java/com/water/system/service/impl/LogServiceImpl.java @@ -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 pageLogs(LogQueryRequest request) { + Page page = new Page<>(request.getPageNum(), request.getPageSize()); + LambdaQueryWrapper 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); + } +} diff --git a/wm-system/src/main/java/com/water/system/service/impl/RoleServiceImpl.java b/wm-system/src/main/java/com/water/system/service/impl/RoleServiceImpl.java new file mode 100644 index 00000000..54b610bf --- /dev/null +++ b/wm-system/src/main/java/com/water/system/service/impl/RoleServiceImpl.java @@ -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 listRoles() { + return roleMapper.selectList( + new LambdaQueryWrapper().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().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 permissions) { + SysRole role = getRoleById(id); + role.setPermissions(toJson(permissions)); + roleMapper.updateById(role); + } + + @Override + public List getRolesByUserId(Long userId) { + return roleMapper.selectRolesByUserId(userId); + } + + @Override + public List 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()); + } + } +} diff --git a/wm-system/src/main/java/com/water/system/service/impl/UserServiceImpl.java b/wm-system/src/main/java/com/water/system/service/impl/UserServiceImpl.java new file mode 100644 index 00000000..1c5cd0c2 --- /dev/null +++ b/wm-system/src/main/java/com/water/system/service/impl/UserServiceImpl.java @@ -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 pageUsers(Integer pageNum, Integer pageSize, String keyword, Integer status, Long deptId) { + Page page = new Page<>(pageNum, pageSize); + LambdaQueryWrapper 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 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)); + } +} diff --git a/wm-system/src/main/resources/db/V2__system_enhance.sql b/wm-system/src/main/resources/db/V2__system_enhance.sql new file mode 100644 index 00000000..3ddd2861 --- /dev/null +++ b/wm-system/src/main/resources/db/V2__system_enhance.sql @@ -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); diff --git a/wm-system/src/test/java/com/water/system/service/DepartmentServiceImplTest.java b/wm-system/src/test/java/com/water/system/service/DepartmentServiceImplTest.java new file mode 100644 index 00000000..11044e82 --- /dev/null +++ b/wm-system/src/test/java/com/water/system/service/DepartmentServiceImplTest.java @@ -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 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); + } +} diff --git a/wm-system/src/test/java/com/water/system/service/DictionaryServiceImplTest.java b/wm-system/src/test/java/com/water/system/service/DictionaryServiceImplTest.java new file mode 100644 index 00000000..37c5610b --- /dev/null +++ b/wm-system/src/test/java/com/water/system/service/DictionaryServiceImplTest.java @@ -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 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); + } +} diff --git a/wm-system/src/test/java/com/water/system/service/LogServiceImplTest.java b/wm-system/src/test/java/com/water/system/service/LogServiceImplTest.java new file mode 100644 index 00000000..a2ca8d6e --- /dev/null +++ b/wm-system/src/test/java/com/water/system/service/LogServiceImplTest.java @@ -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 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 result = logService.pageLogs(request); + + assertEquals(2, result.getTotal()); + assertEquals("LOGIN", result.getRecords().get(0).getLogType()); + } + + @Test + void pageLogs_withFilters_shouldApplyAllConditions() { + Page 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 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); + } +} diff --git a/wm-system/src/test/java/com/water/system/service/RoleServiceImplTest.java b/wm-system/src/test/java/com/water/system/service/RoleServiceImplTest.java new file mode 100644 index 00000000..11dc631e --- /dev/null +++ b/wm-system/src/test/java/com/water/system/service/RoleServiceImplTest.java @@ -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 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); + } +} diff --git a/wm-system/src/test/java/com/water/system/service/UserServiceImplTest.java b/wm-system/src/test/java/com/water/system/service/UserServiceImplTest.java new file mode 100644 index 00000000..ee063590 --- /dev/null +++ b/wm-system/src/test/java/com/water/system/service/UserServiceImplTest.java @@ -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 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 result = userService.pageUsers(1, 10, null, null, null); + + assertEquals(2, result.getTotal()); + // 密码应被清除 + assertNull(result.getRecords().get(0).getPassword()); + } +}