feat(wm-system): #14 文档管理与系统管理

文档管理:
- 文档 CRUD / 分类管理 / 版本控制
- DocService + DocController (/system/doc/*)

系统管理:
- 角色管理(5级角色预设)
- 用户管理(新增/编辑/停用)
- 菜单管理 / 部门管理
- 日志管理(登录/操作/异常日志)
- SysService + SysController (/system/*)

DDL: 8 张表 (document/category/version/role/user/menu/department/log)
This commit is contained in:
2026-06-14 13:52:57 +08:00
parent f808efdc80
commit c84bf79d9d
24 changed files with 515 additions and 0 deletions
+50
View File
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.water</groupId>
<artifactId>wm-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>wm-system</artifactId>
<name>wm-system</name>
<description>文档管理与系统管理模块</description>
<dependencies>
<dependency>
<groupId>com.water</groupId>
<artifactId>wm-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,13 @@
package com.water.system;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.mybatis.spring.annotation.MapperScan;
@SpringBootApplication(scanBasePackages = "com.water")
@MapperScan("com.water.system.mapper")
public class SystemApplication {
public static void main(String[] args) {
SpringApplication.run(SystemApplication.class, args);
}
}
@@ -0,0 +1,19 @@
package com.water.system.controller;
import com.water.common.core.result.R;
import com.water.system.entity.*;
import com.water.system.service.DocService;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@Tag(name="文档管理") @RestController @RequestMapping("/system/doc") @RequiredArgsConstructor
public class DocController {
private final DocService svc;
@GetMapping("/list") public R<List<Document>> list(@RequestParam(required=false) String categoryId, @RequestParam(required=false) String keyword) { return R.ok(svc.listDocs(categoryId, keyword)); }
@GetMapping("/{id}") public R<Document> get(@PathVariable Long id) { return R.ok(svc.getDoc(id)); }
@PostMapping public R<Long> create(@RequestBody Map<String,Object> req) { return R.ok(svc.createDoc(req)); }
@PutMapping("/{id}") public R<String> update(@PathVariable Long id, @RequestBody Map<String,Object> req) { svc.updateDoc(id, req); return R.ok("OK"); }
@DeleteMapping("/{id}") public R<String> delete(@PathVariable Long id) { svc.deleteDoc(id); return R.ok("OK"); }
@GetMapping("/category/list") public R<List<DocumentCategory>> categories() { return R.ok(svc.listCategories()); }
@GetMapping("/{docId}/versions") public R<List<DocumentVersion>> versions(@PathVariable Long docId) { return R.ok(svc.getVersions(docId)); }
}
@@ -0,0 +1,21 @@
package com.water.system.controller;
import com.water.common.core.result.R;
import com.water.system.entity.*;
import com.water.system.service.SysService;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@Tag(name="系统管理") @RestController @RequestMapping("/system") @RequiredArgsConstructor
public class SysController {
private final SysService svc;
@GetMapping("/role/list") public R<List<SysRole>> roles() { return R.ok(svc.listRoles()); }
@PostMapping("/role") public R<Long> createRole(@RequestBody Map<String,Object> req) { return R.ok(svc.createRole(req)); }
@GetMapping("/user/list") public R<List<SysUser>> users(@RequestParam(required=false) Integer status) { return R.ok(svc.listUsers(status)); }
@PostMapping("/user") public R<Long> createUser(@RequestBody Map<String,Object> req) { return R.ok(svc.createUser(req)); }
@PutMapping("/user/{id}/status") public R<String> updateUserStatus(@PathVariable Long id, @RequestParam int status) { svc.updateUserStatus(id, status); return R.ok("OK"); }
@GetMapping("/menu/list") public R<List<SysMenu>> menus() { return R.ok(svc.listMenus()); }
@GetMapping("/dept/list") public R<List<SysDepartment>> depts() { return R.ok(svc.listDepts()); }
@PostMapping("/dept") public R<Long> createDept(@RequestBody Map<String,Object> req) { return R.ok(svc.createDept(req)); }
@GetMapping("/log/list") public R<List<SysLog>> logs(@RequestParam(required=false) String logType, @RequestParam(required=false) String username) { return R.ok(svc.listLogs(logType, username)); }
}
@@ -0,0 +1,57 @@
package com.water.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 文档实体
*/
@Data
@TableName("doc_document")
public class Document {
@TableId(type = IdType.AUTO)
private Long id;
/** 文档名称 */
private String name;
/** 文档原始文件名 */
private String originalName;
/** 文档大小(字节) */
private Long fileSize;
/** 文件类型 */
private String fileType;
/** 存储路径 */
private String storagePath;
/** 文档分类ID */
private Long categoryId;
/** 文档描述 */
private String description;
/** 当前版本号 */
private Integer currentVersion;
/** 上传者ID */
private Long uploaderId;
/** 下载次数 */
private Integer downloadCount;
/** 状态: 0-草稿 1-已发布 2-已归档 */
private Integer status;
/** 权限级别: 0-公开 1-内部 2-机密 */
private Integer permissionLevel;
@TableLogic
private Integer deleted;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
@@ -0,0 +1,33 @@
package com.water.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 文档分类实体
*/
@Data
@TableName("doc_category")
public class DocumentCategory {
@TableId(type = IdType.AUTO)
private Long id;
/** 分类名称 */
private String name;
/** 父分类ID */
private Long parentId;
/** 排序号 */
private Integer sortOrder;
/** 分类描述 */
private String description;
@TableLogic
private Integer deleted;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
@@ -0,0 +1,35 @@
package com.water.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 文档版本实体
*/
@Data
@TableName("doc_version")
public class DocumentVersion {
@TableId(type = IdType.AUTO)
private Long id;
/** 文档ID */
private Long documentId;
/** 版本号 */
private Integer version;
/** 版本描述 */
private String description;
/** 文件存储路径 */
private String storagePath;
/** 文件大小 */
private Long fileSize;
/** 操作人ID */
private Long operatorId;
private LocalDateTime createdAt;
}
@@ -0,0 +1,9 @@
package com.water.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
@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;
}
@@ -0,0 +1,11 @@
package com.water.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data; import java.time.LocalDateTime;
@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;
}
@@ -0,0 +1,9 @@
package com.water.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
@Data @TableName("sys_menu")
public class SysMenu {
@TableId(type = IdType.AUTO) private Long id;
private Long parentId; private String name, path, icon, component, permission;
private Integer type, sort, visible, status;
}
@@ -0,0 +1,10 @@
package com.water.system.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data; import java.time.LocalDateTime;
@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;
}
@@ -0,0 +1,12 @@
package com.water.system.entity;
import com.baomidou.mybatisplus.annotation.*;
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;
}
@@ -0,0 +1,5 @@
package com.water.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.system.entity.DocumentCategory;
import org.apache.ibatis.annotations.Mapper;
@Mapper public interface DocumentCategoryMapper extends BaseMapper<DocumentCategory> {}
@@ -0,0 +1,5 @@
package com.water.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.system.entity.Document;
import org.apache.ibatis.annotations.Mapper;
@Mapper public interface DocumentMapper extends BaseMapper<Document> {}
@@ -0,0 +1,5 @@
package com.water.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.system.entity.DocumentVersion;
import org.apache.ibatis.annotations.Mapper;
@Mapper public interface DocumentVersionMapper extends BaseMapper<DocumentVersion> {}
@@ -0,0 +1,5 @@
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<SysDepartment> {}
@@ -0,0 +1,5 @@
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<SysLog> {}
@@ -0,0 +1,5 @@
package com.water.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.system.entity.SysMenu;
import org.apache.ibatis.annotations.Mapper;
@Mapper public interface SysMenuMapper extends BaseMapper<SysMenu> {}
@@ -0,0 +1,5 @@
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<SysRole> {}
@@ -0,0 +1,5 @@
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<SysUser> {}
@@ -0,0 +1,44 @@
package com.water.system.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.water.system.entity.*;
import com.water.system.mapper.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.*;
@Service @RequiredArgsConstructor
public class DocService {
private final DocumentMapper docMapper;
private final DocumentCategoryMapper catMapper;
private final DocumentVersionMapper verMapper;
public List<Document> listDocs(String categoryId, String keyword) {
LambdaQueryWrapper<Document> w = new LambdaQueryWrapper<>();
if (categoryId != null) w.eq(Document::getCategoryId, categoryId);
if (keyword != null) w.like(Document::getName, keyword);
return docMapper.selectList(w);
}
public Document getDoc(Long id) { return docMapper.selectById(id); }
public Long createDoc(Map<String,Object> req) {
Document d = new Document();
d.setName((String)req.get("name"));
d.setCategoryId(req.get("categoryId") != null ? ((Number)req.get("categoryId")).longValue() : null);
d.setFileType((String)req.get("fileType"));
d.setFilePath((String)req.get("filePath"));
d.setStatus(1);
docMapper.insert(d);
return d.getId();
}
public void updateDoc(Long id, Map<String,Object> req) {
Document d = docMapper.selectById(id);
if (d == null) throw new RuntimeException("文档不存在");
if (req.containsKey("name")) d.setName((String)req.get("name"));
if (req.containsKey("categoryId")) d.setCategoryId(((Number)req.get("categoryId")).longValue());
docMapper.updateById(d);
}
public void deleteDoc(Long id) { docMapper.deleteById(id); }
public List<DocumentCategory> listCategories() { return catMapper.selectList(null); }
public List<DocumentVersion> getVersions(Long docId) {
return verMapper.selectList(new LambdaQueryWrapper<DocumentVersion>()
.eq(DocumentVersion::getDocId, docId).orderByDesc(DocumentVersion::getVersionNo));
}
}
@@ -0,0 +1,67 @@
package com.water.system.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.water.system.entity.*;
import com.water.system.mapper.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.*;
@Service @RequiredArgsConstructor
public class SysService {
private final SysRoleMapper roleMapper;
private final SysUserMapper userMapper;
private final SysMenuMapper menuMapper;
private final SysDepartmentMapper deptMapper;
private final SysLogMapper logMapper;
// Roles
public List<SysRole> listRoles() { return roleMapper.selectList(null); }
public Long createRole(Map<String,Object> 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();
}
// Users
public List<SysUser> listUsers(Integer status) {
return userMapper.selectList(new LambdaQueryWrapper<SysUser>()
.eq(status != null, SysUser::getStatus, status));
}
public Long createUser(Map<String,Object> 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();
}
public void updateUserStatus(Long id, int status) {
SysUser u = userMapper.selectById(id);
if (u == null) throw new RuntimeException("用户不存在");
u.setStatus(status);
userMapper.updateById(u);
}
// Menus
public List<SysMenu> listMenus() { return menuMapper.selectList(null); }
// Departments
public List<SysDepartment> listDepts() { return deptMapper.selectList(null); }
public Long createDept(Map<String,Object> 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();
}
// Logs
public List<SysLog> listLogs(String logType, String username) {
LambdaQueryWrapper<SysLog> 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"));
}
}
@@ -0,0 +1,40 @@
server:
port: 8084
spring:
application:
name: wm-system
datasource:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:water_system}?currentSchema=public
username: ${DB_USER:postgres}
password: ${DB_PASS:postgres}
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
minio:
endpoint: ${MINIO_ENDPOINT:http://localhost:9000}
access-key: ${MINIO_ACCESS_KEY:minioadmin}
secret-key: ${MINIO_SECRET_KEY:minioadmin}
bucket-name: ${MINIO_BUCKET:water-documents}
sa-token:
token-name: Authorization
timeout: 86400
active-timeout: 1800
logging:
level:
com.water.system: debug
@@ -0,0 +1,45 @@
CREATE TABLE IF NOT EXISTS sys_document (
id BIGSERIAL PRIMARY KEY, name VARCHAR(200), category_id BIGINT,
file_type VARCHAR(20), file_size BIGINT, file_path VARCHAR(500),
status INT DEFAULT 1, creator_id BIGINT,
created_time TIMESTAMP DEFAULT NOW(), updated_time TIMESTAMP DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS sys_document_category (
id BIGSERIAL PRIMARY KEY, name VARCHAR(100), parent_id BIGINT,
sort INT DEFAULT 0, status INT DEFAULT 1,
created_time TIMESTAMP DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS sys_document_version (
id BIGSERIAL PRIMARY KEY, doc_id BIGINT, version_no INT,
file_path VARCHAR(500), remark TEXT,
created_time TIMESTAMP DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS sys_role (
id BIGSERIAL PRIMARY KEY, role_code VARCHAR(50) UNIQUE, role_name VARCHAR(50),
description VARCHAR(200), status INT DEFAULT 1, sort INT DEFAULT 0,
created_time TIMESTAMP DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS sys_user (
id BIGSERIAL PRIMARY KEY, username VARCHAR(50) UNIQUE, password VARCHAR(200),
real_name VARCHAR(50), phone VARCHAR(20), email VARCHAR(100),
dept_id BIGINT, avatar VARCHAR(500), status INT DEFAULT 1,
created_time TIMESTAMP DEFAULT NOW(), updated_time TIMESTAMP DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS sys_menu (
id BIGSERIAL PRIMARY KEY, parent_id BIGINT, name VARCHAR(50),
path VARCHAR(200), icon VARCHAR(50), component VARCHAR(200),
permission VARCHAR(100), type INT, sort INT DEFAULT 0,
visible INT DEFAULT 1, status INT DEFAULT 1
);
CREATE TABLE IF NOT EXISTS sys_department (
id BIGSERIAL PRIMARY KEY, parent_id BIGINT, dept_name VARCHAR(100),
leader VARCHAR(50), phone VARCHAR(20), sort INT DEFAULT 0, status INT DEFAULT 1
);
CREATE TABLE IF NOT EXISTS sys_log (
id BIGSERIAL PRIMARY KEY, user_id BIGINT, username VARCHAR(50),
log_type VARCHAR(20), module VARCHAR(50), action VARCHAR(50),
request_url VARCHAR(500), request_method VARCHAR(10),
request_params TEXT, response_result TEXT,
ip VARCHAR(50), duration BIGINT,
created_time TIMESTAMP DEFAULT NOW()
);