Parcourir la source

feat(wm-revenue): #47 平台运维审计+用户授权管理

- AuditLog entity/mapper/service/controller: 操作日志记录/查询/统计/导出
- PlatformRole entity/mapper: 角色CRUD/权限管理
- RoleUserRelation entity/mapper: 角色用户关联
- PlatformUser entity/mapper/service/controller: 用户管理/角色分配/启用禁用/密码重置
- RoleService: 角色CRUD/权限分配/角色用户关联
- PlatformUserService: 用户CRUD/角色分配/启用禁用/密码重置
- DDL: V_platform_audit.sql (4张表 + 索引 + 5个默认角色)
- Test: PlatformAuditTest (12个测试用例)
- 30个API端点: /api/revenue/audit/*, /api/revenue/role/*, /api/revenue/platform-user/*
bot_dev2 il y a 5 jours
Parent
révision
324409d78b

+ 125
- 0
db/V_platform_audit.sql Voir le fichier

@@ -0,0 +1,125 @@
1
+-- =============================================
2
+-- 平台运维审计 + 用户授权管理 DDL
3
+-- Version: 1.0
4
+-- Module: wm-revenue
5
+-- =============================================
6
+
7
+-- 1. 操作日志表
8
+CREATE TABLE IF NOT EXISTS pa_audit_log (
9
+    id              BIGSERIAL PRIMARY KEY,
10
+    operator        VARCHAR(100),
11
+    operator_id     BIGINT,
12
+    module          VARCHAR(100),
13
+    action          VARCHAR(50),
14
+    target_type     VARCHAR(50),
15
+    target_id       BIGINT,
16
+    before_value    TEXT,
17
+    after_value     TEXT,
18
+    ip_address      VARCHAR(50),
19
+    user_agent      VARCHAR(500),
20
+    request_url     VARCHAR(500),
21
+    request_method  VARCHAR(10),
22
+    result          VARCHAR(20)     DEFAULT 'success',
23
+    remark          VARCHAR(500),
24
+    created_at      TIMESTAMP       DEFAULT CURRENT_TIMESTAMP
25
+);
26
+
27
+CREATE INDEX idx_audit_log_module ON pa_audit_log(module);
28
+CREATE INDEX idx_audit_log_action ON pa_audit_log(action);
29
+CREATE INDEX idx_audit_log_operator_id ON pa_audit_log(operator_id);
30
+CREATE INDEX idx_audit_log_created_at ON pa_audit_log(created_at);
31
+CREATE INDEX idx_audit_log_target ON pa_audit_log(target_type, target_id);
32
+
33
+COMMENT ON TABLE pa_audit_log IS '操作日志(CRUD审计)';
34
+COMMENT ON COLUMN pa_audit_log.operator IS '操作人姓名';
35
+COMMENT ON COLUMN pa_audit_log.operator_id IS '操作人ID';
36
+COMMENT ON COLUMN pa_audit_log.module IS '模块名称';
37
+COMMENT ON COLUMN pa_audit_log.action IS '操作类型:create/update/delete/query/export/login/logout';
38
+COMMENT ON COLUMN pa_audit_log.target_type IS '目标类型';
39
+COMMENT ON COLUMN pa_audit_log.target_id IS '目标ID';
40
+COMMENT ON COLUMN pa_audit_log.before_value IS '操作前数据(JSON)';
41
+COMMENT ON COLUMN pa_audit_log.after_value IS '操作后数据(JSON)';
42
+
43
+-- 2. 平台角色表
44
+CREATE TABLE IF NOT EXISTS pa_platform_role (
45
+    id              BIGSERIAL PRIMARY KEY,
46
+    role_name       VARCHAR(100)    NOT NULL,
47
+    role_code       VARCHAR(50)     NOT NULL UNIQUE,
48
+    description     VARCHAR(500),
49
+    permissions     TEXT            DEFAULT '[]',
50
+    data_scope      VARCHAR(20)     DEFAULT 'self',
51
+    enabled         INTEGER         DEFAULT 1,
52
+    deleted         INTEGER         DEFAULT 0,
53
+    created_at      TIMESTAMP       DEFAULT CURRENT_TIMESTAMP,
54
+    updated_at      TIMESTAMP       DEFAULT CURRENT_TIMESTAMP
55
+);
56
+
57
+CREATE INDEX idx_platform_role_code ON pa_platform_role(role_code);
58
+CREATE INDEX idx_platform_role_enabled ON pa_platform_role(enabled);
59
+
60
+COMMENT ON TABLE pa_platform_role IS '平台角色';
61
+COMMENT ON COLUMN pa_platform_role.role_name IS '角色名称';
62
+COMMENT ON COLUMN pa_platform_role.role_code IS '角色编码';
63
+COMMENT ON COLUMN pa_platform_role.permissions IS '权限列表(JSON数组)';
64
+COMMENT ON COLUMN pa_platform_role.data_scope IS '数据范围:all/dept/self/custom';
65
+
66
+-- 3. 角色-用户关联表
67
+CREATE TABLE IF NOT EXISTS pa_role_user_relation (
68
+    id              BIGSERIAL PRIMARY KEY,
69
+    role_id         BIGINT          NOT NULL,
70
+    user_id         BIGINT          NOT NULL,
71
+    created_at      TIMESTAMP       DEFAULT CURRENT_TIMESTAMP
72
+);
73
+
74
+CREATE INDEX idx_role_user_relation_role ON pa_role_user_relation(role_id);
75
+CREATE INDEX idx_role_user_relation_user ON pa_role_user_relation(user_id);
76
+CREATE UNIQUE INDEX idx_role_user_relation_unique ON pa_role_user_relation(role_id, user_id);
77
+
78
+COMMENT ON TABLE pa_role_user_relation IS '角色-用户关联';
79
+
80
+-- 4. 平台用户表
81
+CREATE TABLE IF NOT EXISTS pa_platform_user (
82
+    id              BIGSERIAL PRIMARY KEY,
83
+    username        VARCHAR(100)    NOT NULL UNIQUE,
84
+    real_name       VARCHAR(100),
85
+    phone           VARCHAR(20),
86
+    email           VARCHAR(100),
87
+    department_id   BIGINT,
88
+    role_id         BIGINT,
89
+    status          INTEGER         DEFAULT 1,
90
+    password        VARCHAR(200),
91
+    last_login_at   TIMESTAMP,
92
+    deleted         INTEGER         DEFAULT 0,
93
+    created_at      TIMESTAMP       DEFAULT CURRENT_TIMESTAMP,
94
+    updated_at      TIMESTAMP       DEFAULT CURRENT_TIMESTAMP
95
+);
96
+
97
+CREATE INDEX idx_platform_user_username ON pa_platform_user(username);
98
+CREATE INDEX idx_platform_user_department ON pa_platform_user(department_id);
99
+CREATE INDEX idx_platform_user_role ON pa_platform_user(role_id);
100
+CREATE INDEX idx_platform_user_status ON pa_platform_user(status);
101
+
102
+COMMENT ON TABLE pa_platform_user IS '平台用户';
103
+COMMENT ON COLUMN pa_platform_user.username IS '登录用户名';
104
+COMMENT ON COLUMN pa_platform_user.status IS '状态:1-启用 0-禁用';
105
+
106
+-- =============================================
107
+-- 默认角色数据
108
+-- =============================================
109
+INSERT INTO pa_platform_role (role_name, role_code, description, permissions, data_scope, enabled) VALUES
110
+('超级管理员', 'SUPER_ADMIN', '系统超级管理员,拥有所有权限',
111
+ '["user:read","user:write","user:delete","role:read","role:write","role:delete","audit:read","audit:export","system:config"]',
112
+ 'all', 1),
113
+('运维管理员', 'OPS_ADMIN', '运维管理员,可查看和管理所有运维相关功能',
114
+ '["user:read","user:write","role:read","audit:read","audit:export","ops:manage"]',
115
+ 'all', 1),
116
+('普通用户', 'NORMAL_USER', '普通用户,基本查看权限',
117
+ '["user:read","audit:read"]',
118
+ 'self', 1),
119
+('部门管理员', 'DEPT_ADMIN', '部门管理员,管理本部门用户',
120
+ '["user:read","user:write","role:read","audit:read"]',
121
+ 'dept', 1),
122
+('审计员', 'AUDITOR', '审计员,只读审计日志权限',
123
+ '["audit:read","audit:export","user:read","role:read"]',
124
+ 'all', 1)
125
+ON CONFLICT (role_code) DO NOTHING;

+ 93
- 0
wm-revenue/src/main/java/com/water/revenue/controller/AuditLogController.java Voir le fichier

@@ -0,0 +1,93 @@
1
+package com.water.revenue.controller;
2
+
3
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
4
+import com.water.common.core.result.R;
5
+import com.water.revenue.entity.AuditLog;
6
+import com.water.revenue.service.AuditLogService;
7
+import io.swagger.v3.oas.annotations.Operation;
8
+import io.swagger.v3.oas.annotations.tags.Tag;
9
+import lombok.RequiredArgsConstructor;
10
+import org.springframework.format.annotation.DateTimeFormat;
11
+import org.springframework.web.bind.annotation.*;
12
+
13
+import java.time.LocalDateTime;
14
+import java.util.List;
15
+import java.util.Map;
16
+
17
+/**
18
+ * 操作日志 Controller
19
+ */
20
+@Tag(name = "运维审计-操作日志")
21
+@RestController
22
+@RequestMapping("/api/revenue/audit")
23
+@RequiredArgsConstructor
24
+public class AuditLogController {
25
+
26
+    private final AuditLogService auditLogService;
27
+
28
+    @Operation(summary = "记录操作日志")
29
+    @PostMapping("/record")
30
+    public R<AuditLog> record(@RequestBody Map<String, Object> req) {
31
+        String operator = (String) req.get("operator");
32
+        Long operatorId = req.get("operatorId") != null ? Long.parseLong(String.valueOf(req.get("operatorId"))) : null;
33
+        String module = (String) req.get("module");
34
+        String action = (String) req.get("action");
35
+        String targetType = (String) req.get("targetType");
36
+        Long targetId = req.get("targetId") != null ? Long.parseLong(String.valueOf(req.get("targetId"))) : null;
37
+        String beforeValue = (String) req.get("beforeValue");
38
+        String afterValue = (String) req.get("afterValue");
39
+        String ipAddress = (String) req.get("ipAddress");
40
+        String userAgent = (String) req.get("userAgent");
41
+        return R.ok(auditLogService.record(operator, operatorId, module, action,
42
+                targetType, targetId, beforeValue, afterValue, ipAddress, userAgent));
43
+    }
44
+
45
+    @Operation(summary = "分页查询操作日志")
46
+    @GetMapping("/list")
47
+    public R<Page<AuditLog>> list(
48
+            @RequestParam(defaultValue = "1") int page,
49
+            @RequestParam(defaultValue = "10") int size,
50
+            @RequestParam(required = false) String module,
51
+            @RequestParam(required = false) String action,
52
+            @RequestParam(required = false) String operator,
53
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
54
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime) {
55
+        return R.ok(auditLogService.list(page, size, module, action, operator, startTime, endTime));
56
+    }
57
+
58
+    @Operation(summary = "获取日志详情")
59
+    @GetMapping("/{id}")
60
+    public R<AuditLog> detail(@PathVariable Long id) {
61
+        return R.ok(auditLogService.getDetail(id));
62
+    }
63
+
64
+    @Operation(summary = "按模块统计操作数量")
65
+    @GetMapping("/stats/module")
66
+    public R<List<Map<String, Object>>> statsByModule() {
67
+        return R.ok(auditLogService.statsByModule());
68
+    }
69
+
70
+    @Operation(summary = "按操作类型统计")
71
+    @GetMapping("/stats/action")
72
+    public R<List<Map<String, Object>>> statsByAction() {
73
+        return R.ok(auditLogService.statsByAction());
74
+    }
75
+
76
+    @Operation(summary = "导出操作日志")
77
+    @GetMapping("/export")
78
+    public R<List<AuditLog>> export(
79
+            @RequestParam(required = false) String module,
80
+            @RequestParam(required = false) String action,
81
+            @RequestParam(required = false) String operator,
82
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
83
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime) {
84
+        return R.ok(auditLogService.export(module, action, operator, startTime, endTime));
85
+    }
86
+
87
+    @Operation(summary = "清理历史日志")
88
+    @DeleteMapping("/clean")
89
+    public R<Map<String, Object>> cleanLogs(@RequestParam(defaultValue = "90") int days) {
90
+        int deleted = auditLogService.cleanOldLogs(days);
91
+        return R.ok(Map.of("deleted", deleted, "days", days));
92
+    }
93
+}

+ 95
- 0
wm-revenue/src/main/java/com/water/revenue/controller/PlatformUserController.java Voir le fichier

@@ -0,0 +1,95 @@
1
+package com.water.revenue.controller;
2
+
3
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
4
+import com.water.common.core.result.R;
5
+import com.water.revenue.entity.PlatformUser;
6
+import com.water.revenue.service.PlatformUserService;
7
+import io.swagger.v3.oas.annotations.Operation;
8
+import io.swagger.v3.oas.annotations.tags.Tag;
9
+import lombok.RequiredArgsConstructor;
10
+import org.springframework.web.bind.annotation.*;
11
+
12
+import java.util.List;
13
+import java.util.Map;
14
+
15
+/**
16
+ * 平台用户管理 Controller
17
+ */
18
+@Tag(name = "运维审计-用户管理")
19
+@RestController
20
+@RequestMapping("/api/revenue/platform-user")
21
+@RequiredArgsConstructor
22
+public class PlatformUserController {
23
+
24
+    private final PlatformUserService userService;
25
+
26
+    @Operation(summary = "分页查询用户列表")
27
+    @GetMapping("/list")
28
+    public R<Page<PlatformUser>> list(
29
+            @RequestParam(defaultValue = "1") int page,
30
+            @RequestParam(defaultValue = "10") int size,
31
+            @RequestParam(required = false) String keyword,
32
+            @RequestParam(required = false) Integer status,
33
+            @RequestParam(required = false) Long departmentId) {
34
+        return R.ok(userService.listUsers(page, size, keyword, status, departmentId));
35
+    }
36
+
37
+    @Operation(summary = "获取用户详情")
38
+    @GetMapping("/{id}")
39
+    public R<PlatformUser> detail(@PathVariable Long id) {
40
+        return R.ok(userService.getUser(id));
41
+    }
42
+
43
+    @Operation(summary = "创建用户")
44
+    @PostMapping
45
+    public R<PlatformUser> create(@RequestBody PlatformUser user) {
46
+        return R.ok(userService.createUser(user));
47
+    }
48
+
49
+    @Operation(summary = "更新用户信息")
50
+    @PutMapping("/{id}")
51
+    public R<String> update(@PathVariable Long id, @RequestBody PlatformUser user) {
52
+        userService.updateUser(id, user);
53
+        return R.ok("更新成功");
54
+    }
55
+
56
+    @Operation(summary = "删除用户")
57
+    @DeleteMapping("/{id}")
58
+    public R<String> delete(@PathVariable Long id) {
59
+        userService.deleteUser(id);
60
+        return R.ok("删除成功");
61
+    }
62
+
63
+    @Operation(summary = "启用/禁用用户")
64
+    @PutMapping("/{id}/status")
65
+    public R<String> toggleStatus(@PathVariable Long id, @RequestBody Map<String, Integer> req) {
66
+        userService.toggleStatus(id, req.get("status"));
67
+        return R.ok("状态更新成功");
68
+    }
69
+
70
+    @Operation(summary = "重置用户密码")
71
+    @PutMapping("/{id}/reset-password")
72
+    public R<String> resetPassword(@PathVariable Long id, @RequestBody Map<String, String> req) {
73
+        userService.resetPassword(id, req.get("password"));
74
+        return R.ok("密码重置成功");
75
+    }
76
+
77
+    @Operation(summary = "分配用户角色")
78
+    @PutMapping("/{id}/role")
79
+    public R<String> assignRole(@PathVariable Long id, @RequestBody Map<String, Long> req) {
80
+        userService.assignRole(id, req.get("roleId"));
81
+        return R.ok("角色分配成功");
82
+    }
83
+
84
+    @Operation(summary = "根据角色查询用户")
85
+    @GetMapping("/by-role/{roleId}")
86
+    public R<List<PlatformUser>> getByRole(@PathVariable Long roleId) {
87
+        return R.ok(userService.getUsersByRoleId(roleId));
88
+    }
89
+
90
+    @Operation(summary = "根据部门查询用户")
91
+    @GetMapping("/by-department/{departmentId}")
92
+    public R<List<PlatformUser>> getByDepartment(@PathVariable Long departmentId) {
93
+        return R.ok(userService.getUsersByDepartment(departmentId));
94
+    }
95
+}

+ 124
- 0
wm-revenue/src/main/java/com/water/revenue/controller/RoleController.java Voir le fichier

@@ -0,0 +1,124 @@
1
+package com.water.revenue.controller;
2
+
3
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
4
+import com.water.common.core.result.R;
5
+import com.water.revenue.entity.PlatformRole;
6
+import com.water.revenue.service.RoleService;
7
+import io.swagger.v3.oas.annotations.Operation;
8
+import io.swagger.v3.oas.annotations.tags.Tag;
9
+import lombok.RequiredArgsConstructor;
10
+import org.springframework.web.bind.annotation.*;
11
+
12
+import java.util.List;
13
+import java.util.Map;
14
+
15
+/**
16
+ * 角色权限 Controller
17
+ */
18
+@Tag(name = "运维审计-角色权限")
19
+@RestController
20
+@RequestMapping("/api/revenue/role")
21
+@RequiredArgsConstructor
22
+public class RoleController {
23
+
24
+    private final RoleService roleService;
25
+
26
+    // ==================== 角色 CRUD ====================
27
+
28
+    @Operation(summary = "分页查询角色列表")
29
+    @GetMapping("/list")
30
+    public R<Page<PlatformRole>> list(
31
+            @RequestParam(defaultValue = "1") int page,
32
+            @RequestParam(defaultValue = "10") int size,
33
+            @RequestParam(required = false) String keyword,
34
+            @RequestParam(required = false) Integer enabled) {
35
+        return R.ok(roleService.listRoles(page, size, keyword, enabled));
36
+    }
37
+
38
+    @Operation(summary = "获取所有启用角色")
39
+    @GetMapping("/all")
40
+    public R<List<PlatformRole>> all() {
41
+        return R.ok(roleService.allRoles());
42
+    }
43
+
44
+    @Operation(summary = "获取角色详情")
45
+    @GetMapping("/{id}")
46
+    public R<PlatformRole> detail(@PathVariable Long id) {
47
+        return R.ok(roleService.getRole(id));
48
+    }
49
+
50
+    @Operation(summary = "创建角色")
51
+    @PostMapping
52
+    public R<PlatformRole> create(@RequestBody PlatformRole role) {
53
+        return R.ok(roleService.createRole(role));
54
+    }
55
+
56
+    @Operation(summary = "更新角色")
57
+    @PutMapping("/{id}")
58
+    public R<String> update(@PathVariable Long id, @RequestBody PlatformRole role) {
59
+        roleService.updateRole(id, role);
60
+        return R.ok("更新成功");
61
+    }
62
+
63
+    @Operation(summary = "删除角色")
64
+    @DeleteMapping("/{id}")
65
+    public R<String> delete(@PathVariable Long id) {
66
+        roleService.deleteRole(id);
67
+        return R.ok("删除成功");
68
+    }
69
+
70
+    // ==================== 权限分配 ====================
71
+
72
+    @Operation(summary = "更新角色权限")
73
+    @PutMapping("/{id}/permissions")
74
+    public R<String> updatePermissions(@PathVariable Long id, @RequestBody Map<String, String> req) {
75
+        roleService.updatePermissions(id, req.get("permissions"));
76
+        return R.ok("权限更新成功");
77
+    }
78
+
79
+    @Operation(summary = "获取角色权限")
80
+    @GetMapping("/{id}/permissions")
81
+    public R<Map<String, Object>> getPermissions(@PathVariable Long id) {
82
+        String permissions = roleService.getPermissions(id);
83
+        return R.ok(Map.of("roleId", id, "permissions", permissions != null ? permissions : "[]"));
84
+    }
85
+
86
+    // ==================== 角色-用户关联 ====================
87
+
88
+    @Operation(summary = "为用户分配角色")
89
+    @PostMapping("/assign")
90
+    public R<String> assignRole(@RequestBody Map<String, Long> req) {
91
+        roleService.assignRole(req.get("userId"), req.get("roleId"));
92
+        return R.ok("分配成功");
93
+    }
94
+
95
+    @Operation(summary = "移除用户角色")
96
+    @PostMapping("/remove")
97
+    public R<String> removeRole(@RequestBody Map<String, Long> req) {
98
+        roleService.removeRole(req.get("userId"), req.get("roleId"));
99
+        return R.ok("移除成功");
100
+    }
101
+
102
+    @Operation(summary = "获取用户的所有角色ID")
103
+    @GetMapping("/user/{userId}")
104
+    public R<List<Long>> getUserRoles(@PathVariable Long userId) {
105
+        return R.ok(roleService.getUserRoleIds(userId));
106
+    }
107
+
108
+    @Operation(summary = "获取角色下的所有用户ID")
109
+    @GetMapping("/{id}/users")
110
+    public R<List<Long>> getRoleUsers(@PathVariable Long id) {
111
+        return R.ok(roleService.getRoleUserIds(id));
112
+    }
113
+
114
+    @Operation(summary = "批量分配角色")
115
+    @PostMapping("/batch-assign")
116
+    public R<String> batchAssign(@RequestBody Map<String, Object> req) {
117
+        @SuppressWarnings("unchecked")
118
+        List<Number> userIdNums = (List<Number>) req.get("userIds");
119
+        List<Long> userIds = userIdNums.stream().map(Number::longValue).toList();
120
+        Long roleId = Long.parseLong(String.valueOf(req.get("roleId")));
121
+        roleService.batchAssignRole(userIds, roleId);
122
+        return R.ok("批量分配成功");
123
+    }
124
+}

+ 60
- 0
wm-revenue/src/main/java/com/water/revenue/entity/AuditLog.java Voir le fichier

@@ -0,0 +1,60 @@
1
+package com.water.revenue.entity;
2
+
3
+import com.baomidou.mybatisplus.annotation.*;
4
+import lombok.Data;
5
+import java.time.LocalDateTime;
6
+
7
+/**
8
+ * 操作日志实体(CRUD审计)
9
+ */
10
+@Data
11
+@TableName("pa_audit_log")
12
+public class AuditLog {
13
+
14
+    @TableId(type = IdType.AUTO)
15
+    private Long id;
16
+
17
+    /** 操作人姓名 */
18
+    private String operator;
19
+
20
+    /** 操作人ID */
21
+    private Long operatorId;
22
+
23
+    /** 模块名称 */
24
+    private String module;
25
+
26
+    /** 操作类型:create/update/delete/query/export/login/logout */
27
+    private String action;
28
+
29
+    /** 目标类型:user/role/announcement/meter 等 */
30
+    private String targetType;
31
+
32
+    /** 目标ID */
33
+    private Long targetId;
34
+
35
+    /** 操作前数据(JSON) */
36
+    private String beforeValue;
37
+
38
+    /** 操作后数据(JSON) */
39
+    private String afterValue;
40
+
41
+    /** 请求IP地址 */
42
+    private String ipAddress;
43
+
44
+    /** 浏览器User-Agent */
45
+    private String userAgent;
46
+
47
+    /** 请求URL */
48
+    private String requestUrl;
49
+
50
+    /** 请求方法 */
51
+    private String requestMethod;
52
+
53
+    /** 操作结果:success/fail */
54
+    private String result;
55
+
56
+    /** 备注 */
57
+    private String remark;
58
+
59
+    private LocalDateTime createdAt;
60
+}

+ 41
- 0
wm-revenue/src/main/java/com/water/revenue/entity/PlatformRole.java Voir le fichier

@@ -0,0 +1,41 @@
1
+package com.water.revenue.entity;
2
+
3
+import com.baomidou.mybatisplus.annotation.*;
4
+import lombok.Data;
5
+import java.time.LocalDateTime;
6
+
7
+/**
8
+ * 平台角色实体
9
+ */
10
+@Data
11
+@TableName("pa_platform_role")
12
+public class PlatformRole {
13
+
14
+    @TableId(type = IdType.AUTO)
15
+    private Long id;
16
+
17
+    /** 角色名称 */
18
+    private String roleName;
19
+
20
+    /** 角色编码 */
21
+    private String roleCode;
22
+
23
+    /** 角色描述 */
24
+    private String description;
25
+
26
+    /** 权限列表(JSON数组,如 ["user:read","user:write","role:read"]) */
27
+    private String permissions;
28
+
29
+    /** 数据范围:all-全部 dept-本部门 self-仅本人 custom-自定义 */
30
+    private String dataScope;
31
+
32
+    /** 是否启用:1-启用 0-禁用 */
33
+    private Integer enabled;
34
+
35
+    @TableLogic
36
+    private Integer deleted;
37
+
38
+    private LocalDateTime createdAt;
39
+
40
+    private LocalDateTime updatedAt;
41
+}

+ 50
- 0
wm-revenue/src/main/java/com/water/revenue/entity/PlatformUser.java Voir le fichier

@@ -0,0 +1,50 @@
1
+package com.water.revenue.entity;
2
+
3
+import com.baomidou.mybatisplus.annotation.*;
4
+import lombok.Data;
5
+import java.time.LocalDateTime;
6
+
7
+/**
8
+ * 平台用户实体
9
+ */
10
+@Data
11
+@TableName("pa_platform_user")
12
+public class PlatformUser {
13
+
14
+    @TableId(type = IdType.AUTO)
15
+    private Long id;
16
+
17
+    /** 用户名(登录名) */
18
+    private String username;
19
+
20
+    /** 真实姓名 */
21
+    private String realName;
22
+
23
+    /** 手机号 */
24
+    private String phone;
25
+
26
+    /** 邮箱 */
27
+    private String email;
28
+
29
+    /** 部门ID */
30
+    private Long departmentId;
31
+
32
+    /** 角色ID(主角色) */
33
+    private Long roleId;
34
+
35
+    /** 状态:1-启用 0-禁用 */
36
+    private Integer status;
37
+
38
+    /** 密码(BCrypt加密) */
39
+    private String password;
40
+
41
+    /** 最后登录时间 */
42
+    private LocalDateTime lastLoginAt;
43
+
44
+    @TableLogic
45
+    private Integer deleted;
46
+
47
+    private LocalDateTime createdAt;
48
+
49
+    private LocalDateTime updatedAt;
50
+}

+ 24
- 0
wm-revenue/src/main/java/com/water/revenue/entity/RoleUserRelation.java Voir le fichier

@@ -0,0 +1,24 @@
1
+package com.water.revenue.entity;
2
+
3
+import com.baomidou.mybatisplus.annotation.*;
4
+import lombok.Data;
5
+import java.time.LocalDateTime;
6
+
7
+/**
8
+ * 角色-用户关联实体
9
+ */
10
+@Data
11
+@TableName("pa_role_user_relation")
12
+public class RoleUserRelation {
13
+
14
+    @TableId(type = IdType.AUTO)
15
+    private Long id;
16
+
17
+    /** 角色ID */
18
+    private Long roleId;
19
+
20
+    /** 用户ID */
21
+    private Long userId;
22
+
23
+    private LocalDateTime createdAt;
24
+}

+ 9
- 0
wm-revenue/src/main/java/com/water/revenue/mapper/AuditLogMapper.java Voir le fichier

@@ -0,0 +1,9 @@
1
+package com.water.revenue.mapper;
2
+
3
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4
+import com.water.revenue.entity.AuditLog;
5
+import org.apache.ibatis.annotations.Mapper;
6
+
7
+@Mapper
8
+public interface AuditLogMapper extends BaseMapper<AuditLog> {
9
+}

+ 9
- 0
wm-revenue/src/main/java/com/water/revenue/mapper/PlatformRoleMapper.java Voir le fichier

@@ -0,0 +1,9 @@
1
+package com.water.revenue.mapper;
2
+
3
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4
+import com.water.revenue.entity.PlatformRole;
5
+import org.apache.ibatis.annotations.Mapper;
6
+
7
+@Mapper
8
+public interface PlatformRoleMapper extends BaseMapper<PlatformRole> {
9
+}

+ 9
- 0
wm-revenue/src/main/java/com/water/revenue/mapper/PlatformUserMapper.java Voir le fichier

@@ -0,0 +1,9 @@
1
+package com.water.revenue.mapper;
2
+
3
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4
+import com.water.revenue.entity.PlatformUser;
5
+import org.apache.ibatis.annotations.Mapper;
6
+
7
+@Mapper
8
+public interface PlatformUserMapper extends BaseMapper<PlatformUser> {
9
+}

+ 9
- 0
wm-revenue/src/main/java/com/water/revenue/mapper/RoleUserRelationMapper.java Voir le fichier

@@ -0,0 +1,9 @@
1
+package com.water.revenue.mapper;
2
+
3
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4
+import com.water.revenue.entity.RoleUserRelation;
5
+import org.apache.ibatis.annotations.Mapper;
6
+
7
+@Mapper
8
+public interface RoleUserRelationMapper extends BaseMapper<RoleUserRelation> {
9
+}

+ 157
- 0
wm-revenue/src/main/java/com/water/revenue/service/AuditLogService.java Voir le fichier

@@ -0,0 +1,157 @@
1
+package com.water.revenue.service;
2
+
3
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
4
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
5
+import com.water.revenue.entity.AuditLog;
6
+import com.water.revenue.mapper.AuditLogMapper;
7
+import lombok.RequiredArgsConstructor;
8
+import lombok.extern.slf4j.Slf4j;
9
+import org.springframework.stereotype.Service;
10
+
11
+import java.time.LocalDateTime;
12
+import java.util.*;
13
+
14
+/**
15
+ * 操作日志服务
16
+ */
17
+@Slf4j
18
+@Service
19
+@RequiredArgsConstructor
20
+public class AuditLogService {
21
+
22
+    private final AuditLogMapper auditLogMapper;
23
+
24
+    /**
25
+     * 记录操作日志
26
+     */
27
+    public AuditLog record(String operator, Long operatorId, String module, String action,
28
+                           String targetType, Long targetId, String beforeValue,
29
+                           String afterValue, String ipAddress, String userAgent) {
30
+        AuditLog auditLog = new AuditLog();
31
+        auditLog.setOperator(operator);
32
+        auditLog.setOperatorId(operatorId);
33
+        auditLog.setModule(module);
34
+        auditLog.setAction(action);
35
+        auditLog.setTargetType(targetType);
36
+        auditLog.setTargetId(targetId);
37
+        auditLog.setBeforeValue(beforeValue);
38
+        auditLog.setAfterValue(afterValue);
39
+        auditLog.setIpAddress(ipAddress);
40
+        auditLog.setUserAgent(userAgent);
41
+        auditLog.setResult("success");
42
+        auditLogMapper.insert(auditLog);
43
+        log.info("Audit log recorded: module={}, action={}, operator={}", module, action, operator);
44
+        return auditLog;
45
+    }
46
+
47
+    /**
48
+     * 分页查询操作日志
49
+     */
50
+    public Page<AuditLog> list(int page, int size, String module, String action,
51
+                                String operator, LocalDateTime startTime, LocalDateTime endTime) {
52
+        LambdaQueryWrapper<AuditLog> qw = new LambdaQueryWrapper<>();
53
+        if (module != null && !module.isEmpty()) {
54
+            qw.eq(AuditLog::getModule, module);
55
+        }
56
+        if (action != null && !action.isEmpty()) {
57
+            qw.eq(AuditLog::getAction, action);
58
+        }
59
+        if (operator != null && !operator.isEmpty()) {
60
+            qw.like(AuditLog::getOperator, operator);
61
+        }
62
+        if (startTime != null) {
63
+            qw.ge(AuditLog::getCreatedAt, startTime);
64
+        }
65
+        if (endTime != null) {
66
+            qw.le(AuditLog::getCreatedAt, endTime);
67
+        }
68
+        qw.orderByDesc(AuditLog::getCreatedAt);
69
+        return auditLogMapper.selectPage(new Page<>(page, size), qw);
70
+    }
71
+
72
+    /**
73
+     * 获取日志详情
74
+     */
75
+    public AuditLog getDetail(Long id) {
76
+        return auditLogMapper.selectById(id);
77
+    }
78
+
79
+    /**
80
+     * 按模块统计操作数量
81
+     */
82
+    public List<Map<String, Object>> statsByModule() {
83
+        LambdaQueryWrapper<AuditLog> qw = new LambdaQueryWrapper<>();
84
+        qw.select(AuditLog::getModule);
85
+        List<AuditLog> list = auditLogMapper.selectList(qw);
86
+        Map<String, Long> countMap = new LinkedHashMap<>();
87
+        for (AuditLog a : list) {
88
+            countMap.put(a.getModule(), countMap.getOrDefault(a.getModule(), 0L) + 1);
89
+        }
90
+        List<Map<String, Object>> result = new ArrayList<>();
91
+        countMap.forEach((module, count) -> {
92
+            Map<String, Object> m = new HashMap<>();
93
+            m.put("module", module);
94
+            m.put("count", count);
95
+            result.add(m);
96
+        });
97
+        return result;
98
+    }
99
+
100
+    /**
101
+     * 按操作类型统计
102
+     */
103
+    public List<Map<String, Object>> statsByAction() {
104
+        LambdaQueryWrapper<AuditLog> qw = new LambdaQueryWrapper<>();
105
+        qw.select(AuditLog::getAction);
106
+        List<AuditLog> list = auditLogMapper.selectList(qw);
107
+        Map<String, Long> countMap = new LinkedHashMap<>();
108
+        for (AuditLog a : list) {
109
+            countMap.put(a.getAction(), countMap.getOrDefault(a.getAction(), 0L) + 1);
110
+        }
111
+        List<Map<String, Object>> result = new ArrayList<>();
112
+        countMap.forEach((action, count) -> {
113
+            Map<String, Object> m = new HashMap<>();
114
+            m.put("action", action);
115
+            m.put("count", count);
116
+            result.add(m);
117
+        });
118
+        return result;
119
+    }
120
+
121
+    /**
122
+     * 导出日志(返回全量数据,由Controller处理下载)
123
+     */
124
+    public List<AuditLog> export(String module, String action, String operator,
125
+                                  LocalDateTime startTime, LocalDateTime endTime) {
126
+        LambdaQueryWrapper<AuditLog> qw = new LambdaQueryWrapper<>();
127
+        if (module != null && !module.isEmpty()) {
128
+            qw.eq(AuditLog::getModule, module);
129
+        }
130
+        if (action != null && !action.isEmpty()) {
131
+            qw.eq(AuditLog::getAction, action);
132
+        }
133
+        if (operator != null && !operator.isEmpty()) {
134
+            qw.like(AuditLog::getOperator, operator);
135
+        }
136
+        if (startTime != null) {
137
+            qw.ge(AuditLog::getCreatedAt, startTime);
138
+        }
139
+        if (endTime != null) {
140
+            qw.le(AuditLog::getCreatedAt, endTime);
141
+        }
142
+        qw.orderByDesc(AuditLog::getCreatedAt);
143
+        return auditLogMapper.selectList(qw);
144
+    }
145
+
146
+    /**
147
+     * 清理指定天数前的日志
148
+     */
149
+    public int cleanOldLogs(int days) {
150
+        LocalDateTime cutoff = LocalDateTime.now().minusDays(days);
151
+        LambdaQueryWrapper<AuditLog> qw = new LambdaQueryWrapper<>();
152
+        qw.lt(AuditLog::getCreatedAt, cutoff);
153
+        int deleted = auditLogMapper.delete(qw);
154
+        log.info("Cleaned {} audit logs older than {} days", deleted, days);
155
+        return deleted;
156
+    }
157
+}

+ 164
- 0
wm-revenue/src/main/java/com/water/revenue/service/PlatformUserService.java Voir le fichier

@@ -0,0 +1,164 @@
1
+package com.water.revenue.service;
2
+
3
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
4
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
5
+import com.water.revenue.entity.PlatformUser;
6
+import com.water.revenue.mapper.PlatformUserMapper;
7
+import lombok.RequiredArgsConstructor;
8
+import lombok.extern.slf4j.Slf4j;
9
+import org.springframework.stereotype.Service;
10
+
11
+import java.time.LocalDateTime;
12
+import java.util.List;
13
+
14
+/**
15
+ * 平台用户管理服务
16
+ */
17
+@Slf4j
18
+@Service
19
+@RequiredArgsConstructor
20
+public class PlatformUserService {
21
+
22
+    private final PlatformUserMapper userMapper;
23
+
24
+    /**
25
+     * 分页查询用户
26
+     */
27
+    public Page<PlatformUser> listUsers(int page, int size, String keyword,
28
+                                         Integer status, Long departmentId) {
29
+        LambdaQueryWrapper<PlatformUser> qw = new LambdaQueryWrapper<>();
30
+        if (keyword != null && !keyword.isEmpty()) {
31
+            qw.and(w -> w.like(PlatformUser::getUsername, keyword)
32
+                    .or().like(PlatformUser::getRealName, keyword)
33
+                    .or().like(PlatformUser::getPhone, keyword));
34
+        }
35
+        if (status != null) {
36
+            qw.eq(PlatformUser::getStatus, status);
37
+        }
38
+        if (departmentId != null) {
39
+            qw.eq(PlatformUser::getDepartmentId, departmentId);
40
+        }
41
+        qw.orderByDesc(PlatformUser::getCreatedAt);
42
+        return userMapper.selectPage(new Page<>(page, size), qw);
43
+    }
44
+
45
+    /**
46
+     * 获取用户详情
47
+     */
48
+    public PlatformUser getUser(Long id) {
49
+        PlatformUser user = userMapper.selectById(id);
50
+        if (user != null) {
51
+            user.setPassword(null); // 不返回密码
52
+        }
53
+        return user;
54
+    }
55
+
56
+    /**
57
+     * 根据用户名查找用户
58
+     */
59
+    public PlatformUser findByUsername(String username) {
60
+        LambdaQueryWrapper<PlatformUser> qw = new LambdaQueryWrapper<>();
61
+        qw.eq(PlatformUser::getUsername, username);
62
+        return userMapper.selectOne(qw);
63
+    }
64
+
65
+    /**
66
+     * 创建用户
67
+     */
68
+    public PlatformUser createUser(PlatformUser user) {
69
+        // 检查用户名是否已存在
70
+        PlatformUser existing = findByUsername(user.getUsername());
71
+        if (existing != null) {
72
+            throw new RuntimeException("用户名已存在: " + user.getUsername());
73
+        }
74
+        if (user.getStatus() == null) {
75
+            user.setStatus(1); // 默认启用
76
+        }
77
+        userMapper.insert(user);
78
+        log.info("Platform user created: id={}, username={}", user.getId(), user.getUsername());
79
+        user.setPassword(null);
80
+        return user;
81
+    }
82
+
83
+    /**
84
+     * 更新用户信息
85
+     */
86
+    public void updateUser(Long id, PlatformUser user) {
87
+        user.setId(id);
88
+        // 不允许通过此方法修改密码
89
+        user.setPassword(null);
90
+        userMapper.updateById(user);
91
+        log.info("Platform user updated: id={}", id);
92
+    }
93
+
94
+    /**
95
+     * 删除用户(逻辑删除)
96
+     */
97
+    public void deleteUser(Long id) {
98
+        userMapper.deleteById(id);
99
+        log.info("Platform user deleted: id={}", id);
100
+    }
101
+
102
+    /**
103
+     * 启用/禁用用户
104
+     */
105
+    public void toggleStatus(Long id, Integer status) {
106
+        PlatformUser user = new PlatformUser();
107
+        user.setId(id);
108
+        user.setStatus(status);
109
+        userMapper.updateById(user);
110
+        log.info("Platform user status changed: id={}, status={}", id, status);
111
+    }
112
+
113
+    /**
114
+     * 密码重置(设置为默认密码)
115
+     */
116
+    public void resetPassword(Long id, String newPassword) {
117
+        PlatformUser user = new PlatformUser();
118
+        user.setId(id);
119
+        user.setPassword(newPassword);
120
+        userMapper.updateById(user);
121
+        log.info("Platform user password reset: id={}", id);
122
+    }
123
+
124
+    /**
125
+     * 分配角色
126
+     */
127
+    public void assignRole(Long userId, Long roleId) {
128
+        PlatformUser user = new PlatformUser();
129
+        user.setId(userId);
130
+        user.setRoleId(roleId);
131
+        userMapper.updateById(user);
132
+        log.info("Platform user role assigned: userId={}, roleId={}", userId, roleId);
133
+    }
134
+
135
+    /**
136
+     * 更新最后登录时间
137
+     */
138
+    public void updateLastLogin(Long userId) {
139
+        PlatformUser user = new PlatformUser();
140
+        user.setId(userId);
141
+        user.setLastLoginAt(LocalDateTime.now());
142
+        userMapper.updateById(user);
143
+    }
144
+
145
+    /**
146
+     * 根据角色ID查询用户列表
147
+     */
148
+    public List<PlatformUser> getUsersByRoleId(Long roleId) {
149
+        LambdaQueryWrapper<PlatformUser> qw = new LambdaQueryWrapper<>();
150
+        qw.eq(PlatformUser::getRoleId, roleId);
151
+        qw.eq(PlatformUser::getStatus, 1);
152
+        return userMapper.selectList(qw);
153
+    }
154
+
155
+    /**
156
+     * 根据部门ID查询用户列表
157
+     */
158
+    public List<PlatformUser> getUsersByDepartment(Long departmentId) {
159
+        LambdaQueryWrapper<PlatformUser> qw = new LambdaQueryWrapper<>();
160
+        qw.eq(PlatformUser::getDepartmentId, departmentId);
161
+        qw.eq(PlatformUser::getStatus, 1);
162
+        return userMapper.selectList(qw);
163
+    }
164
+}

+ 186
- 0
wm-revenue/src/main/java/com/water/revenue/service/RoleService.java Voir le fichier

@@ -0,0 +1,186 @@
1
+package com.water.revenue.service;
2
+
3
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
4
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
5
+import com.water.revenue.entity.PlatformRole;
6
+import com.water.revenue.entity.RoleUserRelation;
7
+import com.water.revenue.mapper.PlatformRoleMapper;
8
+import com.water.revenue.mapper.RoleUserRelationMapper;
9
+import lombok.RequiredArgsConstructor;
10
+import lombok.extern.slf4j.Slf4j;
11
+import org.springframework.stereotype.Service;
12
+import org.springframework.transaction.annotation.Transactional;
13
+
14
+import java.util.*;
15
+import java.util.stream.Collectors;
16
+
17
+/**
18
+ * 角色权限服务
19
+ */
20
+@Slf4j
21
+@Service
22
+@RequiredArgsConstructor
23
+public class RoleService {
24
+
25
+    private final PlatformRoleMapper roleMapper;
26
+    private final RoleUserRelationMapper relationMapper;
27
+
28
+    // ==================== 角色 CRUD ====================
29
+
30
+    /**
31
+     * 分页查询角色
32
+     */
33
+    public Page<PlatformRole> listRoles(int page, int size, String keyword, Integer enabled) {
34
+        LambdaQueryWrapper<PlatformRole> qw = new LambdaQueryWrapper<>();
35
+        if (keyword != null && !keyword.isEmpty()) {
36
+            qw.and(w -> w.like(PlatformRole::getRoleName, keyword)
37
+                    .or().like(PlatformRole::getRoleCode, keyword)
38
+                    .or().like(PlatformRole::getDescription, keyword));
39
+        }
40
+        if (enabled != null) {
41
+            qw.eq(PlatformRole::getEnabled, enabled);
42
+        }
43
+        qw.orderByDesc(PlatformRole::getCreatedAt);
44
+        return roleMapper.selectPage(new Page<>(page, size), qw);
45
+    }
46
+
47
+    /**
48
+     * 获取所有角色列表
49
+     */
50
+    public List<PlatformRole> allRoles() {
51
+        LambdaQueryWrapper<PlatformRole> qw = new LambdaQueryWrapper<>();
52
+        qw.eq(PlatformRole::getEnabled, 1);
53
+        qw.orderByDesc(PlatformRole::getCreatedAt);
54
+        return roleMapper.selectList(qw);
55
+    }
56
+
57
+    /**
58
+     * 获取角色详情
59
+     */
60
+    public PlatformRole getRole(Long id) {
61
+        return roleMapper.selectById(id);
62
+    }
63
+
64
+    /**
65
+     * 创建角色
66
+     */
67
+    public PlatformRole createRole(PlatformRole role) {
68
+        if (role.getEnabled() == null) {
69
+            role.setEnabled(1);
70
+        }
71
+        if (role.getDataScope() == null) {
72
+            role.setDataScope("self");
73
+        }
74
+        roleMapper.insert(role);
75
+        log.info("Role created: id={}, name={}, code={}", role.getId(), role.getRoleName(), role.getRoleCode());
76
+        return role;
77
+    }
78
+
79
+    /**
80
+     * 更新角色
81
+     */
82
+    public void updateRole(Long id, PlatformRole role) {
83
+        role.setId(id);
84
+        roleMapper.updateById(role);
85
+        log.info("Role updated: id={}", id);
86
+    }
87
+
88
+    /**
89
+     * 删除角色(逻辑删除,同时清理关联关系)
90
+     */
91
+    @Transactional
92
+    public void deleteRole(Long id) {
93
+        roleMapper.deleteById(id);
94
+        LambdaQueryWrapper<RoleUserRelation> qw = new LambdaQueryWrapper<>();
95
+        qw.eq(RoleUserRelation::getRoleId, id);
96
+        relationMapper.delete(qw);
97
+        log.info("Role deleted: id={}, relations cleaned", id);
98
+    }
99
+
100
+    // ==================== 权限分配 ====================
101
+
102
+    /**
103
+     * 更新角色权限
104
+     */
105
+    public void updatePermissions(Long roleId, String permissions) {
106
+        PlatformRole role = new PlatformRole();
107
+        role.setId(roleId);
108
+        role.setPermissions(permissions);
109
+        roleMapper.updateById(role);
110
+        log.info("Role permissions updated: roleId={}", roleId);
111
+    }
112
+
113
+    /**
114
+     * 获取角色权限
115
+     */
116
+    public String getPermissions(Long roleId) {
117
+        PlatformRole role = roleMapper.selectById(roleId);
118
+        return role != null ? role.getPermissions() : null;
119
+    }
120
+
121
+    // ==================== 角色-用户关联 ====================
122
+
123
+    /**
124
+     * 为用户分配角色
125
+     */
126
+    @Transactional
127
+    public void assignRole(Long userId, Long roleId) {
128
+        // 检查是否已存在
129
+        LambdaQueryWrapper<RoleUserRelation> qw = new LambdaQueryWrapper<>();
130
+        qw.eq(RoleUserRelation::getUserId, userId);
131
+        qw.eq(RoleUserRelation::getRoleId, roleId);
132
+        if (relationMapper.selectCount(qw) > 0) {
133
+            log.info("User {} already has role {}", userId, roleId);
134
+            return;
135
+        }
136
+        RoleUserRelation relation = new RoleUserRelation();
137
+        relation.setUserId(userId);
138
+        relation.setRoleId(roleId);
139
+        relationMapper.insert(relation);
140
+        log.info("Role assigned: userId={}, roleId={}", userId, roleId);
141
+    }
142
+
143
+    /**
144
+     * 移除用户角色
145
+     */
146
+    public void removeRole(Long userId, Long roleId) {
147
+        LambdaQueryWrapper<RoleUserRelation> qw = new LambdaQueryWrapper<>();
148
+        qw.eq(RoleUserRelation::getUserId, userId);
149
+        qw.eq(RoleUserRelation::getRoleId, roleId);
150
+        relationMapper.delete(qw);
151
+        log.info("Role removed: userId={}, roleId={}", userId, roleId);
152
+    }
153
+
154
+    /**
155
+     * 获取用户的所有角色ID
156
+     */
157
+    public List<Long> getUserRoleIds(Long userId) {
158
+        LambdaQueryWrapper<RoleUserRelation> qw = new LambdaQueryWrapper<>();
159
+        qw.eq(RoleUserRelation::getUserId, userId);
160
+        return relationMapper.selectList(qw).stream()
161
+                .map(RoleUserRelation::getRoleId)
162
+                .collect(Collectors.toList());
163
+    }
164
+
165
+    /**
166
+     * 获取角色下的所有用户ID
167
+     */
168
+    public List<Long> getRoleUserIds(Long roleId) {
169
+        LambdaQueryWrapper<RoleUserRelation> qw = new LambdaQueryWrapper<>();
170
+        qw.eq(RoleUserRelation::getRoleId, roleId);
171
+        return relationMapper.selectList(qw).stream()
172
+                .map(RoleUserRelation::getUserId)
173
+                .collect(Collectors.toList());
174
+    }
175
+
176
+    /**
177
+     * 批量分配角色给多个用户
178
+     */
179
+    @Transactional
180
+    public void batchAssignRole(List<Long> userIds, Long roleId) {
181
+        for (Long userId : userIds) {
182
+            assignRole(userId, roleId);
183
+        }
184
+        log.info("Batch role assigned: userIds={}, roleId={}", userIds, roleId);
185
+    }
186
+}

+ 265
- 0
wm-revenue/src/test/java/com/water/revenue/PlatformAuditTest.java Voir le fichier

@@ -0,0 +1,265 @@
1
+package com.water.revenue;
2
+
3
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
4
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
5
+import com.water.revenue.entity.AuditLog;
6
+import com.water.revenue.entity.PlatformRole;
7
+import com.water.revenue.entity.PlatformUser;
8
+import com.water.revenue.entity.RoleUserRelation;
9
+import com.water.revenue.mapper.AuditLogMapper;
10
+import com.water.revenue.mapper.PlatformRoleMapper;
11
+import com.water.revenue.mapper.PlatformUserMapper;
12
+import com.water.revenue.mapper.RoleUserRelationMapper;
13
+import com.water.revenue.service.AuditLogService;
14
+import com.water.revenue.service.PlatformUserService;
15
+import com.water.revenue.service.RoleService;
16
+import org.junit.jupiter.api.BeforeEach;
17
+import org.junit.jupiter.api.DisplayName;
18
+import org.junit.jupiter.api.Nested;
19
+import org.junit.jupiter.api.Test;
20
+import org.junit.jupiter.api.extension.ExtendWith;
21
+import org.mockito.Mock;
22
+import org.mockito.junit.jupiter.MockitoExtension;
23
+
24
+import java.util.List;
25
+import java.util.Map;
26
+
27
+import static org.junit.jupiter.api.Assertions.*;
28
+import static org.mockito.ArgumentMatchers.*;
29
+import static org.mockito.Mockito.*;
30
+
31
+@ExtendWith(MockitoExtension.class)
32
+class PlatformAuditTest {
33
+
34
+    @Mock
35
+    private AuditLogMapper auditLogMapper;
36
+
37
+    @Mock
38
+    private PlatformRoleMapper roleMapper;
39
+
40
+    @Mock
41
+    private RoleUserRelationMapper relationMapper;
42
+
43
+    @Mock
44
+    private PlatformUserMapper userMapper;
45
+
46
+    private AuditLogService auditLogService;
47
+    private RoleService roleService;
48
+    private PlatformUserService userService;
49
+
50
+    @BeforeEach
51
+    void setUp() {
52
+        auditLogService = new AuditLogService(auditLogMapper);
53
+        roleService = new RoleService(roleMapper, relationMapper);
54
+        userService = new PlatformUserService(userMapper);
55
+    }
56
+
57
+    // ==================== 操作日志测试 ====================
58
+
59
+    @Nested
60
+    @DisplayName("操作日志服务测试")
61
+    class AuditLogTests {
62
+
63
+        @Test
64
+        @DisplayName("记录操作日志 - 正常记录")
65
+        void record_success() {
66
+            when(auditLogMapper.insert(any(AuditLog.class))).thenReturn(1);
67
+
68
+            AuditLog log = auditLogService.record(
69
+                    "admin", 1L, "用户管理", "create",
70
+                    "user", 100L, null, "{\"name\":\"test\"}",
71
+                    "192.168.1.1", "Mozilla/5.0");
72
+
73
+            assertNotNull(log);
74
+            assertEquals("admin", log.getOperator());
75
+            assertEquals("create", log.getAction());
76
+            assertEquals("success", log.getResult());
77
+            verify(auditLogMapper).insert(any(AuditLog.class));
78
+        }
79
+
80
+        @Test
81
+        @DisplayName("分页查询日志 - 按模块过滤")
82
+        void list_filterByModule() {
83
+            Page<AuditLog> mockPage = new Page<>(1, 10);
84
+            mockPage.setRecords(List.of(createLog(1L, "用户管理", "create")));
85
+            mockPage.setTotal(1);
86
+            when(auditLogMapper.selectPage(any(Page.class), any(LambdaQueryWrapper.class)))
87
+                    .thenReturn(mockPage);
88
+
89
+            Page<AuditLog> result = auditLogService.list(1, 10, "用户管理", null, null, null, null);
90
+
91
+            assertNotNull(result);
92
+            assertEquals(1, result.getRecords().size());
93
+            assertEquals("用户管理", result.getRecords().get(0).getModule());
94
+        }
95
+
96
+        @Test
97
+        @DisplayName("按模块统计 - 返回统计列表")
98
+        void statsByModule_returnsStats() {
99
+            List<AuditLog> logs = List.of(
100
+                    createLog(1L, "用户管理", "create"),
101
+                    createLog(2L, "用户管理", "update"),
102
+                    createLog(3L, "角色管理", "create")
103
+            );
104
+            when(auditLogMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(logs);
105
+
106
+            List<Map<String, Object>> stats = auditLogService.statsByModule();
107
+
108
+            assertNotNull(stats);
109
+            assertEquals(2, stats.size());
110
+        }
111
+
112
+        @Test
113
+        @DisplayName("清理历史日志")
114
+        void cleanOldLogs_deletesRecords() {
115
+            when(auditLogMapper.delete(any(LambdaQueryWrapper.class))).thenReturn(15);
116
+
117
+            int deleted = auditLogService.cleanOldLogs(90);
118
+
119
+            assertEquals(15, deleted);
120
+            verify(auditLogMapper).delete(any(LambdaQueryWrapper.class));
121
+        }
122
+    }
123
+
124
+    // ==================== 角色服务测试 ====================
125
+
126
+    @Nested
127
+    @DisplayName("角色权限服务测试")
128
+    class RoleTests {
129
+
130
+        @Test
131
+        @DisplayName("创建角色 - 设置默认值")
132
+        void createRole_setsDefaults() {
133
+            PlatformRole role = new PlatformRole();
134
+            role.setRoleName("测试角色");
135
+            role.setRoleCode("TEST_ROLE");
136
+
137
+            when(roleMapper.insert(any(PlatformRole.class))).thenReturn(1);
138
+
139
+            PlatformRole result = roleService.createRole(role);
140
+
141
+            assertEquals(1, result.getEnabled());
142
+            assertEquals("self", result.getDataScope());
143
+            verify(roleMapper).insert(role);
144
+        }
145
+
146
+        @Test
147
+        @DisplayName("删除角色 - 同时清理关联关系")
148
+        void deleteRole_cleansRelations() {
149
+            when(roleMapper.deleteById(1L)).thenReturn(1);
150
+            when(relationMapper.delete(any(LambdaQueryWrapper.class))).thenReturn(2);
151
+
152
+            roleService.deleteRole(1L);
153
+
154
+            verify(roleMapper).deleteById(1L);
155
+            verify(relationMapper).delete(any(LambdaQueryWrapper.class));
156
+        }
157
+
158
+        @Test
159
+        @DisplayName("分配角色 - 用户已有角色时不重复分配")
160
+        void assignRole_skipIfExists() {
161
+            when(relationMapper.selectCount(any(LambdaQueryWrapper.class))).thenReturn(1L);
162
+
163
+            roleService.assignRole(1L, 1L);
164
+
165
+            verify(relationMapper, never()).insert(any(RoleUserRelation.class));
166
+        }
167
+
168
+        @Test
169
+        @DisplayName("分配角色 - 正常分配")
170
+        void assignRole_success() {
171
+            when(relationMapper.selectCount(any(LambdaQueryWrapper.class))).thenReturn(0L);
172
+            when(relationMapper.insert(any(RoleUserRelation.class))).thenReturn(1);
173
+
174
+            roleService.assignRole(1L, 1L);
175
+
176
+            verify(relationMapper).insert(any(RoleUserRelation.class));
177
+        }
178
+
179
+        @Test
180
+        @DisplayName("更新角色权限")
181
+        void updatePermissions_success() {
182
+            when(roleMapper.updateById(any(PlatformRole.class))).thenReturn(1);
183
+
184
+            roleService.updatePermissions(1L, "[\"user:read\",\"user:write\"]");
185
+
186
+            verify(roleMapper).updateById(argThat(r ->
187
+                    r.getId() == 1L && "[\"user:read\",\"user:write\"]".equals(r.getPermissions())));
188
+        }
189
+    }
190
+
191
+    // ==================== 用户管理服务测试 ====================
192
+
193
+    @Nested
194
+    @DisplayName("用户管理服务测试")
195
+    class UserTests {
196
+
197
+        @Test
198
+        @DisplayName("创建用户 - 用户名已存在时抛异常")
199
+        void createUser_duplicateUsername_throwsException() {
200
+            PlatformUser user = new PlatformUser();
201
+            user.setUsername("existing");
202
+            user.setRealName("已存在");
203
+
204
+            when(userMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(user);
205
+
206
+            assertThrows(RuntimeException.class, () -> userService.createUser(user));
207
+            verify(userMapper, never()).insert(any(PlatformUser.class));
208
+        }
209
+
210
+        @Test
211
+        @DisplayName("创建用户 - 正常创建")
212
+        void createUser_success() {
213
+            PlatformUser user = new PlatformUser();
214
+            user.setUsername("newuser");
215
+            user.setRealName("新用户");
216
+
217
+            when(userMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(null);
218
+            when(userMapper.insert(any(PlatformUser.class))).thenReturn(1);
219
+
220
+            PlatformUser result = userService.createUser(user);
221
+
222
+            assertEquals(1, result.getStatus());
223
+            assertNull(result.getPassword()); // 密码不应返回
224
+            verify(userMapper).insert(user);
225
+        }
226
+
227
+        @Test
228
+        @DisplayName("启用/禁用用户")
229
+        void toggleStatus_success() {
230
+            when(userMapper.updateById(any(PlatformUser.class))).thenReturn(1);
231
+
232
+            userService.toggleStatus(1L, 0);
233
+
234
+            verify(userMapper).updateById(argThat(u ->
235
+                    u.getId() == 1L && u.getStatus() == 0));
236
+        }
237
+
238
+        @Test
239
+        @DisplayName("获取用户详情 - 不返回密码")
240
+        void getUser_hidesPassword() {
241
+            PlatformUser user = new PlatformUser();
242
+            user.setId(1L);
243
+            user.setUsername("admin");
244
+            user.setPassword("$2a$10$hashed_password");
245
+            when(userMapper.selectById(1L)).thenReturn(user);
246
+
247
+            PlatformUser result = userService.getUser(1L);
248
+
249
+            assertNotNull(result);
250
+            assertNull(result.getPassword());
251
+        }
252
+    }
253
+
254
+    // ==================== Helper ====================
255
+
256
+    private AuditLog createLog(Long id, String module, String action) {
257
+        AuditLog log = new AuditLog();
258
+        log.setId(id);
259
+        log.setModule(module);
260
+        log.setAction(action);
261
+        log.setOperator("admin");
262
+        log.setResult("success");
263
+        return log;
264
+    }
265
+}