Initial commit
This commit is contained in:
commit
c17f4c38e9
3
.idea/.gitignore
vendored
Normal file
3
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
9
.idea/StdProject.iml
Normal file
9
.idea/StdProject.iml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
19
.idea/compiler.xml
Normal file
19
.idea/compiler.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<annotationProcessing>
|
||||
<profile default="true" name="Default" enabled="true" />
|
||||
<profile name="Maven default annotation processors profile" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<module name="stdproject-backend" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
<component name="JavacSettings">
|
||||
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||
<module name="stdproject-backend" options="-parameters" />
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
6
.idea/encodings.xml
Normal file
6
.idea/encodings.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/backend/src/main/java" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
20
.idea/jarRepositories.xml
Normal file
20
.idea/jarRepositories.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="http://maven.aliyun.com/nexus/content/groups/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
14
.idea/misc.xml
Normal file
14
.idea/misc.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/backend/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="corretto-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/StdProject.iml" filepath="$PROJECT_DIR$/.idea/StdProject.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
242
backend/db/db_mysql.sql
Normal file
242
backend/db/db_mysql.sql
Normal file
@ -0,0 +1,242 @@
|
||||
/*
|
||||
Navicat Premium Dump SQL
|
||||
|
||||
Source Server : 华为云-mysql数据库
|
||||
Source Server Type : MySQL
|
||||
Source Server Version : 80403 (8.4.3)
|
||||
Source Host : 121.37.111.42:3306
|
||||
Source Schema : gisbi-demodb
|
||||
|
||||
Target Server Type : MySQL
|
||||
Target Server Version : 80403 (8.4.3)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 30/05/2025 08:58:25
|
||||
*/
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_application
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `app_application`;
|
||||
CREATE TABLE `app_application` (
|
||||
`id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'ID',
|
||||
`type` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '01' COMMENT '应用类型 01-数据展示分析 ',
|
||||
`code` varchar(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用编号 自动生成:000001',
|
||||
`name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '应用名称',
|
||||
`simplename` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用简称',
|
||||
`createdate` datetime NULL DEFAULT NULL COMMENT '创建日期',
|
||||
`description` varchar(600) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用描述',
|
||||
`image` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '应用图片 base64存储',
|
||||
`appconfig` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '应用配置 JSON格式存储其他配置相关信息',
|
||||
`dbconfig` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '数据库配置 JSON格式存储数据库信息配置(数据库用于存储此应用系统的所有配置数据)',
|
||||
`organization` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户单位 使用这个应用系统的组织单位',
|
||||
`status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '状态 01-初始创建 02-正常运行 09-已停用',
|
||||
`lastmodifier` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '最近修改者',
|
||||
`lastmodifydate` datetime NULL DEFAULT NULL COMMENT '最近修改日期',
|
||||
`custom1` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用1',
|
||||
`custom2` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用2',
|
||||
`custom3` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用3',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '应用系统' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_dictionary
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `app_dictionary`;
|
||||
CREATE TABLE `app_dictionary` (
|
||||
`id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'id',
|
||||
`app_id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用ID 关联应用系统',
|
||||
`orderno` int NOT NULL COMMENT '顺序号',
|
||||
`dictcode` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典编码',
|
||||
`dictname` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典名称',
|
||||
`dictdata` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '字典数据 JSON数组[{\"code\":\"01\",\"name\":\"字典值\"}]',
|
||||
`lastmodifydate` datetime NULL DEFAULT NULL COMMENT '最近修改日期',
|
||||
`custom1` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用1',
|
||||
`custom2` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用2',
|
||||
`custom3` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用3',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '应用-数据字典' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_menu
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `app_menu`;
|
||||
CREATE TABLE `app_menu` (
|
||||
`id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'ID',
|
||||
`app_id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用ID 关联应用系统',
|
||||
`type` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单类型 01-菜单',
|
||||
`code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单编号',
|
||||
`orderno` int NULL DEFAULT NULL COMMENT '同级序号',
|
||||
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称',
|
||||
`icon` varchar(9000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '菜单图标 base64存储',
|
||||
`islink` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否外链 0-非外部 1-是外链',
|
||||
`url` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '菜单URL 内部资源页面URL访问地址',
|
||||
`module_id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模块ID 菜单关联的模块ID',
|
||||
`parentid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '父级菜单ID 顶级为0',
|
||||
`isdisplay` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否显示 0-不显示 1-显示',
|
||||
`lastmodifier` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '最近修改者',
|
||||
`lastmodifydate` datetime NULL DEFAULT NULL COMMENT '最近修改日期',
|
||||
`custom1` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用1',
|
||||
`custom2` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用2',
|
||||
`custom3` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用3',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '应用系统_菜单' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_module
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `app_module`;
|
||||
CREATE TABLE `app_module` (
|
||||
`id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键',
|
||||
`app_id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用ID 关联应用系统',
|
||||
`name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称',
|
||||
`pid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '父级ID 父级模块',
|
||||
`level` int NULL DEFAULT NULL COMMENT '层级',
|
||||
`node_type` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '节点类型 01-目录 02-模块',
|
||||
`type` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '类型 01-登录页面 02-主框架页面 03-GIS大屏页面 04-二级弹窗页面 05-数据填报页面 06-文档资源页面09-自定义页面',
|
||||
`canvas_style_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '样式数据',
|
||||
`component_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '组件数据',
|
||||
`status` int NULL DEFAULT 1 COMMENT '状态 0-未发布 1-已发布',
|
||||
`self_watermark_status` int NULL DEFAULT 0 COMMENT '是否单独打开水印 0-关闭 1-开启',
|
||||
`sort` int NULL DEFAULT 0 COMMENT '排序 目录内的排序号',
|
||||
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`create_by` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人',
|
||||
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
|
||||
`update_by` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人',
|
||||
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
|
||||
`source` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '数据来源',
|
||||
`delete_flag` tinyint NULL DEFAULT 0 COMMENT '删除标志',
|
||||
`delete_time` datetime NULL DEFAULT NULL COMMENT '删除时间',
|
||||
`delete_by` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '删除人',
|
||||
`version` int NULL DEFAULT 0 COMMENT '可视化资源版本',
|
||||
`content_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '内容标识',
|
||||
`check_version` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '内容检查标识',
|
||||
`custom1` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用1',
|
||||
`custom2` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用2',
|
||||
`custom3` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用3',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '应用_系统模块' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_optlog
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `app_optlog`;
|
||||
CREATE TABLE `app_optlog` (
|
||||
`id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'id',
|
||||
`usercode` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '用户账号',
|
||||
`username` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '用户名称',
|
||||
`opttype` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '操作类型 00-登录 01-新增 02-修改 03-删除 06-查询 09其他',
|
||||
`module` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '模块名称',
|
||||
`description` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '日志描述',
|
||||
`method` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '操作方法',
|
||||
`params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL COMMENT '方法参数',
|
||||
`logtime` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`requestip` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '请求IP',
|
||||
`browser` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '浏览器类型',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `log_create_time_index`(`logtime` ASC) USING BTREE,
|
||||
INDEX `inx_log_type`(`opttype` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '系统操作日志' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_organization
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `app_organization`;
|
||||
CREATE TABLE `app_organization` (
|
||||
`id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'ID',
|
||||
`app_id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用ID 关联应用系统',
|
||||
`orgtype` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组织类型:01-公司 02-部门',
|
||||
`orgcode` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组织编号',
|
||||
`orgname` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组织名称',
|
||||
`parentid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '上级ID',
|
||||
`manager` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组织负责人',
|
||||
`description` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组织详情',
|
||||
`address` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系地址',
|
||||
`contact_phone` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话',
|
||||
`contact_person` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系人',
|
||||
`isvaild` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '1' COMMENT '是否有效 1-是 0-否',
|
||||
`lastmodifier` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '最近修改者',
|
||||
`lastmodifydate` datetime NULL DEFAULT NULL COMMENT '最近修改日期',
|
||||
`custom1` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用1',
|
||||
`custom2` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用2',
|
||||
`custom3` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用3',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '应用系统_用户组织' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_role
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `app_role`;
|
||||
CREATE TABLE `app_role` (
|
||||
`id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'ID',
|
||||
`app_id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用ID 关联应用系统',
|
||||
`rolecode` varchar(3) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色编号 系统生成,三位编号',
|
||||
`rolename` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称',
|
||||
`type` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色类别 1-应用管理员 2-应用普通用户',
|
||||
`description` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色描述',
|
||||
`isvaild` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '1' COMMENT '是否有效 1-是 0-否',
|
||||
`lastmodifier` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '最近修改者',
|
||||
`lastmodifydate` datetime NOT NULL COMMENT '最近修改日期',
|
||||
`custom1` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用1',
|
||||
`custom2` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用2',
|
||||
`custom3` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用3',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '应用系统_系统角色' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_role_menu
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `app_role_menu`;
|
||||
CREATE TABLE `app_role_menu` (
|
||||
`id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'id',
|
||||
`app_id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用ID 关联应用系统',
|
||||
`roleid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色id',
|
||||
`menuid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单id',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统角色-数据权限对照' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_role_users
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `app_role_users`;
|
||||
CREATE TABLE `app_role_users` (
|
||||
`id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'id',
|
||||
`app_id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用ID 关联应用系统',
|
||||
`roleid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色id',
|
||||
`userid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户id',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统角色-用户对照' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_user
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `app_user`;
|
||||
CREATE TABLE `app_user` (
|
||||
`id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'ID',
|
||||
`app_id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用ID 关联应用系统',
|
||||
`orgid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '所属组织',
|
||||
`usertype` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户类型 0-管理员 1-普通用户',
|
||||
`username` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名称 用户名称(账号)',
|
||||
`nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户昵称(中文)',
|
||||
`password` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '登录密码',
|
||||
`email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱',
|
||||
`phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号',
|
||||
`avatar` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '用户头像 base64存储用户头像',
|
||||
`status` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '状态 1-有效 0-停用',
|
||||
`pwdvalidperiod` int NULL DEFAULT 90 COMMENT '密码有限期 密码有限期(天)',
|
||||
`failednum` int NULL DEFAULT 0 COMMENT '登录失败次数 允许的登录失败次数',
|
||||
`loginip` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户指定登录IP 如果设置了IP,则只允许IP用户登录',
|
||||
`failedlocktime` datetime NULL DEFAULT NULL COMMENT '登录失败锁定时间',
|
||||
`pwdresettime` datetime NULL DEFAULT NULL COMMENT '密码修改时间',
|
||||
`lastmodifier` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '最近修改者',
|
||||
`lastmodifydate` datetime NULL DEFAULT NULL COMMENT '最近修改日期',
|
||||
`custom1` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用1',
|
||||
`custom2` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用2',
|
||||
`custom3` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用3',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '应用系统_系统用户' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
243
backend/db/db_sqllite.sql
Normal file
243
backend/db/db_sqllite.sql
Normal file
@ -0,0 +1,243 @@
|
||||
/*
|
||||
Navicat SQLite Data Transfer
|
||||
|
||||
Source Server : project
|
||||
Source Server Version : 30808
|
||||
Source Host : :0
|
||||
|
||||
Target Server Type : SQLite
|
||||
Target Server Version : 30808
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 2025-05-30 09:13:56
|
||||
*/
|
||||
|
||||
PRAGMA foreign_keys = OFF;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_application
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS "main"."app_application";
|
||||
CREATE TABLE "app_application" (
|
||||
"id" TEXT(40) NOT NULL,
|
||||
"type" TEXT(2),
|
||||
"code" TEXT(6),
|
||||
"name" TEXT(200) NOT NULL,
|
||||
"simplename" TEXT(100),
|
||||
"createdate" TEXT,
|
||||
"description" TEXT(600),
|
||||
"image" TEXT,
|
||||
"appconfig" TEXT,
|
||||
"dbconfig" TEXT,
|
||||
"organization" TEXT(200),
|
||||
"status" TEXT(20),
|
||||
"lastmodifier" TEXT(40),
|
||||
"lastmodifydate" TEXT,
|
||||
"custom1" TEXT(100),
|
||||
"custom2" TEXT(100),
|
||||
"custom3" TEXT(100),
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_dictionary
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS "main"."app_dictionary";
|
||||
CREATE TABLE "app_dictionary" (
|
||||
"id" TEXT(40) NOT NULL,
|
||||
"app_id" TEXT(40),
|
||||
"orderno" INTEGER NOT NULL,
|
||||
"dictcode" TEXT(40) NOT NULL,
|
||||
"dictname" TEXT(100) NOT NULL,
|
||||
"dictdata" TEXT,
|
||||
"lastmodifydate" TEXT,
|
||||
"custom1" TEXT(1000),
|
||||
"custom2" TEXT(100),
|
||||
"custom3" TEXT(100),
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_menu
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS "main"."app_menu";
|
||||
CREATE TABLE "app_menu" (
|
||||
"id" TEXT(40) NOT NULL,
|
||||
"app_id" TEXT(40),
|
||||
"type" TEXT(2) NOT NULL,
|
||||
"code" TEXT(20) NOT NULL,
|
||||
"orderno" INTEGER,
|
||||
"name" TEXT(100) NOT NULL,
|
||||
"icon" TEXT(9000),
|
||||
"islink" TEXT(2),
|
||||
"url" TEXT(200),
|
||||
"module_id" TEXT(40),
|
||||
"parentid" TEXT(40),
|
||||
"isdisplay" TEXT(2),
|
||||
"lastmodifier" TEXT(40),
|
||||
"lastmodifydate" TEXT,
|
||||
"custom1" TEXT(100),
|
||||
"custom2" TEXT(100),
|
||||
"custom3" TEXT(100),
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_module
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS "main"."app_module";
|
||||
CREATE TABLE "app_module" (
|
||||
"id" TEXT(40) NOT NULL,
|
||||
"app_id" TEXT(40),
|
||||
"name" TEXT(200),
|
||||
"pid" TEXT(40),
|
||||
"level" INTEGER,
|
||||
"node_type" TEXT(40),
|
||||
"type" TEXT(40),
|
||||
"canvas_style_data" TEXT,
|
||||
"component_data" TEXT,
|
||||
"status" INTEGER,
|
||||
"self_watermark_status" INTEGER,
|
||||
"sort" INTEGER,
|
||||
"create_time" TEXT,
|
||||
"create_by" TEXT(40),
|
||||
"update_time" TEXT,
|
||||
"update_by" TEXT(40),
|
||||
"remark" TEXT(255),
|
||||
"source" TEXT(255),
|
||||
"delete_flag" INTEGER,
|
||||
"delete_time" TEXT,
|
||||
"delete_by" TEXT(40),
|
||||
"version" INTEGER,
|
||||
"content_id" TEXT(50),
|
||||
"check_version" TEXT(50),
|
||||
"custom1" TEXT(100),
|
||||
"custom2" TEXT(100),
|
||||
"custom3" TEXT(100),
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_optlog
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS "main"."app_optlog";
|
||||
CREATE TABLE "app_optlog" (
|
||||
"id" TEXT(40) NOT NULL,
|
||||
"usercode" TEXT(200),
|
||||
"username" TEXT(200),
|
||||
"opttype" TEXT(20),
|
||||
"module" TEXT(200),
|
||||
"description" TEXT(400),
|
||||
"method" TEXT(200),
|
||||
"params" TEXT,
|
||||
"logtime" TEXT,
|
||||
"requestip" TEXT(200),
|
||||
"browser" TEXT(200),
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_organization
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS "main"."app_organization";
|
||||
CREATE TABLE "app_organization" (
|
||||
"id" TEXT(40) NOT NULL,
|
||||
"app_id" TEXT(40),
|
||||
"orgtype" TEXT(20),
|
||||
"orgcode" TEXT(20),
|
||||
"orgname" TEXT(200),
|
||||
"parentid" TEXT(40),
|
||||
"manager" TEXT(40),
|
||||
"description" TEXT(1000),
|
||||
"address" TEXT(100),
|
||||
"contact_phone" TEXT(100),
|
||||
"contact_person" TEXT(100),
|
||||
"isvaild" TEXT(2) NOT NULL,
|
||||
"lastmodifier" TEXT(40),
|
||||
"lastmodifydate" TEXT,
|
||||
"custom1" TEXT(100),
|
||||
"custom2" TEXT(100),
|
||||
"custom3" TEXT(100),
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_role
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS "main"."app_role";
|
||||
CREATE TABLE "app_role" (
|
||||
"id" TEXT(40) NOT NULL,
|
||||
"app_id" TEXT(40),
|
||||
"rolecode" TEXT(3) NOT NULL,
|
||||
"rolename" TEXT(100) NOT NULL,
|
||||
"type" TEXT(2) NOT NULL,
|
||||
"description" TEXT(1000),
|
||||
"isvaild" TEXT(2) NOT NULL,
|
||||
"lastmodifier" TEXT(40) NOT NULL,
|
||||
"lastmodifydate" TEXT NOT NULL,
|
||||
"custom1" TEXT(100),
|
||||
"custom2" TEXT(100),
|
||||
"custom3" TEXT(100),
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_role_menu
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS "main"."app_role_menu";
|
||||
CREATE TABLE "app_role_menu" (
|
||||
"id" TEXT(40) NOT NULL,
|
||||
"app_id" TEXT(40),
|
||||
"roleid" TEXT(40) NOT NULL,
|
||||
"menuid" TEXT(40) NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_role_users
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS "main"."app_role_users";
|
||||
CREATE TABLE "app_role_users" (
|
||||
"id" TEXT(40) NOT NULL,
|
||||
"app_id" TEXT(40),
|
||||
"roleid" TEXT(40) NOT NULL,
|
||||
"userid" TEXT(40) NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for app_user
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS "main"."app_user";
|
||||
CREATE TABLE "app_user" (
|
||||
"id" TEXT(40) NOT NULL,
|
||||
"app_id" TEXT(40),
|
||||
"orgid" TEXT(40) NOT NULL,
|
||||
"usertype" TEXT(20) NOT NULL,
|
||||
"username" TEXT(100) NOT NULL,
|
||||
"nickname" TEXT(255) NOT NULL,
|
||||
"password" TEXT(200),
|
||||
"email" TEXT(100),
|
||||
"phone" TEXT(20),
|
||||
"avatar" TEXT,
|
||||
"status" TEXT(2),
|
||||
"pwdvalidperiod" INTEGER,
|
||||
"failednum" INTEGER,
|
||||
"loginip" TEXT(40),
|
||||
"failedlocktime" TEXT,
|
||||
"pwdresettime" TEXT,
|
||||
"lastmodifier" TEXT(40),
|
||||
"lastmodifydate" TEXT,
|
||||
"custom1" TEXT(100),
|
||||
"custom2" TEXT(100),
|
||||
"custom3" TEXT(100),
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- ----------------------------
|
||||
-- Indexes structure for table app_optlog
|
||||
-- ----------------------------
|
||||
CREATE INDEX "main"."inx_log_type"
|
||||
ON "app_optlog" ("opttype" ASC);
|
||||
CREATE INDEX "main"."log_create_time_index"
|
||||
ON "app_optlog" ("logtime" ASC);
|
BIN
backend/db/project.db
Normal file
BIN
backend/db/project.db
Normal file
Binary file not shown.
141
backend/pom.xml
Normal file
141
backend/pom.xml
Normal file
@ -0,0 +1,141 @@
|
||||
<?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>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.stdproject</groupId>
|
||||
<artifactId>stdproject-backend</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>stdproject-backend</name>
|
||||
<description>Standard Project Backend</description>
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<mybatis-plus.version>3.5.3</mybatis-plus.version>
|
||||
<jjwt.version>0.11.5</jjwt.version>
|
||||
<springdoc.version>2.0.2</springdoc.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</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-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.cache</groupId>
|
||||
<artifactId>cache-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.30</version> <!-- Ensure this version is compatible with MySQL 8.0 -->
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.36.0.3</version>
|
||||
</dependency>
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Swagger (OpenAPI 3) -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>${springdoc.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-core</artifactId>
|
||||
<version>3.5.3.1</version> <!-- 请使用你项目中统一的版本 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.34</version> <!-- 推荐使用最新稳定版本 --> <scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
32
backend/src/main/java/com/stdproject/ProjectApplication.java
Normal file
32
backend/src/main/java/com/stdproject/ProjectApplication.java
Normal file
@ -0,0 +1,32 @@
|
||||
package com.stdproject;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/**
|
||||
* Spring Boot 应用启动类
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@MapperScan("com.stdproject.mapper")
|
||||
@EnableCaching
|
||||
@EnableAsync
|
||||
@EnableTransactionManagement
|
||||
public class ProjectApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ProjectApplication.class, args);
|
||||
System.out.println("\n========================================");
|
||||
System.out.println(" StdProject 应用启动成功!");
|
||||
System.out.println(" 接口文档地址: http://localhost:8080/swagger-ui.html");
|
||||
System.out.println(" 应用端口: 8080");
|
||||
System.out.println("========================================\n");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
package com.stdproject.common;
|
||||
|
||||
/**
|
||||
* 业务异常类
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
private Integer code;
|
||||
|
||||
/**
|
||||
* 错误消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
public BusinessException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public BusinessException(String message) {
|
||||
super(message);
|
||||
this.message = message;
|
||||
this.code = ResultCode.ERROR.getCode();
|
||||
}
|
||||
|
||||
public BusinessException(Integer code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public BusinessException(ResultCode resultCode) {
|
||||
super(resultCode.getMessage());
|
||||
this.code = resultCode.getCode();
|
||||
this.message = resultCode.getMessage();
|
||||
}
|
||||
|
||||
public BusinessException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.message = message;
|
||||
this.code = ResultCode.ERROR.getCode();
|
||||
}
|
||||
|
||||
public BusinessException(Integer code, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public BusinessException(ResultCode resultCode, Throwable cause) {
|
||||
super(resultCode.getMessage(), cause);
|
||||
this.code = resultCode.getCode();
|
||||
this.message = resultCode.getMessage();
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(Integer code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 抛出业务异常
|
||||
*
|
||||
* @param message 错误消息
|
||||
*/
|
||||
public static void throwException(String message) {
|
||||
throw new BusinessException(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 抛出业务异常
|
||||
*
|
||||
* @param code 错误码
|
||||
* @param message 错误消息
|
||||
*/
|
||||
public static void throwException(Integer code, String message) {
|
||||
throw new BusinessException(code, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 抛出业务异常
|
||||
*
|
||||
* @param resultCode 结果码枚举
|
||||
*/
|
||||
public static void throwException(ResultCode resultCode) {
|
||||
throw new BusinessException(resultCode);
|
||||
}
|
||||
}
|
121
backend/src/main/java/com/stdproject/common/Constants.java
Normal file
121
backend/src/main/java/com/stdproject/common/Constants.java
Normal file
@ -0,0 +1,121 @@
|
||||
package com.stdproject.common;
|
||||
|
||||
/**
|
||||
* 系统常量类
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
public class Constants {
|
||||
|
||||
/**
|
||||
* JWT相关常量
|
||||
*/
|
||||
public static final String JWT_HEADER = "Authorization";
|
||||
public static final String JWT_PREFIX = "Bearer ";
|
||||
public static final String JWT_SECRET_KEY = "jwt.secret";
|
||||
public static final String JWT_EXPIRATION_KEY = "jwt.expiration";
|
||||
|
||||
/**
|
||||
* 用户状态
|
||||
*/
|
||||
public static final String USER_STATUS_NORMAL = "0"; // 正常
|
||||
public static final String USER_STATUS_DISABLED = "1"; // 停用
|
||||
public static final String USER_STATUS_LOCKED = "2"; // 锁定
|
||||
|
||||
/**
|
||||
* 角色状态
|
||||
*/
|
||||
public static final String ROLE_STATUS_NORMAL = "0"; // 正常
|
||||
public static final String ROLE_STATUS_DISABLED = "1"; // 停用
|
||||
|
||||
/**
|
||||
* 菜单类型
|
||||
*/
|
||||
public static final String MENU_TYPE_DIRECTORY = "M"; // 目录
|
||||
public static final String MENU_TYPE_MENU = "C"; // 菜单
|
||||
public static final String MENU_TYPE_BUTTON = "F"; // 按钮
|
||||
|
||||
/**
|
||||
* 菜单状态
|
||||
*/
|
||||
public static final String MENU_STATUS_NORMAL = "0"; // 正常
|
||||
public static final String MENU_STATUS_DISABLED = "1"; // 停用
|
||||
|
||||
/**
|
||||
* 组织状态
|
||||
*/
|
||||
public static final String ORG_STATUS_NORMAL = "0"; // 正常
|
||||
public static final String ORG_STATUS_DISABLED = "1"; // 停用
|
||||
|
||||
/**
|
||||
* 操作类型
|
||||
*/
|
||||
public static final String OPT_TYPE_LOGIN = "00"; // 登录
|
||||
public static final String OPT_TYPE_INSERT = "01"; // 新增
|
||||
public static final String OPT_TYPE_UPDATE = "02"; // 修改
|
||||
public static final String OPT_TYPE_DELETE = "03"; // 删除
|
||||
public static final String OPT_TYPE_QUERY = "06"; // 查询
|
||||
public static final String OPT_TYPE_OTHER = "09"; // 其他
|
||||
|
||||
/**
|
||||
* 操作状态
|
||||
*/
|
||||
public static final String OPT_STATUS_SUCCESS = "0"; // 成功
|
||||
public static final String OPT_STATUS_FAIL = "1"; // 失败
|
||||
|
||||
/**
|
||||
* 数据权限类型
|
||||
*/
|
||||
public static final String DATA_SCOPE_ALL = "1"; // 全部数据权限
|
||||
public static final String DATA_SCOPE_CUSTOM = "2"; // 自定义数据权限
|
||||
public static final String DATA_SCOPE_DEPT = "3"; // 部门数据权限
|
||||
public static final String DATA_SCOPE_DEPT_AND_CHILD = "4"; // 部门及以下数据权限
|
||||
public static final String DATA_SCOPE_SELF = "5"; // 仅本人数据权限
|
||||
|
||||
/**
|
||||
* 字典状态
|
||||
*/
|
||||
public static final String DICT_STATUS_NORMAL = "0"; // 正常
|
||||
public static final String DICT_STATUS_DISABLED = "1"; // 停用
|
||||
|
||||
/**
|
||||
* 是否删除
|
||||
*/
|
||||
public static final String DELETED_YES = "1"; // 已删除
|
||||
public static final String DELETED_NO = "0"; // 未删除
|
||||
|
||||
/**
|
||||
* 性别
|
||||
*/
|
||||
public static final String GENDER_MALE = "0"; // 男
|
||||
public static final String GENDER_FEMALE = "1"; // 女
|
||||
public static final String GENDER_UNKNOWN = "2"; // 未知
|
||||
|
||||
/**
|
||||
* 默认密码
|
||||
*/
|
||||
public static final String DEFAULT_PASSWORD = "123456";
|
||||
|
||||
/**
|
||||
* 超级管理员角色编码
|
||||
*/
|
||||
public static final String SUPER_ADMIN_ROLE = "admin";
|
||||
|
||||
/**
|
||||
* 根节点ID
|
||||
*/
|
||||
public static final Long ROOT_NODE_ID = 0L;
|
||||
|
||||
/**
|
||||
* 缓存过期时间(秒)
|
||||
*/
|
||||
public static final int CACHE_EXPIRE_TIME = 3600; // 1小时
|
||||
public static final int CACHE_EXPIRE_TIME_SHORT = 300; // 5分钟
|
||||
public static final int CACHE_EXPIRE_TIME_LONG = 86400; // 24小时
|
||||
|
||||
/**
|
||||
* 分页默认值
|
||||
*/
|
||||
public static final int DEFAULT_PAGE_SIZE = 20;
|
||||
public static final int MAX_PAGE_SIZE = 500;
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.stdproject.common;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 数据权限注解
|
||||
* 用于标记需要进行数据权限控制的方法
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface DataPermission {
|
||||
|
||||
/**
|
||||
* 数据权限类型
|
||||
* 1-全部数据权限 2-自定义数据权限 3-部门数据权限 4-部门及以下数据权限 5-仅本人数据权限
|
||||
*/
|
||||
String value() default "1";
|
||||
|
||||
/**
|
||||
* 表别名
|
||||
*/
|
||||
String tableAlias() default "";
|
||||
|
||||
/**
|
||||
* 用户ID字段名
|
||||
*/
|
||||
String userIdColumn() default "create_user";
|
||||
|
||||
/**
|
||||
* 部门ID字段名
|
||||
*/
|
||||
String deptIdColumn() default "dept_id";
|
||||
}
|
@ -0,0 +1,188 @@
|
||||
package com.stdproject.common;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
||||
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
/**
|
||||
* 处理业务异常
|
||||
*/
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
public Result<Void> handleBusinessException(BusinessException e, HttpServletRequest request) {
|
||||
log.warn("业务异常: {} - {}", request.getRequestURI(), e.getMessage());
|
||||
return Result.error(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数校验异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
|
||||
log.warn("参数校验异常: {} - {}", request.getRequestURI(), e.getMessage());
|
||||
StringBuilder message = new StringBuilder();
|
||||
for (FieldError error : e.getBindingResult().getFieldErrors()) {
|
||||
message.append(error.getField()).append(": ").append(error.getDefaultMessage()).append("; ");
|
||||
}
|
||||
return Result.badRequest(message.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数绑定异常
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<Void> handleBindException(BindException e, HttpServletRequest request) {
|
||||
log.warn("参数绑定异常: {} - {}", request.getRequestURI(), e.getMessage());
|
||||
StringBuilder message = new StringBuilder();
|
||||
for (FieldError error : e.getBindingResult().getFieldErrors()) {
|
||||
message.append(error.getField()).append(": ").append(error.getDefaultMessage()).append("; ");
|
||||
}
|
||||
return Result.badRequest(message.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理约束违反异常
|
||||
*/
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<Void> handleConstraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
|
||||
log.warn("约束违反异常: {} - {}", request.getRequestURI(), e.getMessage());
|
||||
StringBuilder message = new StringBuilder();
|
||||
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
|
||||
for (ConstraintViolation<?> violation : violations) {
|
||||
message.append(violation.getPropertyPath()).append(": ").append(violation.getMessage()).append("; ");
|
||||
}
|
||||
return Result.badRequest(message.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数类型不匹配异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<Void> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) {
|
||||
log.warn("参数类型不匹配异常: {} - {}", request.getRequestURI(), e.getMessage());
|
||||
String message = String.format("参数 '%s' 的值 '%s' 类型不正确", e.getName(), e.getValue());
|
||||
return Result.badRequest(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理认证异常
|
||||
*/
|
||||
@ExceptionHandler(AuthenticationException.class)
|
||||
@ResponseStatus(HttpStatus.UNAUTHORIZED)
|
||||
public Result<Void> handleAuthenticationException(AuthenticationException e, HttpServletRequest request) {
|
||||
log.warn("认证异常: {} - {}", request.getRequestURI(), e.getMessage());
|
||||
return Result.unauthorized();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理凭证错误异常
|
||||
*/
|
||||
@ExceptionHandler(BadCredentialsException.class)
|
||||
@ResponseStatus(HttpStatus.UNAUTHORIZED)
|
||||
public Result<Void> handleBadCredentialsException(BadCredentialsException e, HttpServletRequest request) {
|
||||
log.warn("凭证错误异常: {} - {}", request.getRequestURI(), e.getMessage());
|
||||
return Result.error(ResultCode.LOGIN_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理访问拒绝异常
|
||||
*/
|
||||
@ExceptionHandler(AccessDeniedException.class)
|
||||
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||
public Result<Void> handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) {
|
||||
log.warn("访问拒绝异常: {} - {}", request.getRequestURI(), e.getMessage());
|
||||
return Result.forbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理请求方法不支持异常
|
||||
*/
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
|
||||
public Result<Void> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
|
||||
log.warn("请求方法不支持异常: {} - {}", request.getRequestURI(), e.getMessage());
|
||||
return Result.error(ResultCode.METHOD_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理资源未找到异常
|
||||
*/
|
||||
@ExceptionHandler(NoHandlerFoundException.class)
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
public Result<Void> handleNoHandlerFoundException(NoHandlerFoundException e, HttpServletRequest request) {
|
||||
log.warn("资源未找到异常: {} - {}", request.getRequestURI(), e.getMessage());
|
||||
return Result.notFound();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理数据完整性违反异常
|
||||
*/
|
||||
@ExceptionHandler(DataIntegrityViolationException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<Void> handleDataIntegrityViolationException(DataIntegrityViolationException e, HttpServletRequest request) {
|
||||
log.warn("数据完整性违反异常: {} - {}", request.getRequestURI(), e.getMessage());
|
||||
return Result.error(ResultCode.DATA_INTEGRITY_VIOLATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理SQL异常
|
||||
*/
|
||||
@ExceptionHandler(SQLException.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public Result<Void> handleSQLException(SQLException e, HttpServletRequest request) {
|
||||
log.error("SQL异常: {} - {}", request.getRequestURI(), e.getMessage(), e);
|
||||
return Result.error(ResultCode.DATABASE_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理运行时异常
|
||||
*/
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public Result<Void> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
|
||||
log.error("运行时异常: {} - {}", request.getRequestURI(), e.getMessage(), e);
|
||||
return Result.error(ResultCode.ERROR.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理其他异常
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public Result<Void> handleException(Exception e, HttpServletRequest request) {
|
||||
log.error("系统异常: {} - {}", request.getRequestURI(), e.getMessage(), e);
|
||||
return Result.error(ResultCode.UNKNOWN_ERROR);
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package com.stdproject.common;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 操作日志注解
|
||||
* 用于标记需要记录操作日志的方法
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface OperationLog {
|
||||
|
||||
/**
|
||||
* 操作类型
|
||||
* 00-登录 01-新增 02-修改 03-删除 06-查询 09-其他
|
||||
*/
|
||||
String type() default "09";
|
||||
|
||||
/**
|
||||
* 模块名称
|
||||
*/
|
||||
String module() default "";
|
||||
|
||||
/**
|
||||
* 操作描述
|
||||
*/
|
||||
String description() default "";
|
||||
|
||||
/**
|
||||
* 是否记录请求参数
|
||||
*/
|
||||
boolean recordParams() default true;
|
||||
|
||||
/**
|
||||
* 是否记录返回结果
|
||||
*/
|
||||
boolean recordResult() default false;
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
package com.stdproject.common;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.stdproject.entity.AppOptLog;
|
||||
import com.stdproject.service.IAppOptLogService;
|
||||
import com.stdproject.utils.JwtUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 操作日志切面
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
public class OperationLogAspect {
|
||||
|
||||
@Autowired
|
||||
private IAppOptLogService appOptLogService;
|
||||
|
||||
@Autowired
|
||||
private JwtUtils jwtUtils;
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* 定义切点
|
||||
*/
|
||||
@Pointcut("@annotation(com.stdproject.common.OperationLog)")
|
||||
public void operationLogPointcut() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 正常返回通知
|
||||
*/
|
||||
@AfterReturning(pointcut = "operationLogPointcut()", returning = "result")
|
||||
public void doAfterReturning(JoinPoint joinPoint, Object result) {
|
||||
handleLog(joinPoint, null, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 异常通知
|
||||
*/
|
||||
@AfterThrowing(pointcut = "operationLogPointcut()", throwing = "e")
|
||||
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
|
||||
handleLog(joinPoint, e, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理日志记录
|
||||
*/
|
||||
private void handleLog(JoinPoint joinPoint, Exception e, Object result) {
|
||||
try {
|
||||
// 获取注解信息
|
||||
OperationLog operationLog = getAnnotationLog(joinPoint);
|
||||
if (operationLog == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取请求信息
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (attributes == null) {
|
||||
return;
|
||||
}
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
|
||||
// 创建操作日志对象
|
||||
AppOptLog optLog = new AppOptLog();
|
||||
|
||||
// 设置基本信息
|
||||
optLog.setOpttype(operationLog.type());
|
||||
optLog.setModule(operationLog.module());
|
||||
optLog.setDescription(operationLog.description());
|
||||
optLog.setMethod(request.getMethod() + " " + request.getRequestURI());
|
||||
optLog.setRequestip(getIpAddress(request));
|
||||
optLog.setBrowser(request.getHeader("User-Agent"));
|
||||
optLog.setLogtime(LocalDateTime.now());
|
||||
|
||||
// 设置操作用户
|
||||
String token = request.getHeader("Authorization");
|
||||
if (token != null && token.startsWith("Bearer ")) {
|
||||
token = token.substring(7);
|
||||
try {
|
||||
String username = jwtUtils.getUsernameFromToken(token);
|
||||
optLog.setUsername(username);
|
||||
} catch (Exception ex) {
|
||||
log.warn("解析JWT token失败: {}", ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 设置请求参数
|
||||
if (operationLog.recordParams()) {
|
||||
String methodParams = getMethodParams(joinPoint);
|
||||
optLog.setParams(methodParams);
|
||||
}
|
||||
|
||||
// 保存日志
|
||||
appOptLogService.save(optLog);
|
||||
|
||||
} catch (Exception ex) {
|
||||
log.error("记录操作日志异常: {}", ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取注解信息
|
||||
*/
|
||||
private OperationLog getAnnotationLog(JoinPoint joinPoint) {
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
Method method = signature.getMethod();
|
||||
return method.getAnnotation(OperationLog.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取方法参数
|
||||
*/
|
||||
private String getMethodParams(JoinPoint joinPoint) {
|
||||
try {
|
||||
Object[] args = joinPoint.getArgs();
|
||||
if (args == null || args.length == 0) {
|
||||
return "";
|
||||
}
|
||||
String params = objectMapper.writeValueAsString(Arrays.asList(args));
|
||||
if (params.length() > 2000) {
|
||||
params = params.substring(0, 2000) + "...";
|
||||
}
|
||||
return params;
|
||||
} catch (Exception e) {
|
||||
return "参数序列化失败";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端IP地址
|
||||
*/
|
||||
private String getIpAddress(HttpServletRequest request) {
|
||||
String ip = request.getHeader("X-Forwarded-For");
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("HTTP_CLIENT_IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
}
|
72
backend/src/main/java/com/stdproject/common/PageRequest.java
Normal file
72
backend/src/main/java/com/stdproject/common/PageRequest.java
Normal file
@ -0,0 +1,72 @@
|
||||
package com.stdproject.common;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 分页查询请求类
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Data
|
||||
public class PageRequest {
|
||||
|
||||
/**
|
||||
* 当前页码,从1开始
|
||||
*/
|
||||
@NotNull(message = "页码不能为空")
|
||||
@Min(value = 1, message = "页码必须大于0") @Min(value = 1, message = "页码必须大于0")
|
||||
private Integer current = 1;
|
||||
|
||||
/**
|
||||
* 每页大小
|
||||
*/
|
||||
@NotNull(message = "每页大小不能为空")
|
||||
@Min(value = 1, message = "每页大小必须大于0")
|
||||
private Integer size = 20;
|
||||
|
||||
/**
|
||||
* 排序字段
|
||||
*/
|
||||
private String orderBy;
|
||||
|
||||
/**
|
||||
* 排序方向:asc-升序,desc-降序
|
||||
*/
|
||||
private String orderDirection = "desc";
|
||||
|
||||
/**
|
||||
* 搜索关键字
|
||||
*/
|
||||
private String keyword;
|
||||
|
||||
/**
|
||||
* 获取偏移量
|
||||
*
|
||||
* @return 偏移量
|
||||
*/
|
||||
public Integer getOffset() {
|
||||
return (current - 1) * size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否升序排序
|
||||
*
|
||||
* @return 是否升序
|
||||
*/
|
||||
public boolean isAsc() {
|
||||
return "asc".equalsIgnoreCase(orderDirection);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否降序排序
|
||||
*
|
||||
* @return 是否降序
|
||||
*/
|
||||
public boolean isDesc() {
|
||||
return "desc".equalsIgnoreCase(orderDirection);
|
||||
}
|
||||
}
|
191
backend/src/main/java/com/stdproject/common/Result.java
Normal file
191
backend/src/main/java/com/stdproject/common/Result.java
Normal file
@ -0,0 +1,191 @@
|
||||
package com.stdproject.common;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 统一响应结果类
|
||||
*
|
||||
* @author StdProject
|
||||
* @param <T> 数据类型
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class Result<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 响应码
|
||||
*/
|
||||
private Integer code;
|
||||
|
||||
/**
|
||||
* 响应消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
*/
|
||||
private T data;
|
||||
|
||||
/**
|
||||
* 时间戳
|
||||
*/
|
||||
private Long timestamp;
|
||||
|
||||
public Result() {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public Result(Integer code, String message) {
|
||||
this();
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Result(Integer code, String message, T data) {
|
||||
this(code, message);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功响应
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
* @return 成功响应
|
||||
*/
|
||||
public static <T> Result<T> success() {
|
||||
return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功响应
|
||||
*
|
||||
* @param data 响应数据
|
||||
* @param <T> 数据类型
|
||||
* @return 成功响应
|
||||
*/
|
||||
public static <T> Result<T> success(T data) {
|
||||
return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功响应
|
||||
*
|
||||
* @param message 响应消息
|
||||
* @param data 响应数据
|
||||
* @param <T> 数据类型
|
||||
* @return 成功响应
|
||||
*/
|
||||
public static <T> Result<T> success(String message, T data) {
|
||||
return new Result<>(ResultCode.SUCCESS.getCode(), message, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败响应
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
* @return 失败响应
|
||||
*/
|
||||
public static <T> Result<T> error() {
|
||||
return new Result<>(ResultCode.ERROR.getCode(), ResultCode.ERROR.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败响应
|
||||
*
|
||||
* @param message 错误消息
|
||||
* @param <T> 数据类型
|
||||
* @return 失败响应
|
||||
*/
|
||||
public static <T> Result<T> error(String message) {
|
||||
return new Result<>(ResultCode.ERROR.getCode(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败响应
|
||||
*
|
||||
* @param code 错误码
|
||||
* @param message 错误消息
|
||||
* @param <T> 数据类型
|
||||
* @return 失败响应
|
||||
*/
|
||||
public static <T> Result<T> error(Integer code, String message) {
|
||||
return new Result<>(code, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败响应
|
||||
*
|
||||
* @param resultCode 结果码枚举
|
||||
* @param <T> 数据类型
|
||||
* @return 失败响应
|
||||
*/
|
||||
public static <T> Result<T> error(ResultCode resultCode) {
|
||||
return new Result<>(resultCode.getCode(), resultCode.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 未授权响应
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
* @return 未授权响应
|
||||
*/
|
||||
public static <T> Result<T> unauthorized() {
|
||||
return new Result<>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁止访问响应
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
* @return 禁止访问响应
|
||||
*/
|
||||
public static <T> Result<T> forbidden() {
|
||||
return new Result<>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源未找到响应
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
* @return 资源未找到响应
|
||||
*/
|
||||
public static <T> Result<T> notFound() {
|
||||
return new Result<>(ResultCode.NOT_FOUND.getCode(), ResultCode.NOT_FOUND.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数错误响应
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
* @return 参数错误响应
|
||||
*/
|
||||
public static <T> Result<T> badRequest() {
|
||||
return new Result<>(ResultCode.BAD_REQUEST.getCode(), ResultCode.BAD_REQUEST.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数错误响应
|
||||
*
|
||||
* @param message 错误消息
|
||||
* @param <T> 数据类型
|
||||
* @return 参数错误响应
|
||||
*/
|
||||
public static <T> Result<T> badRequest(String message) {
|
||||
return new Result<>(ResultCode.BAD_REQUEST.getCode(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否成功
|
||||
*
|
||||
* @return 是否成功
|
||||
*/
|
||||
public boolean isSuccess() {
|
||||
return ResultCode.SUCCESS.getCode().equals(this.code);
|
||||
}
|
||||
}
|
223
backend/src/main/java/com/stdproject/common/ResultCode.java
Normal file
223
backend/src/main/java/com/stdproject/common/ResultCode.java
Normal file
@ -0,0 +1,223 @@
|
||||
package com.stdproject.common;
|
||||
|
||||
/**
|
||||
* 结果码枚举
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
public enum ResultCode {
|
||||
|
||||
/**
|
||||
* 成功
|
||||
*/
|
||||
SUCCESS(200, "操作成功"),
|
||||
|
||||
/**
|
||||
* 失败
|
||||
*/
|
||||
ERROR(500, "操作失败"),
|
||||
|
||||
/**
|
||||
* 参数错误
|
||||
*/
|
||||
BAD_REQUEST(400, "参数错误"),
|
||||
|
||||
/**
|
||||
* 未授权
|
||||
*/
|
||||
UNAUTHORIZED(401, "未授权"),
|
||||
|
||||
/**
|
||||
* 禁止访问
|
||||
*/
|
||||
FORBIDDEN(403, "禁止访问"),
|
||||
|
||||
/**
|
||||
* 资源未找到
|
||||
*/
|
||||
NOT_FOUND(404, "资源未找到"),
|
||||
|
||||
/**
|
||||
* 方法不允许
|
||||
*/
|
||||
METHOD_NOT_ALLOWED(405, "方法不允许"),
|
||||
|
||||
/**
|
||||
* 请求超时
|
||||
*/
|
||||
REQUEST_TIMEOUT(408, "请求超时"),
|
||||
|
||||
/**
|
||||
* 用户名或密码错误
|
||||
*/
|
||||
LOGIN_ERROR(1001, "用户名或密码错误"),
|
||||
|
||||
/**
|
||||
* 用户已被禁用
|
||||
*/
|
||||
USER_DISABLED(1002, "用户已被禁用"),
|
||||
|
||||
/**
|
||||
* 用户已被锁定
|
||||
*/
|
||||
USER_LOCKED(1003, "用户已被锁定"),
|
||||
|
||||
/**
|
||||
* 密码已过期
|
||||
*/
|
||||
PASSWORD_EXPIRED(1004, "密码已过期"),
|
||||
|
||||
/**
|
||||
* 登录失败次数过多
|
||||
*/
|
||||
LOGIN_FAILED_TOO_MANY(1005, "登录失败次数过多,账户已被锁定"),
|
||||
|
||||
/**
|
||||
* Token无效
|
||||
*/
|
||||
TOKEN_INVALID(1006, "Token无效"),
|
||||
|
||||
/**
|
||||
* Token已过期
|
||||
*/
|
||||
TOKEN_EXPIRED(1007, "Token已过期"),
|
||||
|
||||
/**
|
||||
* 用户不存在
|
||||
*/
|
||||
USER_NOT_FOUND(1008, "用户不存在"),
|
||||
|
||||
/**
|
||||
* 用户名已存在
|
||||
*/
|
||||
USERNAME_EXISTS(1009, "用户名已存在"),
|
||||
|
||||
/**
|
||||
* 邮箱已存在
|
||||
*/
|
||||
EMAIL_EXISTS(1010, "邮箱已存在"),
|
||||
|
||||
/**
|
||||
* 手机号已存在
|
||||
*/
|
||||
PHONE_EXISTS(1011, "手机号已存在"),
|
||||
|
||||
/**
|
||||
* 角色不存在
|
||||
*/
|
||||
ROLE_NOT_FOUND(1012, "角色不存在"),
|
||||
|
||||
/**
|
||||
* 角色已存在
|
||||
*/
|
||||
ROLE_EXISTS(1013, "角色已存在"),
|
||||
|
||||
/**
|
||||
* 菜单不存在
|
||||
*/
|
||||
MENU_NOT_FOUND(1014, "菜单不存在"),
|
||||
|
||||
/**
|
||||
* 菜单已存在
|
||||
*/
|
||||
MENU_EXISTS(1015, "菜单已存在"),
|
||||
|
||||
/**
|
||||
* 组织不存在
|
||||
*/
|
||||
ORGANIZATION_NOT_FOUND(1016, "组织不存在"),
|
||||
|
||||
/**
|
||||
* 组织已存在
|
||||
*/
|
||||
ORGANIZATION_EXISTS(1017, "组织已存在"),
|
||||
|
||||
/**
|
||||
* 数据字典不存在
|
||||
*/
|
||||
DICTIONARY_NOT_FOUND(1018, "数据字典不存在"),
|
||||
|
||||
/**
|
||||
* 数据字典已存在
|
||||
*/
|
||||
DICTIONARY_EXISTS(1019, "数据字典已存在"),
|
||||
|
||||
/**
|
||||
* 文件上传失败
|
||||
*/
|
||||
FILE_UPLOAD_ERROR(2001, "文件上传失败"),
|
||||
|
||||
/**
|
||||
* 文件下载失败
|
||||
*/
|
||||
FILE_DOWNLOAD_ERROR(2002, "文件下载失败"),
|
||||
|
||||
/**
|
||||
* 文件不存在
|
||||
*/
|
||||
FILE_NOT_FOUND(2003, "文件不存在"),
|
||||
|
||||
/**
|
||||
* 文件格式不支持
|
||||
*/
|
||||
FILE_FORMAT_NOT_SUPPORTED(2004, "文件格式不支持"),
|
||||
|
||||
/**
|
||||
* 文件大小超出限制
|
||||
*/
|
||||
FILE_SIZE_EXCEEDED(2005, "文件大小超出限制"),
|
||||
|
||||
/**
|
||||
* 数据库操作失败
|
||||
*/
|
||||
DATABASE_ERROR(3001, "数据库操作失败"),
|
||||
|
||||
/**
|
||||
* 数据不存在
|
||||
*/
|
||||
DATA_NOT_FOUND(3002, "数据不存在"),
|
||||
|
||||
/**
|
||||
* 数据已存在
|
||||
*/
|
||||
DATA_EXISTS(3003, "数据已存在"),
|
||||
|
||||
/**
|
||||
* 数据完整性约束违反
|
||||
*/
|
||||
DATA_INTEGRITY_VIOLATION(3004, "数据完整性约束违反"),
|
||||
|
||||
/**
|
||||
* 系统繁忙
|
||||
*/
|
||||
SYSTEM_BUSY(9001, "系统繁忙,请稍后重试"),
|
||||
|
||||
/**
|
||||
* 系统维护中
|
||||
*/
|
||||
SYSTEM_MAINTENANCE(9002, "系统维护中"),
|
||||
/**
|
||||
* 系统维护中
|
||||
*/
|
||||
|
||||
/**
|
||||
* 未知错误
|
||||
*/
|
||||
UNKNOWN_ERROR(9999, "未知错误");
|
||||
|
||||
private final Integer code;
|
||||
private final String message;
|
||||
|
||||
ResultCode(Integer code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
26
backend/src/main/java/com/stdproject/config/CacheConfig.java
Normal file
26
backend/src/main/java/com/stdproject/config/CacheConfig.java
Normal file
@ -0,0 +1,26 @@
|
||||
package com.stdproject.config;
|
||||
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 缓存配置类
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class CacheConfig {
|
||||
|
||||
// 缓存配置主要通过application.yml中的spring.cache.jcache.config配置
|
||||
// 这里启用缓存注解支持
|
||||
|
||||
/**
|
||||
* 缓存名称常量
|
||||
*/
|
||||
public static final String USER_CACHE = "userCache";
|
||||
public static final String ROLE_CACHE = "roleCache";
|
||||
public static final String MENU_CACHE = "menuCache";
|
||||
public static final String ORGANIZATION_CACHE = "organizationCache";
|
||||
public static final String DICTIONARY_CACHE = "dictionaryCache";
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
package com.stdproject.config;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.stdproject.entity.AppUser;
|
||||
import com.stdproject.entity.AppRole;
|
||||
import com.stdproject.entity.AppRoleUser;
|
||||
import com.stdproject.entity.AppRoleMenu;
|
||||
import com.stdproject.entity.AppMenu;
|
||||
import com.stdproject.service.IAppUserService;
|
||||
import com.stdproject.service.IAppRoleService;
|
||||
import com.stdproject.service.IAppRoleUserService;
|
||||
import com.stdproject.service.IAppRoleMenuService;
|
||||
import com.stdproject.service.IAppMenuService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 自定义用户详情服务
|
||||
* 实现Spring Security的UserDetailsService接口
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class CustomUserDetailsService implements UserDetailsService {
|
||||
|
||||
@Autowired
|
||||
private IAppUserService appUserService;
|
||||
|
||||
@Autowired
|
||||
private IAppRoleService appRoleService;
|
||||
|
||||
@Autowired
|
||||
private IAppRoleUserService appRoleUserService;
|
||||
|
||||
@Autowired
|
||||
private IAppRoleMenuService appRoleMenuService;
|
||||
|
||||
@Autowired
|
||||
private IAppMenuService appMenuService;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
log.debug("加载用户信息: {}", username);
|
||||
|
||||
AppUser appUser = appUserService.findByUsername(username);
|
||||
if (appUser == null) {
|
||||
log.warn("用户不存在: {}", username);
|
||||
throw new UsernameNotFoundException("用户不存在: " + username);
|
||||
}
|
||||
|
||||
// 检查用户状态
|
||||
if (!"1".equals(appUser.getStatus())) {
|
||||
log.warn("用户已被禁用: {}", username);
|
||||
throw new UsernameNotFoundException("用户已被禁用: " + username);
|
||||
}
|
||||
|
||||
// 构建用户权限
|
||||
Collection<GrantedAuthority> authorities = buildUserAuthorities(appUser);
|
||||
|
||||
return User.builder()
|
||||
.username(appUser.getUsername())
|
||||
.password(appUser.getPassword())
|
||||
.authorities(authorities)
|
||||
.accountExpired(false)
|
||||
.accountLocked(isAccountLocked(appUser))
|
||||
.credentialsExpired(isCredentialsExpired(appUser))
|
||||
.disabled(!"1".equals(appUser.getStatus()))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建用户权限
|
||||
*
|
||||
* @param appUser 用户信息
|
||||
* @return 权限集合
|
||||
*/
|
||||
private Collection<GrantedAuthority> buildUserAuthorities(AppUser appUser) {
|
||||
Set<GrantedAuthority> authorities = new HashSet<>();
|
||||
|
||||
try {
|
||||
// 根据用户类型添加基本角色权限
|
||||
if ("0".equals(appUser.getUsertype())) {
|
||||
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
|
||||
} else {
|
||||
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
|
||||
}
|
||||
|
||||
// 查询用户的角色
|
||||
QueryWrapper<AppRoleUser> roleUserQuery = new QueryWrapper<>();
|
||||
roleUserQuery.eq("userid", appUser.getId());
|
||||
List<AppRoleUser> roleUsers = appRoleUserService.list(roleUserQuery);
|
||||
|
||||
if (!roleUsers.isEmpty()) {
|
||||
// 获取角色ID列表
|
||||
List<String> roleIds = roleUsers.stream()
|
||||
.map(AppRoleUser::getRoleid)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 查询角色信息并添加角色权限
|
||||
List<AppRole> roles = appRoleService.listByIds(roleIds);
|
||||
for (AppRole role : roles) {
|
||||
if ("1".equals(role.getIsvaild())) {
|
||||
// 添加角色权限,格式:ROLE_角色编码
|
||||
if (StringUtils.hasText(role.getRolecode())) {
|
||||
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getRolecode().toUpperCase()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 查询角色关联的菜单权限
|
||||
QueryWrapper<AppRoleMenu> roleMenuQuery = new QueryWrapper<>();
|
||||
roleMenuQuery.in("roleid", roleIds);
|
||||
List<AppRoleMenu> roleMenus = appRoleMenuService.list(roleMenuQuery);
|
||||
|
||||
if (!roleMenus.isEmpty()) {
|
||||
// 获取菜单ID列表
|
||||
List<String> menuIds = roleMenus.stream()
|
||||
.map(AppRoleMenu::getMenuid)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 查询菜单信息并添加菜单权限
|
||||
List<AppMenu> menus = appMenuService.listByIds(menuIds);
|
||||
for (AppMenu menu : menus) {
|
||||
if ("1".equals(menu.getIsdisplay()) && StringUtils.hasText(menu.getCode())) {
|
||||
// 添加菜单权限,格式:菜单编码
|
||||
authorities.add(new SimpleGrantedAuthority(menu.getCode()));
|
||||
|
||||
// 根据菜单类型添加操作权限
|
||||
String menuCode = menu.getCode();
|
||||
if (StringUtils.hasText(menuCode)) {
|
||||
// 为每个菜单添加基本操作权限
|
||||
authorities.add(new SimpleGrantedAuthority(menuCode + ":list"));
|
||||
authorities.add(new SimpleGrantedAuthority(menuCode + ":detail"));
|
||||
|
||||
// 管理员拥有所有操作权限
|
||||
if ("0".equals(appUser.getUsertype())) {
|
||||
authorities.add(new SimpleGrantedAuthority(menuCode + ":add"));
|
||||
authorities.add(new SimpleGrantedAuthority(menuCode + ":edit"));
|
||||
authorities.add(new SimpleGrantedAuthority(menuCode + ":delete"));
|
||||
authorities.add(new SimpleGrantedAuthority(menuCode + ":permission"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("用户 {} 的权限列表: {}", appUser.getUsername(),
|
||||
authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("构建用户权限失败: {}", e.getMessage(), e);
|
||||
// 发生异常时,至少保证基本角色权限
|
||||
authorities.clear();
|
||||
if ("0".equals(appUser.getUsertype())) {
|
||||
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
|
||||
} else {
|
||||
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
|
||||
}
|
||||
}
|
||||
|
||||
return authorities;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查账户是否被锁定
|
||||
*
|
||||
* @param appUser 用户信息
|
||||
* @return 是否被锁定
|
||||
*/
|
||||
private boolean isAccountLocked(AppUser appUser) {
|
||||
// 检查登录失败锁定时间
|
||||
if (appUser.getFailedlocktime() != null) {
|
||||
// 如果锁定时间还未过期,则账户被锁定
|
||||
return appUser.getFailedlocktime().isAfter(java.time.LocalDateTime.now());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查凭证是否过期
|
||||
*
|
||||
* @param appUser 用户信息
|
||||
* @return 是否过期
|
||||
*/
|
||||
private boolean isCredentialsExpired(AppUser appUser) {
|
||||
// 检查密码是否过期
|
||||
if (appUser.getPwdresettime() != null && appUser.getPwdvalidperiod() != null) {
|
||||
java.time.LocalDateTime expireTime = appUser.getPwdresettime().plusDays(appUser.getPwdvalidperiod());
|
||||
return expireTime.isBefore(java.time.LocalDateTime.now());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package com.stdproject.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.stdproject.common.Result;
|
||||
import com.stdproject.common.ResultCode;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* JWT认证入口点
|
||||
* 处理未认证的请求
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
|
||||
throws IOException, ServletException {
|
||||
|
||||
log.warn("未认证访问: {} - {}", request.getRequestURI(), authException.getMessage());
|
||||
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
|
||||
Result<Void> result = Result.error(ResultCode.UNAUTHORIZED);
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
String jsonResult = objectMapper.writeValueAsString(result);
|
||||
|
||||
response.getWriter().write(jsonResult);
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package com.stdproject.config;
|
||||
|
||||
import com.stdproject.utils.JwtUtils;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* JWT认证过滤器
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Autowired
|
||||
private JwtUtils jwtUtils;
|
||||
|
||||
@Autowired
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
|
||||
try {
|
||||
String jwt = getJwtFromRequest(request);
|
||||
|
||||
if (StringUtils.hasText(jwt) && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
String username = jwtUtils.getUsernameFromToken(jwt);
|
||||
|
||||
if (StringUtils.hasText(username)) {
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
|
||||
if (jwtUtils.validateToken(jwt, userDetails.getUsername())) {
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("无法设置用户认证: {}", e.getMessage());
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求中获取JWT令牌
|
||||
*
|
||||
* @param request HTTP请求
|
||||
* @return JWT令牌
|
||||
*/
|
||||
private String getJwtFromRequest(HttpServletRequest request) {
|
||||
String bearerToken = request.getHeader("Authorization");
|
||||
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
|
||||
return bearerToken.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.stdproject.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* MyBatis-Plus配置类
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Configuration
|
||||
@MapperScan("com.stdproject.mapper")
|
||||
public class MybatisPlusConfig {
|
||||
|
||||
/**
|
||||
* MyBatis-Plus拦截器配置
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
|
||||
// 分页插件
|
||||
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
|
||||
// 设置最大单页限制数量,默认 500 条,-1 不受限制
|
||||
paginationInnerInterceptor.setMaxLimit(1000L);
|
||||
// 溢出总页数后是否进行处理
|
||||
paginationInnerInterceptor.setOverflow(false);
|
||||
interceptor.addInnerInterceptor(paginationInnerInterceptor);
|
||||
|
||||
// 乐观锁插件
|
||||
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
|
||||
|
||||
return interceptor;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.stdproject.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
/**
|
||||
* 密码编码器配置类
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Configuration
|
||||
public class PasswordEncoderConfig {
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
172
backend/src/main/java/com/stdproject/config/SecurityConfig.java
Normal file
172
backend/src/main/java/com/stdproject/config/SecurityConfig.java
Normal file
@ -0,0 +1,172 @@
|
||||
package com.stdproject.config;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Spring Security配置类
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity(prePostEnabled = true)
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig {
|
||||
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
|
||||
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
private final CustomUserDetailsService customUserDetailsService;
|
||||
|
||||
@Value("${spring.security.jwt.enabled:true}")
|
||||
private boolean jwtEnabled;
|
||||
|
||||
@Value("${spring.security.cors.allowed-origins:http://localhost:3000,http://localhost:8080}")
|
||||
private List<String> allowedOrigins;
|
||||
|
||||
@Value("${spring.security.cors.max-age:3600}")
|
||||
private Long corsMaxAge;
|
||||
|
||||
// 公开路径配置
|
||||
private static final String[] PUBLIC_PATHS = {
|
||||
"/api/auth/**",
|
||||
"/api/public/**",
|
||||
"/swagger-ui/**",
|
||||
"/v3/api-docs/**",
|
||||
"/swagger-ui.html",
|
||||
"/webjars/**",
|
||||
"/favicon.ico",
|
||||
"/error",
|
||||
"/actuator/health",
|
||||
"/actuator/info"
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 认证管理器
|
||||
*/
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
|
||||
return config.getAuthenticationManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* CORS配置
|
||||
* 更安全的CORS配置,支持配置化的允许源
|
||||
*/
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
|
||||
// 设置允许的源,支持配置化
|
||||
configuration.setAllowedOrigins(allowedOrigins);
|
||||
|
||||
// 设置允许的HTTP方法
|
||||
configuration.setAllowedMethods(Arrays.asList(
|
||||
"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"
|
||||
));
|
||||
|
||||
// 设置允许的请求头
|
||||
configuration.setAllowedHeaders(Arrays.asList(
|
||||
"Authorization", "Content-Type", "X-Requested-With",
|
||||
"Accept", "Origin", "Access-Control-Request-Method",
|
||||
"Access-Control-Request-Headers", "X-CSRF-TOKEN"
|
||||
));
|
||||
|
||||
// 设置暴露的响应头
|
||||
configuration.setExposedHeaders(Arrays.asList(
|
||||
"Access-Control-Allow-Origin", "Access-Control-Allow-Credentials",
|
||||
"Authorization", "Content-Disposition"
|
||||
));
|
||||
|
||||
configuration.setAllowCredentials(true);
|
||||
configuration.setMaxAge(corsMaxAge);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全过滤器链配置
|
||||
* 增强的安全配置,包含安全头和更完善的认证授权
|
||||
*/
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
// CORS配置
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||
|
||||
// 禁用CSRF(因为使用JWT,无状态)
|
||||
.csrf(csrf -> csrf.disable())
|
||||
|
||||
// 会话管理配置
|
||||
.sessionManagement(session -> session
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
.maximumSessions(1)
|
||||
.maxSessionsPreventsLogin(false)
|
||||
)
|
||||
|
||||
// 异常处理配置
|
||||
.exceptionHandling(ex -> ex
|
||||
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
|
||||
)
|
||||
|
||||
// 安全头配置
|
||||
.headers(headers -> headers
|
||||
// 禁用iframe嵌入,防止点击劫持攻击
|
||||
.frameOptions().deny()
|
||||
// 设置X-Content-Type-Options头为nosniff,防止MIME类型嗅探攻击
|
||||
.contentTypeOptions().and()
|
||||
// 设置Referrer Policy为strict-origin-when-cross-origin
|
||||
// 跨域请求时只发送源(origin),同源请求发送完整referrer
|
||||
.referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)
|
||||
// 注意:已移除HSTS配置,不再强制使用HTTPS
|
||||
);
|
||||
|
||||
// 根据配置决定是否启用JWT认证
|
||||
if (jwtEnabled) {
|
||||
http.authorizeHttpRequests(authz -> authz
|
||||
// 公开接口,无需认证
|
||||
.requestMatchers(PUBLIC_PATHS).permitAll()
|
||||
// 管理员接口需要管理员权限
|
||||
.requestMatchers("/api/admin/**").hasRole("ADMIN")
|
||||
// 其他所有请求都需要认证
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
|
||||
// 添加JWT过滤器
|
||||
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
} else {
|
||||
// 如果禁用JWT认证,则允许所有请求(开发环境)
|
||||
http.authorizeHttpRequests(authz -> authz
|
||||
.anyRequest().permitAll()
|
||||
);
|
||||
}
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.stdproject.config;
|
||||
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Swagger配置类
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Configuration
|
||||
public class SwaggerConfig {
|
||||
|
||||
/**
|
||||
* OpenAPI配置
|
||||
*/
|
||||
@Bean
|
||||
public OpenAPI customOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("StdProject API")
|
||||
.version("1.0.0")
|
||||
.description("标准项目后端API文档")
|
||||
.contact(new Contact()
|
||||
.name("StdProject Team")
|
||||
.email("support@stdproject.com")
|
||||
.url("https://www.stdproject.com"))
|
||||
.license(new License()
|
||||
.name("Apache 2.0")
|
||||
.url("https://www.apache.org/licenses/LICENSE-2.0")))
|
||||
.components(new Components()
|
||||
.addSecuritySchemes("Bearer", new SecurityScheme()
|
||||
.type(SecurityScheme.Type.HTTP)
|
||||
.scheme("bearer")
|
||||
.bearerFormat("JWT")
|
||||
.description("JWT认证,请在值前面加上'Bearer '前缀")))
|
||||
.addSecurityItem(new SecurityRequirement().addList("Bearer"));
|
||||
}
|
||||
}
|
28
backend/src/main/java/com/stdproject/config/WebConfig.java
Normal file
28
backend/src/main/java/com/stdproject/config/WebConfig.java
Normal file
@ -0,0 +1,28 @@
|
||||
package com.stdproject.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* Web配置类
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
/**
|
||||
* 静态资源处理
|
||||
*/
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
// Swagger UI 静态资源
|
||||
registry.addResourceHandler("/swagger-ui/**")
|
||||
.addResourceLocations("classpath:/META-INF/resources/webjars/swagger-ui/");
|
||||
|
||||
// 其他静态资源
|
||||
registry.addResourceHandler("/static/**")
|
||||
.addResourceLocations("classpath:/static/");
|
||||
}
|
||||
}
|
@ -0,0 +1,270 @@
|
||||
package com.stdproject.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.stdproject.common.OperationLog;
|
||||
import com.stdproject.common.PageRequest;
|
||||
import com.stdproject.common.Result;
|
||||
import com.stdproject.entity.AppDictionary;
|
||||
import com.stdproject.service.IAppDictionaryService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用-数据字典表 前端控制器
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Tag(name = "数据字典管理", description = "数据字典添加、数据字典修改、数据字典删除、数据字典查询等功能")
|
||||
@RestController
|
||||
@RequestMapping("/app-dictionary")
|
||||
public class AppDictionaryController {
|
||||
|
||||
@Autowired
|
||||
private IAppDictionaryService appDictionaryService;
|
||||
|
||||
@Operation(summary = "分页查询数据字典列表")
|
||||
@PostMapping("/page")
|
||||
@OperationLog(type = "06", module = "数据字典管理", description = "分页查询数据字典列表")
|
||||
public Result<IPage<AppDictionary>> page(@RequestBody @Valid PageRequest pageRequest) {
|
||||
Page<AppDictionary> page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize());
|
||||
QueryWrapper<AppDictionary> queryWrapper = new QueryWrapper<>();
|
||||
|
||||
// 关键字搜索
|
||||
if (StringUtils.hasText(pageRequest.getKeyword())) {
|
||||
queryWrapper.and(wrapper -> wrapper
|
||||
.like("dictname", pageRequest.getKeyword())
|
||||
.or().like("dictcode", pageRequest.getKeyword())
|
||||
.or().like("dictdata", pageRequest.getKeyword())
|
||||
);
|
||||
}
|
||||
|
||||
// 排序
|
||||
if (StringUtils.hasText(pageRequest.getOrderBy())) {
|
||||
if ("asc".equalsIgnoreCase(pageRequest.getOrderDirection())) {
|
||||
queryWrapper.orderByAsc(pageRequest.getOrderBy());
|
||||
} else {
|
||||
queryWrapper.orderByDesc(pageRequest.getOrderBy());
|
||||
}
|
||||
} else {
|
||||
queryWrapper.orderByAsc("dictcode", "orderno");
|
||||
}
|
||||
|
||||
IPage<AppDictionary> result = appDictionaryService.page(page, queryWrapper);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@Operation(summary = "查询所有数据字典列表")
|
||||
@GetMapping("/list")
|
||||
@OperationLog(type = "06", module = "数据字典管理", description = "查询所有数据字典列表")
|
||||
public Result<List<AppDictionary>> list() {
|
||||
QueryWrapper<AppDictionary> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.orderByAsc("dictcode", "orderno");
|
||||
|
||||
List<AppDictionary> dictionaries = appDictionaryService.list(queryWrapper);
|
||||
return Result.success(dictionaries);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据ID查询数据字典详情")
|
||||
@GetMapping("/{id}")
|
||||
@OperationLog(type = "06", module = "数据字典管理", description = "查询数据字典详情")
|
||||
public Result<AppDictionary> getById(@Parameter(description = "字典ID") @PathVariable String id) {
|
||||
AppDictionary dictionary = appDictionaryService.getById(id);
|
||||
return Result.success(dictionary);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据字典编码查询字典项列表")
|
||||
@GetMapping("/code/{dictCode}")
|
||||
@OperationLog(type = "06", module = "数据字典管理", description = "根据字典编码查询字典项列表")
|
||||
public Result<List<AppDictionary>> getByDictCode(@Parameter(description = "字典编码") @PathVariable String dictCode) {
|
||||
QueryWrapper<AppDictionary> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("dictcode", dictCode);
|
||||
queryWrapper.orderByAsc("orderno");
|
||||
|
||||
List<AppDictionary> dictionaries = appDictionaryService.list(queryWrapper);
|
||||
return Result.success(dictionaries);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据字典编码和字典值查询字典项")
|
||||
@GetMapping("/code/{dictCode}/data/{dictData}")
|
||||
@OperationLog(type = "06", module = "数据字典管理", description = "根据字典编码和字典数据查询字典项")
|
||||
public Result<AppDictionary> getByCodeAndData(
|
||||
@Parameter(description = "字典编码") @PathVariable String dictCode,
|
||||
@Parameter(description = "字典数据") @PathVariable String dictData) {
|
||||
QueryWrapper<AppDictionary> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("dictcode", dictCode);
|
||||
queryWrapper.eq("dictdata", dictData);
|
||||
|
||||
AppDictionary dictionary = appDictionaryService.getOne(queryWrapper);
|
||||
return Result.success(dictionary);
|
||||
}
|
||||
|
||||
@Operation(summary = "新增数据字典")
|
||||
@PostMapping
|
||||
@OperationLog(type = "01", module = "数据字典管理", description = "新增数据字典")
|
||||
public Result<String> save(@RequestBody @Valid AppDictionary appDictionary) {
|
||||
// 检查同一字典编码下的字典值是否已存在
|
||||
QueryWrapper<AppDictionary> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("dictcode", appDictionary.getDictcode());
|
||||
queryWrapper.eq("dictdata", appDictionary.getDictdata());
|
||||
AppDictionary existDictionary = appDictionaryService.getOne(queryWrapper);
|
||||
if (existDictionary != null) {
|
||||
return Result.error("该字典编码下的字典值已存在");
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if (appDictionary.getOrderno() == null) {
|
||||
// 获取同一字典编码下的最大序号
|
||||
QueryWrapper<AppDictionary> orderQuery = new QueryWrapper<>();
|
||||
orderQuery.eq("dictcode", appDictionary.getDictcode());
|
||||
orderQuery.orderByDesc("orderno");
|
||||
orderQuery.last("LIMIT 1");
|
||||
AppDictionary lastDict = appDictionaryService.getOne(orderQuery);
|
||||
appDictionary.setOrderno(lastDict != null ? lastDict.getOrderno() + 1 : 1);
|
||||
}
|
||||
|
||||
appDictionary.setLastmodifydate(LocalDateTime.now());
|
||||
boolean success = appDictionaryService.save(appDictionary);
|
||||
return success ? Result.success("新增成功") : Result.error("新增失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "修改数据字典")
|
||||
@PutMapping
|
||||
@OperationLog(type = "02", module = "数据字典管理", description = "修改数据字典")
|
||||
public Result<String> update(@RequestBody @Valid AppDictionary appDictionary) {
|
||||
// 检查字典是否存在
|
||||
AppDictionary existDictionary = appDictionaryService.getById(appDictionary.getId());
|
||||
if (existDictionary == null) {
|
||||
return Result.error("数据字典不存在");
|
||||
}
|
||||
|
||||
// 如果修改了字典编码或字典值,检查新的组合是否已被其他字典项使用
|
||||
if (!existDictionary.getDictcode().equals(appDictionary.getDictcode()) ||
|
||||
!existDictionary.getDictdata().equals(appDictionary.getDictdata())) {
|
||||
QueryWrapper<AppDictionary> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("dictcode", appDictionary.getDictcode());
|
||||
queryWrapper.eq("dictdata", appDictionary.getDictdata());
|
||||
queryWrapper.ne("id", appDictionary.getId());
|
||||
AppDictionary dictWithSameCodeValue = appDictionaryService.getOne(queryWrapper);
|
||||
if (dictWithSameCodeValue != null) {
|
||||
return Result.error("该字典编码下的字典数据已被其他字典项使用");
|
||||
}
|
||||
}
|
||||
|
||||
appDictionary.setLastmodifydate(LocalDateTime.now());
|
||||
boolean success = appDictionaryService.updateById(appDictionary);
|
||||
return success ? Result.success("修改成功") : Result.error("修改失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "删除数据字典")
|
||||
@DeleteMapping("/{id}")
|
||||
@OperationLog(type = "03", module = "数据字典管理", description = "删除数据字典")
|
||||
public Result<String> delete(@Parameter(description = "字典ID") @PathVariable String id) {
|
||||
boolean success = appDictionaryService.removeById(id);
|
||||
return success ? Result.success("删除成功") : Result.error("删除失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "批量删除数据字典")
|
||||
@DeleteMapping("/batch")
|
||||
@OperationLog(type = "03", module = "数据字典管理", description = "批量删除数据字典")
|
||||
public Result<String> deleteBatch(@RequestBody List<String> ids) {
|
||||
boolean success = appDictionaryService.removeByIds(ids);
|
||||
return success ? Result.success("批量删除成功") : Result.error("批量删除失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "根据字典编码删除所有字典项")
|
||||
@DeleteMapping("/code/{dictCode}")
|
||||
@OperationLog(type = "03", module = "数据字典管理", description = "根据字典编码删除所有字典项")
|
||||
public Result<String> deleteByDictCode(@Parameter(description = "字典编码") @PathVariable String dictCode) {
|
||||
QueryWrapper<AppDictionary> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("dictcode", dictCode);
|
||||
|
||||
boolean success = appDictionaryService.remove(queryWrapper);
|
||||
return success ? Result.success("删除成功") : Result.error("删除失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "根据字典名称获取字典数据")
|
||||
@GetMapping("/name/{dictName}")
|
||||
@OperationLog(type = "06", module = "数据字典管理", description = "根据字典名称获取字典数据")
|
||||
public Result<String> getDictDataByName(@Parameter(description = "字典名称") @PathVariable String dictName) {
|
||||
QueryWrapper<AppDictionary> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("dictname", dictName);
|
||||
AppDictionary dictionary = appDictionaryService.getOne(queryWrapper);
|
||||
|
||||
if (dictionary != null) {
|
||||
return Result.success(dictionary.getDictdata());
|
||||
}
|
||||
return Result.error("未找到对应的字典数据");
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有字典编码列表")
|
||||
@GetMapping("/codes")
|
||||
@OperationLog(type = "06", module = "数据字典管理", description = "获取所有字典编码列表")
|
||||
public Result<List<String>> getDictCodes() {
|
||||
QueryWrapper<AppDictionary> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.select("DISTINCT dictcode");
|
||||
queryWrapper.orderByAsc("dictcode");
|
||||
|
||||
List<AppDictionary> dictionaries = appDictionaryService.list(queryWrapper);
|
||||
List<String> dictCodes = dictionaries.stream()
|
||||
.map(AppDictionary::getDictcode)
|
||||
.distinct()
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
|
||||
return Result.success(dictCodes);
|
||||
}
|
||||
|
||||
@Operation(summary = "调整字典项顺序")
|
||||
@PutMapping("/reorder")
|
||||
@OperationLog(type = "02", module = "数据字典管理", description = "调整字典项顺序")
|
||||
public Result<String> reorder(@RequestBody List<DictOrderRequest> orderRequests) {
|
||||
for (DictOrderRequest request : orderRequests) {
|
||||
AppDictionary dictionary = appDictionaryService.getById(request.getId());
|
||||
if (dictionary != null) {
|
||||
dictionary.setOrderno(request.getOrderno());
|
||||
dictionary.setLastmodifydate(LocalDateTime.now());
|
||||
appDictionaryService.updateById(dictionary);
|
||||
}
|
||||
}
|
||||
return Result.success("顺序调整成功");
|
||||
}
|
||||
|
||||
@Operation(summary = "根据字典编码分组查询字典项")
|
||||
@GetMapping("/grouped")
|
||||
@OperationLog(type = "06", module = "数据字典管理", description = "根据字典编码分组查询字典项")
|
||||
public Result<java.util.Map<String, List<AppDictionary>>> getGroupedDictionaries() {
|
||||
QueryWrapper<AppDictionary> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.orderByAsc("dictcode", "orderno");
|
||||
|
||||
List<AppDictionary> allDictionaries = appDictionaryService.list(queryWrapper);
|
||||
java.util.Map<String, List<AppDictionary>> groupedDict = allDictionaries.stream()
|
||||
.collect(java.util.stream.Collectors.groupingBy(AppDictionary::getDictcode));
|
||||
|
||||
return Result.success(groupedDict);
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典排序请求
|
||||
*/
|
||||
public static class DictOrderRequest {
|
||||
private String id;
|
||||
private Integer orderno;
|
||||
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
public Integer getOrderno() { return orderno; }
|
||||
public void setOrderno(Integer orderno) { this.orderno = orderno; }
|
||||
}
|
||||
}
|
@ -0,0 +1,378 @@
|
||||
package com.stdproject.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.stdproject.common.OperationLog;
|
||||
import com.stdproject.common.PageRequest;
|
||||
import com.stdproject.common.Result;
|
||||
import com.stdproject.entity.AppMenu;
|
||||
import com.stdproject.service.IAppMenuService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用-菜单表 前端控制器
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Tag(name = "菜单管理", description = "菜单添加、菜单修改、菜单删除、菜单查询等功能")
|
||||
@RestController
|
||||
@RequestMapping("/app-menu")
|
||||
public class AppMenuController {
|
||||
|
||||
@Autowired
|
||||
private IAppMenuService appMenuService;
|
||||
|
||||
@Operation(summary = "分页查询菜单列表")
|
||||
@PostMapping("/page")
|
||||
@OperationLog(type = "06", module = "菜单管理", description = "分页查询菜单列表")
|
||||
public Result<IPage<AppMenu>> page(@RequestBody @Valid PageRequest pageRequest) {
|
||||
Page<AppMenu> page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize());
|
||||
QueryWrapper<AppMenu> queryWrapper = new QueryWrapper<>();
|
||||
|
||||
// 关键字搜索
|
||||
if (StringUtils.hasText(pageRequest.getKeyword())) {
|
||||
queryWrapper.and(wrapper -> wrapper
|
||||
.like("name", pageRequest.getKeyword())
|
||||
.or().like("code", pageRequest.getKeyword())
|
||||
.or().like("url", pageRequest.getKeyword())
|
||||
);
|
||||
}
|
||||
|
||||
// 排序
|
||||
if (StringUtils.hasText(pageRequest.getOrderBy())) {
|
||||
if ("asc".equalsIgnoreCase(pageRequest.getOrderDirection())) {
|
||||
queryWrapper.orderByAsc(pageRequest.getOrderBy());
|
||||
} else {
|
||||
queryWrapper.orderByDesc(pageRequest.getOrderBy());
|
||||
}
|
||||
} else {
|
||||
queryWrapper.orderByAsc("parentid", "orderno");
|
||||
}
|
||||
|
||||
IPage<AppMenu> result = appMenuService.page(page, queryWrapper);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@Operation(summary = "查询所有菜单列表")
|
||||
@GetMapping("/list")
|
||||
@OperationLog(type = "06", module = "菜单管理", description = "查询所有菜单列表")
|
||||
public Result<List<AppMenu>> list() {
|
||||
QueryWrapper<AppMenu> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.orderByAsc("parentid", "orderno");
|
||||
|
||||
List<AppMenu> menus = appMenuService.list(queryWrapper);
|
||||
return Result.success(menus);
|
||||
}
|
||||
|
||||
@Operation(summary = "获取菜单树形结构")
|
||||
@GetMapping("/tree")
|
||||
@OperationLog(type = "06", module = "菜单管理", description = "获取菜单树形结构")
|
||||
public Result<List<MenuTreeNode>> getMenuTree() {
|
||||
QueryWrapper<AppMenu> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.orderByAsc("parentid", "orderno");
|
||||
|
||||
List<AppMenu> allMenus = appMenuService.list(queryWrapper);
|
||||
List<MenuTreeNode> tree = buildMenuTree(allMenus, "0");
|
||||
return Result.success(tree);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据ID查询菜单详情")
|
||||
@GetMapping("/{id}")
|
||||
@OperationLog(type = "06", module = "菜单管理", description = "查询菜单详情")
|
||||
public Result<AppMenu> getById(@Parameter(description = "菜单ID") @PathVariable String id) {
|
||||
AppMenu menu = appMenuService.getById(id);
|
||||
return Result.success(menu);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据父级ID查询子菜单")
|
||||
@GetMapping("/children/{parentId}")
|
||||
@OperationLog(type = "06", module = "菜单管理", description = "根据父级ID查询子菜单")
|
||||
public Result<List<AppMenu>> getChildren(@Parameter(description = "父级菜单ID") @PathVariable String parentId) {
|
||||
QueryWrapper<AppMenu> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("parentid", parentId);
|
||||
queryWrapper.orderByAsc("orderno");
|
||||
|
||||
List<AppMenu> children = appMenuService.list(queryWrapper);
|
||||
return Result.success(children);
|
||||
}
|
||||
|
||||
@Operation(summary = "新增菜单")
|
||||
@PostMapping
|
||||
@OperationLog(type = "01", module = "菜单管理", description = "新增菜单")
|
||||
public Result<String> save(@RequestBody @Valid AppMenu appMenu) {
|
||||
// 检查菜单编号是否已存在
|
||||
QueryWrapper<AppMenu> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("code", appMenu.getCode());
|
||||
AppMenu existMenu = appMenuService.getOne(queryWrapper);
|
||||
if (existMenu != null) {
|
||||
return Result.error("菜单编号已存在");
|
||||
}
|
||||
|
||||
// 检查父级菜单是否存在(如果不是顶级菜单)
|
||||
if (!"0".equals(appMenu.getParentid())) {
|
||||
AppMenu parentMenu = appMenuService.getById(appMenu.getParentid());
|
||||
if (parentMenu == null) {
|
||||
return Result.error("父级菜单不存在");
|
||||
}
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if (appMenu.getOrderno() == null) {
|
||||
// 获取同级菜单的最大序号
|
||||
QueryWrapper<AppMenu> orderQuery = new QueryWrapper<>();
|
||||
orderQuery.eq("parentid", appMenu.getParentid());
|
||||
orderQuery.orderByDesc("orderno");
|
||||
orderQuery.last("LIMIT 1");
|
||||
AppMenu lastMenu = appMenuService.getOne(orderQuery);
|
||||
appMenu.setOrderno(lastMenu != null ? lastMenu.getOrderno() + 1 : 1);
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(appMenu.getIsdisplay())) {
|
||||
appMenu.setIsdisplay("1");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(appMenu.getIslink())) {
|
||||
appMenu.setIslink("0");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(appMenu.getType())) {
|
||||
appMenu.setType("01");
|
||||
}
|
||||
|
||||
appMenu.setLastmodifydate(LocalDateTime.now());
|
||||
boolean success = appMenuService.save(appMenu);
|
||||
return success ? Result.success("新增成功") : Result.error("新增失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "修改菜单")
|
||||
@PutMapping
|
||||
@OperationLog(type = "02", module = "菜单管理", description = "修改菜单")
|
||||
public Result<String> update(@RequestBody @Valid AppMenu appMenu) {
|
||||
// 检查菜单是否存在
|
||||
AppMenu existMenu = appMenuService.getById(appMenu.getId());
|
||||
if (existMenu == null) {
|
||||
return Result.error("菜单不存在");
|
||||
}
|
||||
|
||||
// 如果修改了菜单编号,检查新编号是否已被其他菜单使用
|
||||
if (!existMenu.getCode().equals(appMenu.getCode())) {
|
||||
QueryWrapper<AppMenu> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("code", appMenu.getCode());
|
||||
queryWrapper.ne("id", appMenu.getId());
|
||||
AppMenu menuWithSameCode = appMenuService.getOne(queryWrapper);
|
||||
if (menuWithSameCode != null) {
|
||||
return Result.error("菜单编号已被其他菜单使用");
|
||||
}
|
||||
}
|
||||
|
||||
// 如果修改了父级菜单,检查是否会形成循环引用
|
||||
if (!existMenu.getParentid().equals(appMenu.getParentid())) {
|
||||
if (isCircularReference(appMenu.getId(), appMenu.getParentid())) {
|
||||
return Result.error("不能将菜单移动到自己的子菜单下");
|
||||
}
|
||||
|
||||
// 检查父级菜单是否存在(如果不是顶级菜单)
|
||||
if (!"0".equals(appMenu.getParentid())) {
|
||||
AppMenu parentMenu = appMenuService.getById(appMenu.getParentid());
|
||||
if (parentMenu == null) {
|
||||
return Result.error("父级菜单不存在");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
appMenu.setLastmodifydate(LocalDateTime.now());
|
||||
boolean success = appMenuService.updateById(appMenu);
|
||||
return success ? Result.success("修改成功") : Result.error("修改失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "删除菜单")
|
||||
@DeleteMapping("/{id}")
|
||||
@OperationLog(type = "03", module = "菜单管理", description = "删除菜单")
|
||||
public Result<String> delete(@Parameter(description = "菜单ID") @PathVariable String id) {
|
||||
// 检查是否有子菜单
|
||||
QueryWrapper<AppMenu> childQuery = new QueryWrapper<>();
|
||||
childQuery.eq("parentid", id);
|
||||
long childCount = appMenuService.count(childQuery);
|
||||
if (childCount > 0) {
|
||||
return Result.error("存在子菜单,无法删除");
|
||||
}
|
||||
|
||||
boolean success = appMenuService.removeById(id);
|
||||
return success ? Result.success("删除成功") : Result.error("删除失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "批量删除菜单")
|
||||
@DeleteMapping("/batch")
|
||||
@OperationLog(type = "03", module = "菜单管理", description = "批量删除菜单")
|
||||
public Result<String> deleteBatch(@RequestBody List<String> ids) {
|
||||
for (String id : ids) {
|
||||
// 检查是否有子菜单
|
||||
QueryWrapper<AppMenu> childQuery = new QueryWrapper<>();
|
||||
childQuery.eq("parentid", id);
|
||||
long childCount = appMenuService.count(childQuery);
|
||||
if (childCount > 0) {
|
||||
return Result.error("存在子菜单的菜单无法删除");
|
||||
}
|
||||
}
|
||||
|
||||
boolean success = appMenuService.removeByIds(ids);
|
||||
return success ? Result.success("批量删除成功") : Result.error("批量删除失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "根据菜单类型查询菜单列表")
|
||||
@GetMapping("/type/{type}")
|
||||
@OperationLog(type = "06", module = "菜单管理", description = "根据菜单类型查询菜单列表")
|
||||
public Result<List<AppMenu>> getByType(@Parameter(description = "菜单类型") @PathVariable String type) {
|
||||
QueryWrapper<AppMenu> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("type", type);
|
||||
queryWrapper.orderByAsc("parentid", "orderno");
|
||||
|
||||
List<AppMenu> menus = appMenuService.list(queryWrapper);
|
||||
return Result.success(menus);
|
||||
}
|
||||
|
||||
@Operation(summary = "获取显示的菜单树")
|
||||
@GetMapping("/display-tree")
|
||||
@OperationLog(type = "06", module = "菜单管理", description = "获取显示的菜单树")
|
||||
public Result<List<MenuTreeNode>> getDisplayMenuTree() {
|
||||
QueryWrapper<AppMenu> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("isdisplay", "1");
|
||||
queryWrapper.orderByAsc("parentid", "orderno");
|
||||
|
||||
List<AppMenu> displayMenus = appMenuService.list(queryWrapper);
|
||||
List<MenuTreeNode> tree = buildMenuTree(displayMenus, "0");
|
||||
return Result.success(tree);
|
||||
}
|
||||
|
||||
@Operation(summary = "调整菜单顺序")
|
||||
@PutMapping("/reorder")
|
||||
@OperationLog(type = "02", module = "菜单管理", description = "调整菜单顺序")
|
||||
public Result<String> reorder(@RequestBody List<MenuOrderRequest> orderRequests) {
|
||||
for (MenuOrderRequest request : orderRequests) {
|
||||
AppMenu menu = appMenuService.getById(request.getId());
|
||||
if (menu != null) {
|
||||
menu.setOrderno(request.getOrderno());
|
||||
menu.setLastmodifydate(LocalDateTime.now());
|
||||
appMenuService.updateById(menu);
|
||||
}
|
||||
}
|
||||
return Result.success("顺序调整成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建菜单树
|
||||
*/
|
||||
private List<MenuTreeNode> buildMenuTree(List<AppMenu> allMenus, String parentId) {
|
||||
return allMenus.stream()
|
||||
.filter(menu -> parentId.equals(menu.getParentid()))
|
||||
.map(menu -> {
|
||||
MenuTreeNode node = new MenuTreeNode();
|
||||
node.setId(menu.getId());
|
||||
node.setCode(menu.getCode());
|
||||
node.setName(menu.getName());
|
||||
node.setIcon(menu.getIcon());
|
||||
node.setUrl(menu.getUrl());
|
||||
node.setType(menu.getType());
|
||||
node.setIslink(menu.getIslink());
|
||||
node.setIsdisplay(menu.getIsdisplay());
|
||||
node.setOrderno(menu.getOrderno());
|
||||
node.setParentid(menu.getParentid());
|
||||
node.setModuleId(menu.getModuleId());
|
||||
node.setChildren(buildMenuTree(allMenus, menu.getId()));
|
||||
return node;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否会形成循环引用
|
||||
*/
|
||||
private boolean isCircularReference(String menuId, String newParentId) {
|
||||
if ("0".equals(newParentId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (menuId.equals(newParentId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
AppMenu parentMenu = appMenuService.getById(newParentId);
|
||||
if (parentMenu == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isCircularReference(menuId, parentMenu.getParentid());
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单树节点
|
||||
*/
|
||||
public static class MenuTreeNode {
|
||||
private String id;
|
||||
private String code;
|
||||
private String name;
|
||||
private String icon;
|
||||
private String url;
|
||||
private String type;
|
||||
private String islink;
|
||||
private String isdisplay;
|
||||
private Integer orderno;
|
||||
private String parentid;
|
||||
private String moduleId;
|
||||
private List<MenuTreeNode> children = new ArrayList<>();
|
||||
|
||||
// Getters and Setters
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
public String getCode() { return code; }
|
||||
public void setCode(String code) { this.code = code; }
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public String getIcon() { return icon; }
|
||||
public void setIcon(String icon) { this.icon = icon; }
|
||||
public String getUrl() { return url; }
|
||||
public void setUrl(String url) { this.url = url; }
|
||||
public String getType() { return type; }
|
||||
public void setType(String type) { this.type = type; }
|
||||
public String getIslink() { return islink; }
|
||||
public void setIslink(String islink) { this.islink = islink; }
|
||||
public String getIsdisplay() { return isdisplay; }
|
||||
public void setIsdisplay(String isdisplay) { this.isdisplay = isdisplay; }
|
||||
public Integer getOrderno() { return orderno; }
|
||||
public void setOrderno(Integer orderno) { this.orderno = orderno; }
|
||||
public String getParentid() { return parentid; }
|
||||
public void setParentid(String parentid) { this.parentid = parentid; }
|
||||
public String getModuleId() { return moduleId; }
|
||||
public void setModuleId(String moduleId) { this.moduleId = moduleId; }
|
||||
public List<MenuTreeNode> getChildren() { return children; }
|
||||
public void setChildren(List<MenuTreeNode> children) { this.children = children; }
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单排序请求
|
||||
*/
|
||||
public static class MenuOrderRequest {
|
||||
private String id;
|
||||
private Integer orderno;
|
||||
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
public Integer getOrderno() { return orderno; }
|
||||
public void setOrderno(Integer orderno) { this.orderno = orderno; }
|
||||
}
|
||||
}
|
@ -0,0 +1,248 @@
|
||||
package com.stdproject.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.stdproject.common.OperationLog;
|
||||
import com.stdproject.common.PageRequest;
|
||||
import com.stdproject.common.Result;
|
||||
import com.stdproject.entity.AppOptLog;
|
||||
import com.stdproject.service.IAppOptLogService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用-操作日志表 前端控制器
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Tag(name = "操作日志管理", description = "操作日志查询、操作日志删除等功能")
|
||||
@RestController
|
||||
@RequestMapping("/api/optlog")
|
||||
public class AppOptLogController {
|
||||
|
||||
@Autowired
|
||||
private IAppOptLogService appOptLogService;
|
||||
|
||||
@Operation(summary = "分页查询操作日志列表")
|
||||
@PostMapping("/page")
|
||||
@OperationLog(type = "06", module = "操作日志管理", description = "分页查询操作日志列表")
|
||||
public Result<IPage<AppOptLog>> page(@RequestBody @Valid PageRequest pageRequest) {
|
||||
Page<AppOptLog> page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize());
|
||||
QueryWrapper<AppOptLog> queryWrapper = new QueryWrapper<>();
|
||||
|
||||
// 关键字搜索
|
||||
if (StringUtils.hasText(pageRequest.getKeyword())) {
|
||||
queryWrapper.and(wrapper -> wrapper
|
||||
.like("username", pageRequest.getKeyword())
|
||||
.or().like("module", pageRequest.getKeyword())
|
||||
.or().like("description", pageRequest.getKeyword())
|
||||
);
|
||||
}
|
||||
|
||||
// 排序
|
||||
if (StringUtils.hasText(pageRequest.getOrderBy())) {
|
||||
if ("asc".equalsIgnoreCase(pageRequest.getOrderDirection())) {
|
||||
queryWrapper.orderByAsc(pageRequest.getOrderBy());
|
||||
} else {
|
||||
queryWrapper.orderByDesc(pageRequest.getOrderBy());
|
||||
}
|
||||
} else {
|
||||
queryWrapper.orderByDesc("logtime");
|
||||
}
|
||||
|
||||
IPage<AppOptLog> result = appOptLogService.page(page, queryWrapper);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据ID查询操作日志详情")
|
||||
@GetMapping("/{id}")
|
||||
@OperationLog(type = "06", module = "操作日志管理", description = "查询操作日志详情")
|
||||
public Result<AppOptLog> getById(@Parameter(description = "日志ID") @PathVariable String id) {
|
||||
AppOptLog optLog = appOptLogService.getById(id);
|
||||
return Result.success(optLog);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据用户名查询操作日志")
|
||||
@GetMapping("/user/{username}")
|
||||
@OperationLog(type = "06", module = "操作日志管理", description = "根据用户名查询操作日志")
|
||||
public Result<List<AppOptLog>> getByUsername(
|
||||
@Parameter(description = "用户名") @PathVariable String username,
|
||||
@Parameter(description = "限制条数") @RequestParam(defaultValue = "100") Integer limit) {
|
||||
QueryWrapper<AppOptLog> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("username", username);
|
||||
queryWrapper.orderByDesc("logtime");
|
||||
queryWrapper.last("LIMIT " + limit);
|
||||
|
||||
List<AppOptLog> logs = appOptLogService.list(queryWrapper);
|
||||
return Result.success(logs);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据操作类型查询操作日志")
|
||||
@GetMapping("/type/{type}")
|
||||
@OperationLog(type = "06", module = "操作日志管理", description = "根据操作类型查询操作日志")
|
||||
public Result<List<AppOptLog>> getByType(
|
||||
@Parameter(description = "操作类型") @PathVariable String type,
|
||||
@Parameter(description = "限制条数") @RequestParam(defaultValue = "100") Integer limit) {
|
||||
QueryWrapper<AppOptLog> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("type", type);
|
||||
queryWrapper.orderByDesc("logtime");
|
||||
queryWrapper.last("LIMIT " + limit);
|
||||
|
||||
List<AppOptLog> logs = appOptLogService.list(queryWrapper);
|
||||
return Result.success(logs);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据模块查询操作日志")
|
||||
@GetMapping("/module/{module}")
|
||||
@OperationLog(type = "06", module = "操作日志管理", description = "根据模块查询操作日志")
|
||||
public Result<List<AppOptLog>> getByModule(
|
||||
@Parameter(description = "模块名称") @PathVariable String module,
|
||||
@Parameter(description = "限制条数") @RequestParam(defaultValue = "100") Integer limit) {
|
||||
QueryWrapper<AppOptLog> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("module", module);
|
||||
queryWrapper.orderByDesc("opttime");
|
||||
queryWrapper.last("LIMIT " + limit);
|
||||
|
||||
List<AppOptLog> logs = appOptLogService.list(queryWrapper);
|
||||
return Result.success(logs);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据时间范围查询操作日志")
|
||||
@GetMapping("/time-range")
|
||||
@OperationLog(type = "06", module = "操作日志管理", description = "根据时间范围查询操作日志")
|
||||
public Result<List<AppOptLog>> getByTimeRange(
|
||||
@Parameter(description = "开始时间") @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
|
||||
@Parameter(description = "结束时间") @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime,
|
||||
@Parameter(description = "限制条数") @RequestParam(defaultValue = "1000") Integer limit) {
|
||||
QueryWrapper<AppOptLog> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.between("opttime", startTime, endTime);
|
||||
queryWrapper.orderByDesc("opttime");
|
||||
queryWrapper.last("LIMIT " + limit);
|
||||
|
||||
List<AppOptLog> logs = appOptLogService.list(queryWrapper);
|
||||
return Result.success(logs);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据IP地址查询操作日志")
|
||||
@GetMapping("/ip/{ip}")
|
||||
@OperationLog(type = "06", module = "操作日志管理", description = "根据IP地址查询操作日志")
|
||||
public Result<List<AppOptLog>> getByIp(
|
||||
@Parameter(description = "IP地址") @PathVariable String ip,
|
||||
@Parameter(description = "限制条数") @RequestParam(defaultValue = "100") Integer limit) {
|
||||
QueryWrapper<AppOptLog> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("ip", ip);
|
||||
queryWrapper.orderByDesc("opttime");
|
||||
queryWrapper.last("LIMIT " + limit);
|
||||
|
||||
List<AppOptLog> logs = appOptLogService.list(queryWrapper);
|
||||
return Result.success(logs);
|
||||
}
|
||||
|
||||
|
||||
@Operation(summary = "删除操作日志")
|
||||
@DeleteMapping("/{id}")
|
||||
@OperationLog(type = "03", module = "操作日志管理", description = "删除操作日志")
|
||||
public Result<String> delete(@Parameter(description = "日志ID") @PathVariable String id) {
|
||||
boolean success = appOptLogService.removeById(id);
|
||||
return success ? Result.success("删除成功") : Result.error("删除失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "批量删除操作日志")
|
||||
@DeleteMapping("/batch")
|
||||
@OperationLog(type = "03", module = "操作日志管理", description = "批量删除操作日志")
|
||||
public Result<String> deleteBatch(@RequestBody List<String> ids) {
|
||||
boolean success = appOptLogService.removeByIds(ids);
|
||||
return success ? Result.success("批量删除成功") : Result.error("批量删除失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "清理指定天数前的操作日志")
|
||||
@DeleteMapping("/clean/{days}")
|
||||
@OperationLog(type = "03", module = "操作日志管理", description = "清理历史操作日志")
|
||||
public Result<String> cleanOldLogs(@Parameter(description = "保留天数") @PathVariable Integer days) {
|
||||
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(days);
|
||||
QueryWrapper<AppOptLog> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.lt("logtime", cutoffTime);
|
||||
|
||||
boolean success = appOptLogService.remove(queryWrapper);
|
||||
return success ? Result.success("清理成功") : Result.error("清理失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "获取操作日志统计信息")
|
||||
@GetMapping("/statistics")
|
||||
@OperationLog(type = "06", module = "操作日志管理", description = "获取操作日志统计信息")
|
||||
public Result<LogStatistics> getStatistics() {
|
||||
// 总日志数
|
||||
long totalCount = appOptLogService.count();
|
||||
|
||||
// 今日日志数
|
||||
LocalDateTime todayStart = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0).withNano(0);
|
||||
QueryWrapper<AppOptLog> todayQuery = new QueryWrapper<>();
|
||||
todayQuery.ge("opttime", todayStart);
|
||||
long todayCount = appOptLogService.count(todayQuery);
|
||||
|
||||
// 本周日志数
|
||||
LocalDateTime weekStart = LocalDateTime.now().minusDays(7);
|
||||
QueryWrapper<AppOptLog> weekQuery = new QueryWrapper<>();
|
||||
weekQuery.ge("logtime", weekStart);
|
||||
long weekCount = appOptLogService.count(weekQuery);
|
||||
|
||||
// 本月日志数
|
||||
LocalDateTime monthStart = LocalDateTime.now().minusDays(30);
|
||||
QueryWrapper<AppOptLog> monthQuery = new QueryWrapper<>();
|
||||
monthQuery.ge("logtime", monthStart);
|
||||
long monthCount = appOptLogService.count(monthQuery);
|
||||
|
||||
LogStatistics statistics = new LogStatistics();
|
||||
statistics.setTotalCount(totalCount);
|
||||
statistics.setTodayCount(todayCount);
|
||||
statistics.setWeekCount(weekCount);
|
||||
statistics.setMonthCount(monthCount);
|
||||
|
||||
return Result.success(statistics);
|
||||
}
|
||||
|
||||
@Operation(summary = "获取最近的操作日志")
|
||||
@GetMapping("/recent")
|
||||
@OperationLog(type = "06", module = "操作日志管理", description = "获取最近的操作日志")
|
||||
public Result<List<AppOptLog>> getRecentLogs(
|
||||
@Parameter(description = "限制条数") @RequestParam(defaultValue = "50") Integer limit) {
|
||||
QueryWrapper<AppOptLog> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.orderByDesc("logtime");
|
||||
queryWrapper.last("LIMIT " + limit);
|
||||
|
||||
List<AppOptLog> logs = appOptLogService.list(queryWrapper);
|
||||
return Result.success(logs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志统计信息
|
||||
*/
|
||||
public static class LogStatistics {
|
||||
private Long totalCount;
|
||||
private Long todayCount;
|
||||
private Long weekCount;
|
||||
private Long monthCount;
|
||||
|
||||
public Long getTotalCount() { return totalCount; }
|
||||
public void setTotalCount(Long totalCount) { this.totalCount = totalCount; }
|
||||
public Long getTodayCount() { return todayCount; }
|
||||
public void setTodayCount(Long todayCount) { this.todayCount = todayCount; }
|
||||
public Long getWeekCount() { return weekCount; }
|
||||
public void setWeekCount(Long weekCount) { this.weekCount = weekCount; }
|
||||
public Long getMonthCount() { return monthCount; }
|
||||
public void setMonthCount(Long monthCount) { this.monthCount = monthCount; }
|
||||
}
|
||||
}
|
@ -0,0 +1,221 @@
|
||||
package com.stdproject.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.stdproject.common.OperationLog;
|
||||
import com.stdproject.common.Result;
|
||||
import com.stdproject.entity.AppOrganization;
|
||||
import com.stdproject.service.IAppOrganizationService;
|
||||
import com.stdproject.service.IAppUserService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用-组织机构 前端控制器
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Tag(name = "组织管理", description = "组织添加、组织修改、组织删除、组织查询等功能,支持公司和部门两级树形结构")
|
||||
@RestController
|
||||
@RequestMapping("/api/org")
|
||||
public class AppOrganizationController {
|
||||
|
||||
@Autowired
|
||||
private IAppOrganizationService appOrganizationService;
|
||||
// 需要在类开头注入 IAppUserService
|
||||
@Autowired
|
||||
private IAppUserService appUserService;
|
||||
|
||||
@Operation(summary = "查询公司结构树")
|
||||
@GetMapping("/getCompanyTree")
|
||||
@OperationLog(type = "06", module = "组织管理", description = "查询公司结构树")
|
||||
public Result<List<AppOrganization>> getCompanyTree() {
|
||||
QueryWrapper<AppOrganization> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("orgtype", "01");
|
||||
queryWrapper.eq("isvaild", "1");
|
||||
queryWrapper.orderByAsc("orgcode");
|
||||
List<AppOrganization> children = appOrganizationService.list(queryWrapper);
|
||||
return Result.success(children);
|
||||
}
|
||||
|
||||
@Operation(summary = "查询部门列表")
|
||||
@GetMapping("/getDepartmentList")
|
||||
@OperationLog(type = "06", module = "组织管理", description = "查询部门列表")
|
||||
public Result<List<AppOrganization>> getDepartmentList(@RequestParam String parentid,@RequestParam String keystr) {
|
||||
QueryWrapper<AppOrganization> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("orgtype", "02");
|
||||
queryWrapper.eq("parentid", parentid);
|
||||
queryWrapper.eq("isvaild", "1");
|
||||
if (StringUtils.hasText(keystr)) {
|
||||
queryWrapper.and(wrapper -> wrapper
|
||||
.like("orgname", keystr)
|
||||
.or().like("orgcode", keystr)
|
||||
.or().like("description", keystr)
|
||||
);
|
||||
}
|
||||
queryWrapper.orderByAsc("orgcode");
|
||||
List<AppOrganization> children = appOrganizationService.list(queryWrapper);
|
||||
return Result.success(children);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Operation(summary = "根据ID查询组织详情")
|
||||
@GetMapping("getDetailById/{id}")
|
||||
@OperationLog(type = "06", module = "组织管理", description = "根据ID查询组织详情")
|
||||
public Result<AppOrganization> getDetailById(@PathVariable String id) {
|
||||
AppOrganization organization = appOrganizationService.getById(id);
|
||||
return Result.success(organization);
|
||||
}
|
||||
|
||||
|
||||
@Operation(summary = "新增组织")
|
||||
@PostMapping("add")
|
||||
@OperationLog(type = "01", module = "组织管理", description = "新增组织")
|
||||
public Result<String> add(@RequestBody @Valid AppOrganization appOrganization) {
|
||||
// 设置默认值
|
||||
//生成新的组织编号 公司编号:010101 部门编号:0001
|
||||
appOrganization.setOrgcode(getNewOrgCode(appOrganization.getOrgtype(), appOrganization.getParentid()));
|
||||
//设置状态为有效
|
||||
appOrganization.setIsvaild("1");
|
||||
// 从当前登录用户上下文中获取用户名
|
||||
appOrganization.setLastmodifier(appUserService.getCurrentUsername());
|
||||
appOrganization.setLastmodifydate(LocalDateTime.now());
|
||||
boolean success = appOrganizationService.save(appOrganization);
|
||||
return success ? Result.success("新增成功") : Result.error("新增失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "修改组织")
|
||||
@PostMapping("update")
|
||||
@OperationLog(type = "02", module = "组织管理", description = "修改组织")
|
||||
public Result<String> update(@RequestBody @Valid AppOrganization appOrganization) {
|
||||
// 检查组织是否存在
|
||||
AppOrganization existOrg = appOrganizationService.getById(appOrganization.getId());
|
||||
if (existOrg == null) {
|
||||
return Result.error("组织不存在");
|
||||
}
|
||||
// 验证父级组织变更的合法性
|
||||
if (StringUtils.hasText(appOrganization.getParentid()) && !"0".equals(appOrganization.getParentid())) {
|
||||
// 不能将自己设为父级
|
||||
if (appOrganization.getId().equals(appOrganization.getParentid())) {
|
||||
return Result.error("不能将自己设为父级组织");
|
||||
}
|
||||
// 验证父级组织是否存在
|
||||
AppOrganization parent = appOrganizationService.getById(appOrganization.getParentid());
|
||||
if (parent == null || !"1".equals(parent.getIsvaild())) {
|
||||
return Result.error("父级组织不存在或已禁用");
|
||||
}
|
||||
|
||||
if ("02".equals(parent.getOrgtype())) {
|
||||
return Result.error("部门下不能有子组织");
|
||||
}
|
||||
}
|
||||
appOrganization.setLastmodifier(appUserService.getCurrentUsername());
|
||||
appOrganization.setLastmodifydate(LocalDateTime.now());
|
||||
boolean success = appOrganizationService.updateById(appOrganization);
|
||||
return success ? Result.success("修改成功") : Result.error("修改失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "删除组织")
|
||||
@PostMapping("delete/{id}")
|
||||
@OperationLog(type = "03", module = "组织管理", description = "删除组织")
|
||||
public Result<String> delete(@PathVariable String id) {
|
||||
boolean success = appOrganizationService.removeById(id);
|
||||
return success ? Result.success("删除成功") : Result.error("删除失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据组织类型和父节点生成新的组织编码
|
||||
*
|
||||
* @param orgType 组织类型:"01"表示公司,"02"表示部门
|
||||
* @param parentId 父节点ID,新增部门时使用
|
||||
* @return 新的组织编码
|
||||
*/
|
||||
private String getNewOrgCode(String orgType, String parentId) {
|
||||
if ("01".equals(orgType)) {
|
||||
// 公司类型:查找当前最大的公司编码并递增最后一级
|
||||
AppOrganization parentOrg = null;
|
||||
if (StringUtils.hasText(parentId) && !"0".equals(parentId)) {
|
||||
parentOrg = appOrganizationService.getById(parentId);
|
||||
if (parentOrg == null || !"1".equals(parentOrg.getIsvaild())) {
|
||||
throw new IllegalArgumentException("无效的父级组织");
|
||||
}
|
||||
}
|
||||
|
||||
// 获取父节点编码(顶级为“01”)
|
||||
String parentCode = parentOrg != null ? parentOrg.getOrgcode() : "01";
|
||||
|
||||
// 获取当前父节点下的最大子节点编码
|
||||
String maxCompanyCode = getMaxCodeByParentId("01",parentId);
|
||||
|
||||
int nextLevel = 1;
|
||||
if (maxCompanyCode != null && !maxCompanyCode.isEmpty()) {
|
||||
try {
|
||||
// 提取最后两位
|
||||
String lastTwo = maxCompanyCode.substring(maxCompanyCode.length() - 2);
|
||||
nextLevel = Integer.parseInt(lastTwo) + 1;
|
||||
} catch (Exception e) {
|
||||
nextLevel = 1; // 出错则重置为1
|
||||
}
|
||||
}
|
||||
|
||||
if (nextLevel > 99) {
|
||||
throw new RuntimeException("公司编码已达上限,请联系管理员处理");
|
||||
}
|
||||
|
||||
return parentCode + String.format("%02d", nextLevel);
|
||||
} else if ("02".equals(orgType) && StringUtils.hasText(parentId)) {
|
||||
// 部门类型:查找该公司下最大部门编码并递增
|
||||
AppOrganization parentOrg = appOrganizationService.getById(parentId);
|
||||
if (parentOrg == null || !"1".equals(parentOrg.getIsvaild())) {
|
||||
throw new IllegalArgumentException("无效的父级组织");
|
||||
}
|
||||
|
||||
// 获取该父节点下最大部门编号
|
||||
String maxDeptCodeStr = getMaxCodeByParentId("02",parentId);
|
||||
int nextSeq = 1;
|
||||
|
||||
if (maxDeptCodeStr != null && !maxDeptCodeStr.isEmpty()) {
|
||||
try {
|
||||
nextSeq = Integer.parseInt(maxDeptCodeStr) + 1;
|
||||
} catch (NumberFormatException e) {
|
||||
nextSeq = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextSeq > 9999) {
|
||||
throw new RuntimeException("部门编码已达上限,请联系管理员处理");
|
||||
}
|
||||
|
||||
return String.format("%04d", nextSeq);
|
||||
} else {
|
||||
throw new IllegalArgumentException("无法生成组织编码:参数不完整");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String getMaxCodeByParentId(String orgType,String parentId) {
|
||||
LambdaQueryWrapper<AppOrganization> queryWrapper = new LambdaQueryWrapper<AppOrganization>()
|
||||
.eq(AppOrganization::getOrgtype, orgType)
|
||||
.eq(AppOrganization::getParentid, parentId)
|
||||
.eq(AppOrganization::getIsvaild, "1")
|
||||
.orderByDesc(AppOrganization::getOrgcode)
|
||||
.select(AppOrganization::getOrgcode)
|
||||
.last("LIMIT 1");
|
||||
// 使用selectList方法获取符合条件的记录列表
|
||||
List<AppOrganization> list = appOrganizationService.list(queryWrapper);
|
||||
// 如果列表不为空,返回第一个记录的orgcode
|
||||
return list.isEmpty() ? null : list.get(0).getOrgcode();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,320 @@
|
||||
package com.stdproject.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.stdproject.common.OperationLog;
|
||||
import com.stdproject.common.PageRequest;
|
||||
import com.stdproject.common.Result;
|
||||
import com.stdproject.entity.AppRole;
|
||||
import com.stdproject.entity.AppRoleMenu;
|
||||
import com.stdproject.entity.AppRoleUser;
|
||||
import com.stdproject.service.IAppRoleMenuService;
|
||||
import com.stdproject.service.IAppRoleService;
|
||||
import com.stdproject.service.IAppRoleUserService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用-角色表 前端控制器
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Tag(name = "角色管理", description = "角色添加、角色修改、角色删除、角色查询,角色权限分配等功能")
|
||||
@RestController
|
||||
@RequestMapping("/api/role")
|
||||
public class AppRoleController {
|
||||
|
||||
@Autowired
|
||||
private IAppRoleService appRoleService;
|
||||
|
||||
@Autowired
|
||||
private IAppRoleMenuService appRoleMenuService;
|
||||
|
||||
@Autowired
|
||||
private IAppRoleUserService appRoleUserService;
|
||||
|
||||
@Operation(summary = "分页查询角色列表")
|
||||
@PostMapping("/page")
|
||||
@OperationLog(type = "06", module = "角色管理", description = "分页查询角色列表")
|
||||
public Result<IPage<AppRole>> page(@RequestBody @Valid PageRequest pageRequest) {
|
||||
Page<AppRole> page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize());
|
||||
QueryWrapper<AppRole> queryWrapper = new QueryWrapper<>();
|
||||
|
||||
// 关键字搜索
|
||||
if (StringUtils.hasText(pageRequest.getKeyword())) {
|
||||
queryWrapper.and(wrapper -> wrapper
|
||||
.like("rolename", pageRequest.getKeyword())
|
||||
.or().like("rolecode", pageRequest.getKeyword())
|
||||
.or().like("description", pageRequest.getKeyword())
|
||||
);
|
||||
}
|
||||
|
||||
// 只查询有效的角色
|
||||
queryWrapper.eq("isvaild", "1");
|
||||
|
||||
// 排序
|
||||
if (StringUtils.hasText(pageRequest.getOrderBy())) {
|
||||
if ("asc".equalsIgnoreCase(pageRequest.getOrderDirection())) {
|
||||
queryWrapper.orderByAsc(pageRequest.getOrderBy());
|
||||
} else {
|
||||
queryWrapper.orderByDesc(pageRequest.getOrderBy());
|
||||
}
|
||||
} else {
|
||||
queryWrapper.orderByAsc("rolecode");
|
||||
}
|
||||
|
||||
IPage<AppRole> result = appRoleService.page(page, queryWrapper);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@Operation(summary = "查询所有有效角色列表")
|
||||
@GetMapping("/list")
|
||||
@OperationLog(type = "06", module = "角色管理", description = "查询所有有效角色列表")
|
||||
public Result<List<AppRole>> list() {
|
||||
QueryWrapper<AppRole> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("isvaild", "1");
|
||||
queryWrapper.orderByAsc("rolecode");
|
||||
|
||||
List<AppRole> roles = appRoleService.list(queryWrapper);
|
||||
return Result.success(roles);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据ID查询角色详情")
|
||||
@GetMapping("/{id}")
|
||||
@OperationLog(type = "06", module = "角色管理", description = "查询角色详情")
|
||||
public Result<AppRole> getById(@Parameter(description = "角色ID") @PathVariable String id) {
|
||||
AppRole role = appRoleService.getById(id);
|
||||
return Result.success(role);
|
||||
}
|
||||
@Operation(summary = "新增角色")
|
||||
@PostMapping
|
||||
@OperationLog(type = "01", module = "角色管理", description = "新增角色")
|
||||
public Result<String> save(@RequestBody @Valid AppRole appRole) {
|
||||
// 检查角色名称是否已存在
|
||||
QueryWrapper<AppRole> nameQuery = new QueryWrapper<>();
|
||||
nameQuery.eq("rolename", appRole.getRolename());
|
||||
nameQuery.eq("isvaild", "1");
|
||||
AppRole existRoleName = appRoleService.getOne(nameQuery);
|
||||
if (ObjectUtils.isNotEmpty(existRoleName)) {
|
||||
return Result.error("角色名称已存在");
|
||||
}
|
||||
// 自动生成角色编号
|
||||
String maxRoleCode = getMaxRoleCode();
|
||||
String newRoleCode;
|
||||
if (maxRoleCode == null) {
|
||||
newRoleCode = "001";
|
||||
} else {
|
||||
int nextCode = Integer.parseInt(maxRoleCode) + 1;
|
||||
newRoleCode = String.format("%03d", nextCode);
|
||||
}
|
||||
appRole.setRolecode(newRoleCode);
|
||||
// 设置默认值
|
||||
appRole.setIsvaild("1");
|
||||
appRole.setLastmodifier("admin");
|
||||
appRole.setLastmodifydate(LocalDateTime.now());
|
||||
boolean success = appRoleService.save(appRole);
|
||||
return success ? Result.success("新增成功") : Result.error("新增失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最大的角色编号
|
||||
* @return 最大角色编号
|
||||
*/
|
||||
private String getMaxRoleCode() {
|
||||
LambdaQueryWrapper<AppRole> queryWrapper = new LambdaQueryWrapper<AppRole>()
|
||||
.eq(AppRole::getIsvaild, "1")
|
||||
.orderByDesc(AppRole::getRolecode)
|
||||
.select(AppRole::getRolecode)
|
||||
.last("LIMIT 1");
|
||||
List<AppRole> list = appRoleService.list(queryWrapper);
|
||||
return list.isEmpty() ? null : list.get(0).getRolecode();
|
||||
}
|
||||
|
||||
@Operation(summary = "修改角色")
|
||||
@PutMapping
|
||||
@OperationLog(type = "02", module = "角色管理", description = "修改角色")
|
||||
public Result<String> update(@RequestBody @Valid AppRole appRole) {
|
||||
// 检查角色是否存在
|
||||
AppRole existRole = appRoleService.getById(appRole.getId());
|
||||
if (existRole == null) {
|
||||
return Result.error("角色不存在");
|
||||
}
|
||||
|
||||
// 如果修改了角色名称,检查新名称是否已被其他角色使用
|
||||
if (!existRole.getRolename().equals(appRole.getRolename())) {
|
||||
QueryWrapper<AppRole> nameQuery = new QueryWrapper<>();
|
||||
nameQuery.eq("rolename", appRole.getRolename());
|
||||
nameQuery.eq("isvaild", "1");
|
||||
nameQuery.ne("id", appRole.getId());
|
||||
AppRole roleWithSameName = appRoleService.getOne(nameQuery);
|
||||
if (roleWithSameName != null) {
|
||||
return Result.error("角色名称已被其他角色使用");
|
||||
}
|
||||
}
|
||||
appRole.setLastmodifier("admin");
|
||||
appRole.setLastmodifydate(LocalDateTime.now());
|
||||
boolean success = appRoleService.updateById(appRole);
|
||||
return success ? Result.success("修改成功") : Result.error("修改失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "删除角色")
|
||||
@DeleteMapping("/{id}")
|
||||
@OperationLog(type = "03", module = "角色管理", description = "删除角色")
|
||||
public Result<String> delete(@Parameter(description = "角色ID") @PathVariable String id) {
|
||||
// 检查是否有用户关联此角色
|
||||
QueryWrapper<AppRoleUser> userQuery = new QueryWrapper<>();
|
||||
userQuery.eq("roleid", id);
|
||||
long userCount = appRoleUserService.count(userQuery);
|
||||
if (userCount > 0) {
|
||||
return Result.error("存在用户关联此角色,无法删除");
|
||||
}
|
||||
|
||||
// 软删除:设置为无效
|
||||
AppRole role = appRoleService.getById(id);
|
||||
if (role != null) {
|
||||
role.setIsvaild("0");
|
||||
role.setLastmodifydate(LocalDateTime.now());
|
||||
boolean success = appRoleService.updateById(role);
|
||||
|
||||
if (success) {
|
||||
// 同时删除角色菜单关联
|
||||
QueryWrapper<AppRoleMenu> menuQuery = new QueryWrapper<>();
|
||||
menuQuery.eq("roleid", id);
|
||||
appRoleMenuService.remove(menuQuery);
|
||||
}
|
||||
|
||||
return success ? Result.success("删除成功") : Result.error("删除失败");
|
||||
}
|
||||
|
||||
return Result.error("角色不存在");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Operation(summary = "根据角色类型查询角色列表")
|
||||
@GetMapping("/type/{type}")
|
||||
@OperationLog(type = "06", module = "角色管理", description = "根据角色类型查询角色列表")
|
||||
public Result<List<AppRole>> getByType(@Parameter(description = "角色类型:1-应用管理员,2-应用普通用户") @PathVariable String type) {
|
||||
QueryWrapper<AppRole> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("type", type);
|
||||
queryWrapper.eq("isvaild", "1");
|
||||
queryWrapper.orderByAsc("rolecode");
|
||||
List<AppRole> roles = appRoleService.list(queryWrapper);
|
||||
return Result.success(roles);
|
||||
}
|
||||
|
||||
@Operation(summary = "分配角色菜单权限")
|
||||
@PostMapping("/assign-menus/{roleId}")
|
||||
@OperationLog(type = "02", module = "角色管理", description = "分配角色菜单权限")
|
||||
public Result<String> assignMenus(
|
||||
@Parameter(description = "角色ID") @PathVariable String roleId,
|
||||
@RequestBody List<String> menuIds) {
|
||||
|
||||
// 检查角色是否存在
|
||||
AppRole role = appRoleService.getById(roleId);
|
||||
if (role == null || !"1".equals(role.getIsvaild())) {
|
||||
return Result.error("角色不存在或已禁用");
|
||||
}
|
||||
|
||||
// 先删除原有的菜单权限
|
||||
QueryWrapper<AppRoleMenu> deleteQuery = new QueryWrapper<>();
|
||||
deleteQuery.eq("roleid", roleId);
|
||||
appRoleMenuService.remove(deleteQuery);
|
||||
|
||||
// 添加新的菜单权限
|
||||
if (menuIds != null && !menuIds.isEmpty()) {
|
||||
List<AppRoleMenu> roleMenus = menuIds.stream().map(menuId -> {
|
||||
AppRoleMenu roleMenu = new AppRoleMenu();
|
||||
roleMenu.setRoleid(roleId);
|
||||
roleMenu.setMenuid(menuId);
|
||||
roleMenu.setAppId(role.getAppId());
|
||||
return roleMenu;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
boolean success = appRoleMenuService.saveBatch(roleMenus);
|
||||
return success ? Result.success("权限分配成功") : Result.error("权限分配失败");
|
||||
}
|
||||
|
||||
return Result.success("权限分配成功");
|
||||
}
|
||||
|
||||
@Operation(summary = "获取角色的菜单权限")
|
||||
@GetMapping("/menus/{roleId}")
|
||||
@OperationLog(type = "06", module = "角色管理", description = "获取角色的菜单权限")
|
||||
public Result<List<String>> getRoleMenus(@Parameter(description = "角色ID") @PathVariable String roleId) {
|
||||
QueryWrapper<AppRoleMenu> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("roleid", roleId);
|
||||
|
||||
List<AppRoleMenu> roleMenus = appRoleMenuService.list(queryWrapper);
|
||||
List<String> menuIds = roleMenus.stream()
|
||||
.map(AppRoleMenu::getMenuid)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Result.success(menuIds);
|
||||
}
|
||||
|
||||
@Operation(summary = "分配用户角色")
|
||||
@PostMapping("/assign-users/{roleId}")
|
||||
@OperationLog(type = "02", module = "角色管理", description = "分配用户角色")
|
||||
public Result<String> assignUsers(
|
||||
@Parameter(description = "角色ID") @PathVariable String roleId,
|
||||
@RequestBody List<String> userIds) {
|
||||
|
||||
// 检查角色是否存在
|
||||
AppRole role = appRoleService.getById(roleId);
|
||||
if (role == null || !"1".equals(role.getIsvaild())) {
|
||||
return Result.error("角色不存在或已禁用");
|
||||
}
|
||||
|
||||
// 先删除原有的用户角色关联
|
||||
QueryWrapper<AppRoleUser> deleteQuery = new QueryWrapper<>();
|
||||
deleteQuery.eq("roleid", roleId);
|
||||
appRoleUserService.remove(deleteQuery);
|
||||
|
||||
// 添加新的用户角色关联
|
||||
if (userIds != null && !userIds.isEmpty()) {
|
||||
List<AppRoleUser> roleUsers = userIds.stream().map(userId -> {
|
||||
AppRoleUser roleUser = new AppRoleUser();
|
||||
roleUser.setRoleid(roleId);
|
||||
roleUser.setUserid(userId);
|
||||
roleUser.setAppId(role.getAppId());
|
||||
return roleUser;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
boolean success = appRoleUserService.saveBatch(roleUsers);
|
||||
return success ? Result.success("用户分配成功") : Result.error("用户分配失败");
|
||||
}
|
||||
|
||||
return Result.success("用户分配成功");
|
||||
}
|
||||
|
||||
@Operation(summary = "获取角色的用户列表")
|
||||
@GetMapping("/users/{roleId}")
|
||||
@OperationLog(type = "06", module = "角色管理", description = "获取角色的用户列表")
|
||||
public Result<List<String>> getRoleUsers(@Parameter(description = "角色ID") @PathVariable String roleId) {
|
||||
QueryWrapper<AppRoleUser> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("roleid", roleId);
|
||||
|
||||
List<AppRoleUser> roleUsers = appRoleUserService.list(queryWrapper);
|
||||
List<String> userIds = roleUsers.stream()
|
||||
.map(AppRoleUser::getUserid)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Result.success(userIds);
|
||||
}
|
||||
}
|
@ -0,0 +1,277 @@
|
||||
package com.stdproject.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.stdproject.common.OperationLog;
|
||||
import com.stdproject.common.PageRequest;
|
||||
import com.stdproject.common.Result;
|
||||
import com.stdproject.entity.AppUser;
|
||||
import com.stdproject.service.IAppUserService;
|
||||
import com.stdproject.utils.PasswordUtils;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用-用户表 前端控制器
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Tag(name = "用户管理", description = "用户信息维护、用户角色分配、用户登录、用户修改密码、用户修改个人信息等功能")
|
||||
@RestController
|
||||
@RequestMapping("/app-user")
|
||||
public class AppUserController {
|
||||
|
||||
@Autowired
|
||||
private IAppUserService appUserService;
|
||||
|
||||
@Operation(summary = "分页查询用户列表")
|
||||
@PostMapping("/page")
|
||||
@OperationLog(type = "06", module = "用户管理", description = "分页查询用户列表")
|
||||
public Result<IPage<AppUser>> page(@RequestBody @Valid PageRequest pageRequest) {
|
||||
Page<AppUser> page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize());
|
||||
QueryWrapper<AppUser> queryWrapper = new QueryWrapper<>();
|
||||
|
||||
// 关键字搜索
|
||||
if (StringUtils.hasText(pageRequest.getKeyword())) {
|
||||
queryWrapper.and(wrapper -> wrapper
|
||||
.like("username", pageRequest.getKeyword())
|
||||
.or().like("nickname", pageRequest.getKeyword())
|
||||
.or().like("email", pageRequest.getKeyword())
|
||||
.or().like("phone", pageRequest.getKeyword())
|
||||
);
|
||||
}
|
||||
|
||||
// 排序
|
||||
if (StringUtils.hasText(pageRequest.getOrderBy())) {
|
||||
if ("asc".equalsIgnoreCase(pageRequest.getOrderDirection())) {
|
||||
queryWrapper.orderByAsc(pageRequest.getOrderBy());
|
||||
} else {
|
||||
queryWrapper.orderByDesc(pageRequest.getOrderBy());
|
||||
}
|
||||
} else {
|
||||
queryWrapper.orderByDesc("lastmodifydate");
|
||||
}
|
||||
|
||||
IPage<AppUser> result = appUserService.page(page, queryWrapper);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据ID查询用户详情")
|
||||
@GetMapping("/{id}")
|
||||
@OperationLog(type = "06", module = "用户管理", description = "查询用户详情")
|
||||
public Result<AppUser> getById(@Parameter(description = "用户ID") @PathVariable String id) {
|
||||
AppUser user = appUserService.getById(id);
|
||||
if (user != null) {
|
||||
// 不返回密码信息
|
||||
user.setPassword(null);
|
||||
}
|
||||
return Result.success(user);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据用户名查询用户")
|
||||
@GetMapping("/username/{username}")
|
||||
@OperationLog(type = "06", module = "用户管理", description = "根据用户名查询用户")
|
||||
public Result<AppUser> getByUsername(@Parameter(description = "用户名") @PathVariable String username) {
|
||||
AppUser user = appUserService.findByUsername(username);
|
||||
if (user != null) {
|
||||
// 不返回密码信息
|
||||
user.setPassword(null);
|
||||
}
|
||||
return Result.success(user);
|
||||
}
|
||||
|
||||
@Operation(summary = "新增用户")
|
||||
@PostMapping
|
||||
@OperationLog(type = "01", module = "用户管理", description = "新增用户")
|
||||
public Result<String> save(@RequestBody @Valid AppUser appUser) {
|
||||
// 检查用户名是否已存在
|
||||
AppUser existUser = appUserService.findByUsername(appUser.getUsername());
|
||||
if (existUser != null) {
|
||||
return Result.error("用户名已存在");
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if (!StringUtils.hasText(appUser.getStatus())) {
|
||||
appUser.setStatus("1"); // 默认有效
|
||||
}
|
||||
if (appUser.getPwdvalidperiod() == null) {
|
||||
appUser.setPwdvalidperiod(90); // 默认90天
|
||||
}
|
||||
if (appUser.getFailednum() == null) {
|
||||
appUser.setFailednum(0);
|
||||
}
|
||||
|
||||
// 密码加密
|
||||
if (StringUtils.hasText(appUser.getPassword())) {
|
||||
appUser.setPassword(PasswordUtils.encodePassword(appUser.getPassword()));
|
||||
}
|
||||
|
||||
appUser.setLastmodifydate(LocalDateTime.now());
|
||||
boolean success = appUserService.save(appUser);
|
||||
return success ? Result.success("新增成功") : Result.error("新增失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "修改用户")
|
||||
@PutMapping
|
||||
@OperationLog(type = "02", module = "用户管理", description = "修改用户")
|
||||
public Result<String> update(@RequestBody @Valid AppUser appUser) {
|
||||
// 检查用户是否存在
|
||||
AppUser existUser = appUserService.getById(appUser.getId());
|
||||
if (existUser == null) {
|
||||
return Result.error("用户不存在");
|
||||
}
|
||||
|
||||
// 如果修改了用户名,检查新用户名是否已被其他用户使用
|
||||
if (!existUser.getUsername().equals(appUser.getUsername())) {
|
||||
AppUser userWithSameName = appUserService.findByUsername(appUser.getUsername());
|
||||
if (userWithSameName != null && !userWithSameName.getId().equals(appUser.getId())) {
|
||||
return Result.error("用户名已被其他用户使用");
|
||||
}
|
||||
}
|
||||
|
||||
// 如果传入了新密码,进行加密
|
||||
if (StringUtils.hasText(appUser.getPassword())) {
|
||||
appUser.setPassword(PasswordUtils.encodePassword(appUser.getPassword()));
|
||||
appUser.setPwdresettime(LocalDateTime.now());
|
||||
} else {
|
||||
// 如果没有传入密码,保持原密码不变
|
||||
appUser.setPassword(existUser.getPassword());
|
||||
}
|
||||
|
||||
appUser.setLastmodifydate(LocalDateTime.now());
|
||||
boolean success = appUserService.updateById(appUser);
|
||||
return success ? Result.success("修改成功") : Result.error("修改失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "删除用户")
|
||||
@DeleteMapping("/{id}")
|
||||
@OperationLog(type = "03", module = "用户管理", description = "删除用户")
|
||||
public Result<String> delete(@Parameter(description = "用户ID") @PathVariable String id) {
|
||||
boolean success = appUserService.removeById(id);
|
||||
return success ? Result.success("删除成功") : Result.error("删除失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "批量删除用户")
|
||||
@DeleteMapping("/batch")
|
||||
@OperationLog(type = "03", module = "用户管理", description = "批量删除用户")
|
||||
public Result<String> deleteBatch(@RequestBody List<String> ids) {
|
||||
boolean success = appUserService.removeByIds(ids);
|
||||
return success ? Result.success("批量删除成功") : Result.error("批量删除失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "修改用户密码")
|
||||
@PutMapping("/password/{id}")
|
||||
@OperationLog(type = "02", module = "用户管理", description = "修改用户密码")
|
||||
public Result<String> updatePassword(
|
||||
@Parameter(description = "用户ID") @PathVariable String id,
|
||||
@RequestBody PasswordUpdateRequest request) {
|
||||
AppUser user = appUserService.getById(id);
|
||||
if (user == null) {
|
||||
return Result.error("用户不存在");
|
||||
}
|
||||
|
||||
// 验证旧密码
|
||||
if (!PasswordUtils.matches(request.getOldPassword(), user.getPassword())) {
|
||||
return Result.error("原密码错误");
|
||||
}
|
||||
|
||||
// 更新新密码
|
||||
user.setPassword(PasswordUtils.encodePassword(request.getNewPassword()));
|
||||
user.setPwdresettime(LocalDateTime.now());
|
||||
user.setLastmodifydate(LocalDateTime.now());
|
||||
|
||||
boolean success = appUserService.updateById(user);
|
||||
return success ? Result.success("密码修改成功") : Result.error("密码修改失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "重置用户密码")
|
||||
@PutMapping("/reset-password/{id}")
|
||||
@OperationLog(type = "02", module = "用户管理", description = "重置用户密码")
|
||||
public Result<String> resetPassword(
|
||||
@Parameter(description = "用户ID") @PathVariable String id,
|
||||
@RequestBody PasswordResetRequest request) {
|
||||
AppUser user = appUserService.getById(id);
|
||||
if (user == null) {
|
||||
return Result.error("用户不存在");
|
||||
}
|
||||
|
||||
// 重置密码
|
||||
user.setPassword(PasswordUtils.encodePassword(request.getNewPassword()));
|
||||
user.setPwdresettime(LocalDateTime.now());
|
||||
user.setLastmodifydate(LocalDateTime.now());
|
||||
|
||||
boolean success = appUserService.updateById(user);
|
||||
return success ? Result.success("密码重置成功") : Result.error("密码重置失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "启用/禁用用户")
|
||||
@PutMapping("/status/{id}")
|
||||
@OperationLog(type = "02", module = "用户管理", description = "启用/禁用用户")
|
||||
public Result<String> updateStatus(
|
||||
@Parameter(description = "用户ID") @PathVariable String id,
|
||||
@RequestParam String status) {
|
||||
AppUser user = appUserService.getById(id);
|
||||
if (user == null) {
|
||||
return Result.error("用户不存在");
|
||||
}
|
||||
|
||||
user.setStatus(status);
|
||||
user.setLastmodifydate(LocalDateTime.now());
|
||||
|
||||
boolean success = appUserService.updateById(user);
|
||||
String message = "1".equals(status) ? "启用成功" : "禁用成功";
|
||||
return success ? Result.success(message) : Result.error("操作失败");
|
||||
}
|
||||
|
||||
@Operation(summary = "根据组织ID查询用户列表")
|
||||
@GetMapping("/org/{orgId}")
|
||||
@OperationLog(type = "06", module = "用户管理", description = "根据组织ID查询用户列表")
|
||||
public Result<List<AppUser>> getByOrgId(@Parameter(description = "组织ID") @PathVariable String orgId) {
|
||||
QueryWrapper<AppUser> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("orgid", orgId);
|
||||
queryWrapper.orderByDesc("lastmodifydate");
|
||||
|
||||
List<AppUser> users = appUserService.list(queryWrapper);
|
||||
// 不返回密码信息
|
||||
users.forEach(user -> user.setPassword(null));
|
||||
|
||||
return Result.success(users);
|
||||
}
|
||||
|
||||
/**
|
||||
* 密码修改请求类
|
||||
*/
|
||||
public static class PasswordUpdateRequest {
|
||||
private String oldPassword;
|
||||
private String newPassword;
|
||||
|
||||
// getters and setters
|
||||
public String getOldPassword() { return oldPassword; }
|
||||
public void setOldPassword(String oldPassword) { this.oldPassword = oldPassword; }
|
||||
public String getNewPassword() { return newPassword; }
|
||||
public void setNewPassword(String newPassword) { this.newPassword = newPassword; }
|
||||
}
|
||||
|
||||
/**
|
||||
* 密码重置请求类
|
||||
*/
|
||||
public static class PasswordResetRequest {
|
||||
private String newPassword;
|
||||
|
||||
// getters and setters
|
||||
public String getNewPassword() { return newPassword; }
|
||||
public void setNewPassword(String newPassword) { this.newPassword = newPassword; }
|
||||
}
|
||||
}
|
@ -0,0 +1,258 @@
|
||||
package com.stdproject.controller;
|
||||
|
||||
import com.stdproject.common.Constants;
|
||||
import com.stdproject.common.OperationLog;
|
||||
import com.stdproject.common.Result;
|
||||
import com.stdproject.entity.AppUser;
|
||||
import com.stdproject.service.IAppUserService;
|
||||
import com.stdproject.utils.CaptchaUtils;
|
||||
import com.stdproject.utils.JwtUtils;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 认证控制器
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Tag(name = "认证管理", description = "用户登录、登出、验证码等认证相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
@Slf4j
|
||||
public class AuthController {
|
||||
|
||||
@Autowired
|
||||
private AuthenticationManager authenticationManager;
|
||||
|
||||
@Autowired
|
||||
private JwtUtils jwtUtils;
|
||||
|
||||
@Autowired
|
||||
private CaptchaUtils captchaUtils;
|
||||
|
||||
@Autowired
|
||||
private IAppUserService appUserService;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 登录请求类
|
||||
*/
|
||||
public static class LoginRequest {
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
|
||||
@NotBlank(message = "密码不能为空")
|
||||
private String password;
|
||||
|
||||
@NotBlank(message = "验证码不能为空")
|
||||
private String captcha;
|
||||
|
||||
@NotBlank(message = "验证码Key不能为空")
|
||||
private String captchaKey;
|
||||
|
||||
// Getters and Setters
|
||||
public String getUsername() { return username; }
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
public String getPassword() { return password; }
|
||||
public void setPassword(String password) { this.password = password; }
|
||||
public String getCaptcha() { return captcha; }
|
||||
public void setCaptcha(String captcha) { this.captcha = captcha; }
|
||||
public String getCaptchaKey() { return captchaKey; }
|
||||
public void setCaptchaKey(String captchaKey) { this.captchaKey = captchaKey; }
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码请求类
|
||||
*/
|
||||
public static class ChangePasswordRequest {
|
||||
@NotBlank(message = "原密码不能为空")
|
||||
private String oldPassword;
|
||||
|
||||
@NotBlank(message = "新密码不能为空")
|
||||
private String newPassword;
|
||||
|
||||
// Getters and Setters
|
||||
public String getOldPassword() { return oldPassword; }
|
||||
public void setOldPassword(String oldPassword) { this.oldPassword = oldPassword; }
|
||||
public String getNewPassword() { return newPassword; }
|
||||
public void setNewPassword(String newPassword) { this.newPassword = newPassword; }
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
*/
|
||||
@Operation(summary = "生成验证码", description = "生成图形验证码")
|
||||
@GetMapping("/captcha")
|
||||
public Result<Map<String, String>> generateCaptcha() {
|
||||
try {
|
||||
// 生成验证码
|
||||
CaptchaUtils.CaptchaResult captchaResult = captchaUtils.generateCaptcha();
|
||||
|
||||
// 生成验证码Key
|
||||
String captchaKey = UUID.randomUUID().toString();
|
||||
|
||||
// 注意:验证码应当存储在会话中或其他存储中,此处省略存储步骤
|
||||
// 在实际应用中,可以使用Session或其他方式存储验证码
|
||||
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("captchaKey", captchaKey);
|
||||
result.put("captchaImage", captchaResult.getImageBase64());
|
||||
result.put("code", captchaResult.getCode()); // 临时方案:直接返回验证码(生产环境不建议)
|
||||
|
||||
return Result.success(result);
|
||||
} catch (Exception e) {
|
||||
log.error("生成验证码失败: {}", e.getMessage(), e);
|
||||
return Result.error("生成验证码失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
@Operation(summary = "用户登录", description = "用户登录认证")
|
||||
@PostMapping("/login")
|
||||
@OperationLog(type = Constants.OPT_TYPE_LOGIN, module = "认证管理", description = "用户登录")
|
||||
public Result<Map<String, Object>> login(@Valid @RequestBody LoginRequest loginRequest, HttpServletRequest request) {
|
||||
try {
|
||||
// 注意:在实际应用中,应该从会话或其他存储中获取验证码进行验证
|
||||
// 此处简化处理,假设验证码已通过(实际应用中需要实现验证逻辑)
|
||||
// 如果使用了临时方案,可以从前端传回验证码进行比对
|
||||
|
||||
// 进行身份认证
|
||||
Authentication authentication = authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
loginRequest.getUsername(),
|
||||
loginRequest.getPassword()
|
||||
)
|
||||
);
|
||||
AppUser user=appUserService.findByUsername(loginRequest.getUsername());
|
||||
// 设置认证信息到安全上下文
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
|
||||
// 生成JWT Token
|
||||
String token = jwtUtils.generateToken(user.getUsername(),user.getId());
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("token", token);
|
||||
result.put("userInfo", user);
|
||||
|
||||
return Result.success(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("用户登录失败: {}", e.getMessage(), e);
|
||||
return Result.error("登录失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登出
|
||||
*/
|
||||
@Operation(summary = "用户登出", description = "用户登出")
|
||||
@PostMapping("/logout")
|
||||
@OperationLog(type = Constants.OPT_TYPE_OTHER, module = "认证管理", description = "用户登出")
|
||||
public Result<Void> logout(HttpServletRequest request) {
|
||||
try {
|
||||
// 获取Token
|
||||
String token = request.getHeader(Constants.JWT_HEADER);
|
||||
if (token != null && token.startsWith(Constants.JWT_PREFIX)) {
|
||||
token = token.substring(Constants.JWT_PREFIX.length());
|
||||
|
||||
// 注意:在实际应用中,应该实现Token失效机制
|
||||
// 可以考虑使用短期Token或其他方式实现Token失效
|
||||
// 此处省略Token黑名单实现
|
||||
}
|
||||
|
||||
// 清除安全上下文
|
||||
SecurityContextHolder.clearContext();
|
||||
|
||||
return Result.success();
|
||||
} catch (Exception e) {
|
||||
log.error("用户登出失败: {}", e.getMessage(), e);
|
||||
return Result.error("登出失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*/
|
||||
@Operation(summary = "获取当前用户信息", description = "获取当前登录用户的详细信息")
|
||||
@GetMapping("/userinfo")
|
||||
public Result<AppUser> getCurrentUserInfo(HttpServletRequest request) {
|
||||
try {
|
||||
String token = request.getHeader(Constants.JWT_HEADER);
|
||||
if (token != null && token.startsWith(Constants.JWT_PREFIX)) {
|
||||
token = token.substring(Constants.JWT_PREFIX.length());
|
||||
String username = jwtUtils.getUsernameFromToken(token);
|
||||
AppUser userInfo = appUserService.findByUsername(username);
|
||||
return Result.success(userInfo);
|
||||
}
|
||||
|
||||
return Result.unauthorized();
|
||||
} catch (Exception e) {
|
||||
log.error("获取用户信息失败: {}", e.getMessage(), e);
|
||||
return Result.error("获取用户信息失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*/
|
||||
@Operation(summary = "修改密码", description = "修改当前用户密码")
|
||||
@PostMapping("/changePassword")
|
||||
@OperationLog(type = Constants.OPT_TYPE_UPDATE, module = "认证管理", description = "修改密码")
|
||||
public Result<Void> changePassword(@Valid @RequestBody ChangePasswordRequest request, HttpServletRequest httpRequest) {
|
||||
try {
|
||||
String token = httpRequest.getHeader(Constants.JWT_HEADER);
|
||||
if (token != null && token.startsWith(Constants.JWT_PREFIX)) {
|
||||
token = token.substring(Constants.JWT_PREFIX.length());
|
||||
String username = jwtUtils.getUsernameFromToken(token);
|
||||
|
||||
boolean success = appUserService.changePassword(username, request.getOldPassword(), request.getNewPassword());
|
||||
if (success) {
|
||||
return Result.success();
|
||||
} else {
|
||||
return Result.error("原密码错误");
|
||||
}
|
||||
}
|
||||
|
||||
return Result.unauthorized();
|
||||
} catch (Exception e) {
|
||||
log.error("修改密码失败: {}", e.getMessage(), e);
|
||||
return Result.error("修改密码失败");
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/refreshToken")
|
||||
public Result<String> refreshToken(@RequestBody Map<String, String> params) {
|
||||
String refreshToken = params.get("refreshToken");
|
||||
if (refreshToken == null || refreshToken.trim().isEmpty()) {
|
||||
return Result.error("刷新令牌不能为空");
|
||||
}
|
||||
// 验证refreshToken的有效性并获取用户信息
|
||||
String username = jwtUtils.getUsernameFromToken(refreshToken);
|
||||
if (username != null) {
|
||||
AppUser user = appUserService.findByUsername(username);
|
||||
// 生成新的token
|
||||
return Result.success(jwtUtils.generateToken(user.getUsername(), user.getId()));
|
||||
}
|
||||
return Result.error("无效的刷新令牌");
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package com.stdproject.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 应用-数据字典
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("app_dictionary")
|
||||
public class AppDictionary implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 应用ID 关联应用系统
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 顺序号
|
||||
*/
|
||||
private Integer orderno;
|
||||
|
||||
/**
|
||||
* 字典编码
|
||||
*/
|
||||
private String dictcode;
|
||||
|
||||
/**
|
||||
* 字典名称
|
||||
*/
|
||||
private String dictname;
|
||||
|
||||
/**
|
||||
* 字典数据 JSON数组[{"code":"01","name":"字典值"}]
|
||||
*/
|
||||
private String dictdata;
|
||||
|
||||
/**
|
||||
* 最近修改日期
|
||||
*/
|
||||
private LocalDateTime lastmodifydate;
|
||||
|
||||
/**
|
||||
* 备用1
|
||||
*/
|
||||
private String custom1;
|
||||
|
||||
/**
|
||||
* 备用2
|
||||
*/
|
||||
private String custom2;
|
||||
|
||||
/**
|
||||
* 备用3
|
||||
*/
|
||||
private String custom3;
|
||||
|
||||
}
|
110
backend/src/main/java/com/stdproject/entity/AppMenu.java
Normal file
110
backend/src/main/java/com/stdproject/entity/AppMenu.java
Normal file
@ -0,0 +1,110 @@
|
||||
package com.stdproject.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 应用系统_菜单
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("app_menu")
|
||||
public class AppMenu implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 应用ID 关联应用系统
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 菜单类型 01-菜单
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 菜单编号
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 同级序号
|
||||
*/
|
||||
private Integer orderno;
|
||||
|
||||
/**
|
||||
* 菜单名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 菜单图标 base64存储
|
||||
*/
|
||||
private String icon;
|
||||
|
||||
/**
|
||||
* 是否外链 0-非外部 1-是外链
|
||||
*/
|
||||
private String islink;
|
||||
|
||||
/**
|
||||
* 菜单URL 内部资源页面URL访问地址
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 模块ID 菜单关联的模块ID
|
||||
*/
|
||||
private String moduleId;
|
||||
|
||||
/**
|
||||
* 父级菜单ID 顶级为0
|
||||
*/
|
||||
private String parentid;
|
||||
|
||||
/**
|
||||
* 是否显示 0-不显示 1-显示
|
||||
*/
|
||||
private String isdisplay;
|
||||
|
||||
/**
|
||||
* 最近修改者
|
||||
*/
|
||||
private String lastmodifier;
|
||||
|
||||
/**
|
||||
* 最近修改日期
|
||||
*/
|
||||
private LocalDateTime lastmodifydate;
|
||||
|
||||
/**
|
||||
* 备用1
|
||||
*/
|
||||
private String custom1;
|
||||
|
||||
/**
|
||||
* 备用2
|
||||
*/
|
||||
private String custom2;
|
||||
|
||||
/**
|
||||
* 备用3
|
||||
*/
|
||||
private String custom3;
|
||||
|
||||
}
|
80
backend/src/main/java/com/stdproject/entity/AppOptLog.java
Normal file
80
backend/src/main/java/com/stdproject/entity/AppOptLog.java
Normal file
@ -0,0 +1,80 @@
|
||||
package com.stdproject.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 系统操作日志
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("app_optlog")
|
||||
public class AppOptLog implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
private String usercode;
|
||||
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 操作类型 00-登录 01-新增 02-修改 03-删除 06-查询 09其他
|
||||
*/
|
||||
private String opttype;
|
||||
|
||||
/**
|
||||
* 模块名称
|
||||
*/
|
||||
private String module;
|
||||
|
||||
/**
|
||||
* 日志描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 操作方法
|
||||
*/
|
||||
private String method;
|
||||
|
||||
/**
|
||||
* 方法参数
|
||||
*/
|
||||
private String params;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime logtime;
|
||||
|
||||
/**
|
||||
* 请求IP
|
||||
*/
|
||||
private String requestip;
|
||||
|
||||
/**
|
||||
* 浏览器类型
|
||||
*/
|
||||
private String browser;
|
||||
|
||||
}
|
110
backend/src/main/java/com/stdproject/entity/AppOrganization.java
Normal file
110
backend/src/main/java/com/stdproject/entity/AppOrganization.java
Normal file
@ -0,0 +1,110 @@
|
||||
package com.stdproject.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 应用系统_用户组织
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("app_organization")
|
||||
public class AppOrganization implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 应用ID 关联应用系统
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 组织类型:01-公司 02-部门
|
||||
*/
|
||||
private String orgtype;
|
||||
|
||||
/**
|
||||
* 组织编号
|
||||
*/
|
||||
private String orgcode;
|
||||
|
||||
/**
|
||||
* 组织名称
|
||||
*/
|
||||
private String orgname;
|
||||
|
||||
/**
|
||||
* 上级ID
|
||||
*/
|
||||
private String parentid;
|
||||
|
||||
/**
|
||||
* 组织负责人
|
||||
*/
|
||||
private String manager;
|
||||
|
||||
/**
|
||||
* 组织详情
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 联系地址
|
||||
*/
|
||||
private String address;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
private String contactPhone;
|
||||
|
||||
/**
|
||||
* 联系人
|
||||
*/
|
||||
private String contactPerson;
|
||||
|
||||
/**
|
||||
* 是否有效 1-是 0-否
|
||||
*/
|
||||
private String isvaild;
|
||||
|
||||
/**
|
||||
* 最近修改者
|
||||
*/
|
||||
private String lastmodifier;
|
||||
|
||||
/**
|
||||
* 最近修改日期
|
||||
*/
|
||||
private LocalDateTime lastmodifydate;
|
||||
|
||||
/**
|
||||
* 备用1
|
||||
*/
|
||||
private String custom1;
|
||||
|
||||
/**
|
||||
* 备用2
|
||||
*/
|
||||
private String custom2;
|
||||
|
||||
/**
|
||||
* 备用3
|
||||
*/
|
||||
private String custom3;
|
||||
|
||||
}
|
85
backend/src/main/java/com/stdproject/entity/AppRole.java
Normal file
85
backend/src/main/java/com/stdproject/entity/AppRole.java
Normal file
@ -0,0 +1,85 @@
|
||||
package com.stdproject.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 应用系统_系统角色
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("app_role")
|
||||
public class AppRole implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 应用ID 关联应用系统
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 角色编号 系统生成,三位编号
|
||||
*/
|
||||
private String rolecode;
|
||||
|
||||
/**
|
||||
* 角色名称
|
||||
*/
|
||||
private String rolename;
|
||||
|
||||
/**
|
||||
* 角色类别 1-应用管理员 2-应用普通用户
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 角色描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 是否有效 1-是 0-否
|
||||
*/
|
||||
private String isvaild;
|
||||
|
||||
/**
|
||||
* 最近修改者
|
||||
*/
|
||||
private String lastmodifier;
|
||||
|
||||
/**
|
||||
* 最近修改日期
|
||||
*/
|
||||
private LocalDateTime lastmodifydate;
|
||||
|
||||
/**
|
||||
* 备用1
|
||||
*/
|
||||
private String custom1;
|
||||
|
||||
/**
|
||||
* 备用2
|
||||
*/
|
||||
private String custom2;
|
||||
|
||||
/**
|
||||
* 备用3
|
||||
*/
|
||||
private String custom3;
|
||||
|
||||
}
|
44
backend/src/main/java/com/stdproject/entity/AppRoleMenu.java
Normal file
44
backend/src/main/java/com/stdproject/entity/AppRoleMenu.java
Normal file
@ -0,0 +1,44 @@
|
||||
package com.stdproject.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 系统角色-数据权限对照
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("app_role_menu")
|
||||
public class AppRoleMenu implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 应用ID 关联应用系统
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 角色id
|
||||
*/
|
||||
private String roleid;
|
||||
|
||||
/**
|
||||
* 菜单id
|
||||
*/
|
||||
private String menuid;
|
||||
|
||||
}
|
44
backend/src/main/java/com/stdproject/entity/AppRoleUser.java
Normal file
44
backend/src/main/java/com/stdproject/entity/AppRoleUser.java
Normal file
@ -0,0 +1,44 @@
|
||||
package com.stdproject.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 系统角色-用户对照
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("app_role_users")
|
||||
public class AppRoleUser implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 应用ID 关联应用系统
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 角色id
|
||||
*/
|
||||
private String roleid;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
private String userid;
|
||||
|
||||
}
|
130
backend/src/main/java/com/stdproject/entity/AppUser.java
Normal file
130
backend/src/main/java/com/stdproject/entity/AppUser.java
Normal file
@ -0,0 +1,130 @@
|
||||
package com.stdproject.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 应用系统_系统用户
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("app_user")
|
||||
public class AppUser implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 应用ID 关联应用系统
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 所属组织
|
||||
*/
|
||||
private String orgid;
|
||||
|
||||
/**
|
||||
* 用户类型 0-管理员 1-普通用户
|
||||
*/
|
||||
private String usertype;
|
||||
|
||||
/**
|
||||
* 用户名称 用户名称(账号)
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 用户昵称(中文)
|
||||
*/
|
||||
private String nickname;
|
||||
|
||||
/**
|
||||
* 登录密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 用户头像 base64存储用户头像
|
||||
*/
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 状态 1-有效 0-停用
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 密码有限期 密码有限期(天)
|
||||
*/
|
||||
private Integer pwdvalidperiod;
|
||||
|
||||
/**
|
||||
* 登录失败次数 允许的登录失败次数
|
||||
*/
|
||||
private Integer failednum;
|
||||
|
||||
/**
|
||||
* 用户指定登录IP 如果设置了IP,则只允许IP用户登录
|
||||
*/
|
||||
private String loginip;
|
||||
|
||||
/**
|
||||
* 登录失败锁定时间
|
||||
*/
|
||||
private LocalDateTime failedlocktime;
|
||||
|
||||
/**
|
||||
* 密码修改时间
|
||||
*/
|
||||
private LocalDateTime pwdresettime;
|
||||
|
||||
/**
|
||||
* 最近修改者
|
||||
*/
|
||||
private String lastmodifier;
|
||||
|
||||
/**
|
||||
* 最近修改日期
|
||||
*/
|
||||
private LocalDateTime lastmodifydate;
|
||||
|
||||
/**
|
||||
* 备用1
|
||||
*/
|
||||
private String custom1;
|
||||
|
||||
/**
|
||||
* 备用2
|
||||
*/
|
||||
private String custom2;
|
||||
|
||||
/**
|
||||
* 备用3
|
||||
*/
|
||||
private String custom3;
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.stdproject.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.stdproject.entity.AppDictionary;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用-数据字典 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Mapper
|
||||
public interface AppDictionaryMapper extends BaseMapper<AppDictionary> {
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.stdproject.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.stdproject.entity.AppMenu;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用系统_菜单 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Mapper
|
||||
public interface AppMenuMapper extends BaseMapper<AppMenu> {
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.stdproject.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.stdproject.entity.AppOptLog;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 系统操作日志 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Mapper
|
||||
public interface AppOptLogMapper extends BaseMapper<AppOptLog> {
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.stdproject.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.stdproject.entity.AppOrganization;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用系统_用户组织 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Mapper
|
||||
public interface AppOrganizationMapper extends BaseMapper<AppOrganization> {
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.stdproject.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.stdproject.entity.AppRole;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用系统_系统角色 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Mapper
|
||||
public interface AppRoleMapper extends BaseMapper<AppRole> {
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.stdproject.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.stdproject.entity.AppRoleMenu;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 系统角色-数据权限对照 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Mapper
|
||||
public interface AppRoleMenuMapper extends BaseMapper<AppRoleMenu> {
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.stdproject.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.stdproject.entity.AppRoleUser;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 系统角色-用户对照 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Mapper
|
||||
public interface AppRoleUserMapper extends BaseMapper<AppRoleUser> {
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.stdproject.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.stdproject.entity.AppUser;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用系统_系统用户 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Mapper
|
||||
public interface AppUserMapper extends BaseMapper<AppUser> {
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.stdproject.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.stdproject.entity.AppDictionary;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用-数据字典 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
public interface IAppDictionaryService extends IService<AppDictionary> {
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.stdproject.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.stdproject.entity.AppMenu;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用系统_菜单 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
public interface IAppMenuService extends IService<AppMenu> {
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.stdproject.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.stdproject.entity.AppOptLog;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 系统操作日志 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
public interface IAppOptLogService extends IService<AppOptLog> {
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.stdproject.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.stdproject.entity.AppOrganization;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用系统_用户组织 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
public interface IAppOrganizationService extends IService<AppOrganization> {
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.stdproject.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.stdproject.entity.AppRoleMenu;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 系统角色-数据权限对照 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
public interface IAppRoleMenuService extends IService<AppRoleMenu> {
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.stdproject.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.stdproject.entity.AppRole;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用系统_系统角色 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
public interface IAppRoleService extends IService<AppRole> {
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.stdproject.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.stdproject.entity.AppRoleUser;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 系统角色-用户对照 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
public interface IAppRoleUserService extends IService<AppRoleUser> {
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package com.stdproject.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.stdproject.entity.AppUser;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用系统_系统用户 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
public interface IAppUserService extends IService<AppUser> {
|
||||
|
||||
/**
|
||||
* 根据用户名查找用户
|
||||
* @param username 用户名
|
||||
* @return 用户实体
|
||||
*/
|
||||
AppUser findByUsername(String username);
|
||||
|
||||
/**
|
||||
* 获取当前登录用户名
|
||||
* @return 当前登录用户名,未登录则返回null
|
||||
*/
|
||||
String getCurrentUsername();
|
||||
|
||||
/**
|
||||
* 获取当前登录用户
|
||||
* @return 当前登录用户实体,未登录则返回null
|
||||
*/
|
||||
AppUser getCurrentUser();
|
||||
|
||||
/**
|
||||
* 修改用户密码
|
||||
* @param username 用户名
|
||||
* @param oldPassword 原密码
|
||||
* @param newPassword 新密码
|
||||
* @return 修改是否成功
|
||||
*/
|
||||
boolean changePassword(String username, String oldPassword, String newPassword);
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.stdproject.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.stdproject.entity.AppDictionary;
|
||||
import com.stdproject.mapper.AppDictionaryMapper;
|
||||
import com.stdproject.service.IAppDictionaryService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用-数据字典 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Service
|
||||
public class AppDictionaryServiceImpl extends ServiceImpl<AppDictionaryMapper, AppDictionary> implements IAppDictionaryService {
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.stdproject.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.stdproject.entity.AppMenu;
|
||||
import com.stdproject.mapper.AppMenuMapper;
|
||||
import com.stdproject.service.IAppMenuService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用系统_菜单 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Service
|
||||
public class AppMenuServiceImpl extends ServiceImpl<AppMenuMapper, AppMenu> implements IAppMenuService {
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.stdproject.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.stdproject.entity.AppOptLog;
|
||||
import com.stdproject.mapper.AppOptLogMapper;
|
||||
import com.stdproject.service.IAppOptLogService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 系统操作日志 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Service
|
||||
public class AppOptLogServiceImpl extends ServiceImpl<AppOptLogMapper, AppOptLog> implements IAppOptLogService {
|
||||
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.stdproject.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.stdproject.entity.AppOrganization;
|
||||
import com.stdproject.mapper.AppOrganizationMapper;
|
||||
import com.stdproject.service.IAppOrganizationService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用系统_用户组织 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Service
|
||||
public class AppOrganizationServiceImpl extends ServiceImpl<AppOrganizationMapper, AppOrganization> implements IAppOrganizationService {
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.stdproject.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.stdproject.entity.AppRoleMenu;
|
||||
import com.stdproject.mapper.AppRoleMenuMapper;
|
||||
import com.stdproject.service.IAppRoleMenuService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 系统角色-数据权限对照 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Service
|
||||
public class AppRoleMenuServiceImpl extends ServiceImpl<AppRoleMenuMapper, AppRoleMenu> implements IAppRoleMenuService {
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.stdproject.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.stdproject.entity.AppRole;
|
||||
import com.stdproject.mapper.AppRoleMapper;
|
||||
import com.stdproject.service.IAppRoleService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用系统_系统角色 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Service
|
||||
public class AppRoleServiceImpl extends ServiceImpl<AppRoleMapper, AppRole> implements IAppRoleService {
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.stdproject.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.stdproject.entity.AppRoleUser;
|
||||
import com.stdproject.mapper.AppRoleUserMapper;
|
||||
import com.stdproject.service.IAppRoleUserService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 系统角色-用户对照 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Service
|
||||
public class AppRoleUserServiceImpl extends ServiceImpl<AppRoleUserMapper, AppRoleUser> implements IAppRoleUserService {
|
||||
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package com.stdproject.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.stdproject.entity.AppUser;
|
||||
import com.stdproject.mapper.AppUserMapper;
|
||||
import com.stdproject.service.IAppUserService;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 应用系统_系统用户 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author StdProject
|
||||
* @since 2023-12-07
|
||||
*/
|
||||
@Service
|
||||
public class AppUserServiceImpl extends ServiceImpl<AppUserMapper, AppUser> implements IAppUserService {
|
||||
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Override
|
||||
public AppUser findByUsername(String username) {
|
||||
return getOne(new QueryWrapper<AppUser>().eq("username", username).select( "id", "username", "orgid", "usertype","nickname"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentUsername() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication != null && authentication.isAuthenticated()) {
|
||||
return authentication.getName();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AppUser getCurrentUser() {
|
||||
String username = getCurrentUsername();
|
||||
if (StringUtils.hasText(username)) {
|
||||
return findByUsername(username);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean changePassword(String username, String oldPassword, String newPassword) {
|
||||
if (!StringUtils.hasText(username) || !StringUtils.hasText(oldPassword) || !StringUtils.hasText(newPassword)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取完整的用户信息(包含密码)
|
||||
AppUser user = getOne(new QueryWrapper<AppUser>().eq("username", username));
|
||||
if (user == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证原密码
|
||||
if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 更新新密码
|
||||
user.setPassword(passwordEncoder.encode(newPassword));
|
||||
user.setPwdresettime(LocalDateTime.now());
|
||||
|
||||
return updateById(user);
|
||||
}
|
||||
}
|
271
backend/src/main/java/com/stdproject/utils/CaptchaUtils.java
Normal file
271
backend/src/main/java/com/stdproject/utils/CaptchaUtils.java
Normal file
@ -0,0 +1,271 @@
|
||||
package com.stdproject.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* 验证码工具类
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class CaptchaUtils {
|
||||
|
||||
/**
|
||||
* 验证码字符集
|
||||
*/
|
||||
private static final String CAPTCHA_CHARS = "ABCDEFGHJKMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
|
||||
|
||||
/**
|
||||
* 验证码长度
|
||||
*/
|
||||
private static final int CAPTCHA_LENGTH = 4;
|
||||
|
||||
/**
|
||||
* 图片宽度
|
||||
*/
|
||||
private static final int IMAGE_WIDTH = 120;
|
||||
|
||||
/**
|
||||
* 图片高度
|
||||
*/
|
||||
private static final int IMAGE_HEIGHT = 40;
|
||||
|
||||
/**
|
||||
* 干扰线数量
|
||||
*/
|
||||
private static final int INTERFERENCE_LINE_COUNT = 5;
|
||||
|
||||
/**
|
||||
* 干扰点数量
|
||||
*/
|
||||
private static final int INTERFERENCE_POINT_COUNT = 50;
|
||||
|
||||
/**
|
||||
* 验证码结果类
|
||||
*/
|
||||
public static class CaptchaResult {
|
||||
private String code;
|
||||
private String imageBase64;
|
||||
|
||||
public CaptchaResult(String code, String imageBase64) {
|
||||
this.code = code;
|
||||
this.imageBase64 = imageBase64;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getImageBase64() {
|
||||
return imageBase64;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
*
|
||||
* @return 验证码结果
|
||||
*/
|
||||
public CaptchaResult generateCaptcha() {
|
||||
// 生成验证码字符串
|
||||
String code = generateRandomCode();
|
||||
|
||||
// 生成验证码图片
|
||||
String imageBase64 = generateCaptchaImage(code);
|
||||
|
||||
return new CaptchaResult(code, imageBase64);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机验证码字符串
|
||||
*
|
||||
* @return 验证码字符串
|
||||
*/
|
||||
private String generateRandomCode() {
|
||||
Random random = new Random();
|
||||
StringBuilder code = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < CAPTCHA_LENGTH; i++) {
|
||||
int index = random.nextInt(CAPTCHA_CHARS.length());
|
||||
code.append(CAPTCHA_CHARS.charAt(index));
|
||||
}
|
||||
|
||||
return code.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证码图片
|
||||
*
|
||||
* @param code 验证码字符串
|
||||
* @return Base64编码的图片
|
||||
*/
|
||||
private String generateCaptchaImage(String code) {
|
||||
try {
|
||||
// 创建图片
|
||||
BufferedImage image = new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g2d = image.createGraphics();
|
||||
|
||||
// 设置抗锯齿
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
// 填充背景
|
||||
g2d.setColor(Color.WHITE);
|
||||
g2d.fillRect(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
|
||||
|
||||
// 绘制干扰线
|
||||
drawInterferenceLines(g2d);
|
||||
|
||||
// 绘制验证码字符
|
||||
drawCaptchaText(g2d, code);
|
||||
|
||||
// 绘制干扰点
|
||||
drawInterferencePoints(g2d);
|
||||
|
||||
g2d.dispose();
|
||||
|
||||
// 转换为Base64
|
||||
return imageToBase64(image);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("生成验证码图片失败: {}", e.getMessage(), e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制干扰线
|
||||
*
|
||||
* @param g2d 图形对象
|
||||
*/
|
||||
private void drawInterferenceLines(Graphics2D g2d) {
|
||||
Random random = new Random();
|
||||
|
||||
for (int i = 0; i < INTERFERENCE_LINE_COUNT; i++) {
|
||||
int x1 = random.nextInt(IMAGE_WIDTH);
|
||||
int y1 = random.nextInt(IMAGE_HEIGHT);
|
||||
int x2 = random.nextInt(IMAGE_WIDTH);
|
||||
int y2 = random.nextInt(IMAGE_HEIGHT);
|
||||
|
||||
g2d.setColor(getRandomColor(150, 200));
|
||||
g2d.setStroke(new BasicStroke(1.0f));
|
||||
g2d.drawLine(x1, y1, x2, y2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制验证码文字
|
||||
*
|
||||
* @param g2d 图形对象
|
||||
* @param code 验证码字符串
|
||||
*/
|
||||
private void drawCaptchaText(Graphics2D g2d, String code) {
|
||||
Random random = new Random();
|
||||
int charWidth = IMAGE_WIDTH / CAPTCHA_LENGTH;
|
||||
|
||||
for (int i = 0; i < code.length(); i++) {
|
||||
char c = code.charAt(i);
|
||||
|
||||
// 随机字体大小
|
||||
int fontSize = 20 + random.nextInt(8);
|
||||
Font font = new Font("Arial", Font.BOLD, fontSize);
|
||||
g2d.setFont(font);
|
||||
|
||||
// 随机颜色
|
||||
g2d.setColor(getRandomColor(20, 130));
|
||||
|
||||
// 随机位置和角度
|
||||
int x = i * charWidth + random.nextInt(charWidth / 3) + 5;
|
||||
int y = IMAGE_HEIGHT / 2 + random.nextInt(10) - 5;
|
||||
|
||||
// 随机旋转角度
|
||||
double angle = (random.nextDouble() - 0.5) * 0.4;
|
||||
g2d.rotate(angle, x, y);
|
||||
|
||||
g2d.drawString(String.valueOf(c), x, y);
|
||||
|
||||
// 恢复角度
|
||||
g2d.rotate(-angle, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制干扰点
|
||||
*
|
||||
* @param g2d 图形对象
|
||||
*/
|
||||
private void drawInterferencePoints(Graphics2D g2d) {
|
||||
Random random = new Random();
|
||||
|
||||
for (int i = 0; i < INTERFERENCE_POINT_COUNT; i++) {
|
||||
int x = random.nextInt(IMAGE_WIDTH);
|
||||
int y = random.nextInt(IMAGE_HEIGHT);
|
||||
|
||||
g2d.setColor(getRandomColor(100, 200));
|
||||
g2d.fillOval(x, y, 2, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取随机颜色
|
||||
*
|
||||
* @param min 最小值
|
||||
* @param max 最大值
|
||||
* @return 随机颜色
|
||||
*/
|
||||
private Color getRandomColor(int min, int max) {
|
||||
Random random = new Random();
|
||||
|
||||
if (min > 255) min = 255;
|
||||
if (max > 255) max = 255;
|
||||
if (min < 0) min = 0;
|
||||
if (max < 0) max = 0;
|
||||
if (min > max) {
|
||||
int temp = min;
|
||||
min = max;
|
||||
max = temp;
|
||||
}
|
||||
|
||||
int r = min + random.nextInt(max - min);
|
||||
int g = min + random.nextInt(max - min);
|
||||
int b = min + random.nextInt(max - min);
|
||||
|
||||
return new Color(r, g, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片转Base64
|
||||
*
|
||||
* @param image 图片
|
||||
* @return Base64字符串
|
||||
*/
|
||||
private String imageToBase64(BufferedImage image) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ImageIO.write(image, "png", baos);
|
||||
byte[] imageBytes = baos.toByteArray();
|
||||
return "data:image/png;base64," + Base64.getEncoder().encodeToString(imageBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证验证码
|
||||
*
|
||||
* @param inputCode 用户输入的验证码
|
||||
* @param correctCode 正确的验证码
|
||||
* @return 是否验证通过
|
||||
*/
|
||||
public boolean verifyCaptcha(String inputCode, String correctCode) {
|
||||
if (inputCode == null || correctCode == null) {
|
||||
return false;
|
||||
}
|
||||
return inputCode.equalsIgnoreCase(correctCode);
|
||||
}
|
||||
}
|
219
backend/src/main/java/com/stdproject/utils/FileUtils.java
Normal file
219
backend/src/main/java/com/stdproject/utils/FileUtils.java
Normal file
@ -0,0 +1,219 @@
|
||||
package com.stdproject.utils;
|
||||
|
||||
import com.stdproject.common.BusinessException;
|
||||
import com.stdproject.common.ResultCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 文件工具类
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class FileUtils {
|
||||
|
||||
@Value("${file.upload.path:/uploads}")
|
||||
private String uploadPath;
|
||||
|
||||
@Value("${file.upload.max-size:10485760}")
|
||||
private long maxFileSize; // 默认10MB
|
||||
|
||||
/**
|
||||
* 允许的图片文件类型
|
||||
*/
|
||||
private static final List<String> ALLOWED_IMAGE_TYPES = Arrays.asList(
|
||||
"jpg", "jpeg", "png", "gif", "bmp", "webp"
|
||||
);
|
||||
|
||||
/**
|
||||
* 允许的文档文件类型
|
||||
*/
|
||||
private static final List<String> ALLOWED_DOCUMENT_TYPES = Arrays.asList(
|
||||
"pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt"
|
||||
);
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
*
|
||||
* @param file 文件
|
||||
* @param subDir 子目录
|
||||
* @return 文件相对路径
|
||||
*/
|
||||
public String uploadFile(MultipartFile file, String subDir) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new BusinessException("文件不能为空");
|
||||
}
|
||||
|
||||
// 检查文件大小
|
||||
if (file.getSize() > maxFileSize) {
|
||||
throw new BusinessException( "文件大小不能超过" + (maxFileSize / 1024 / 1024) + "MB");
|
||||
}
|
||||
|
||||
// 获取原始文件名和扩展名
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
if (originalFilename == null || originalFilename.trim().isEmpty()) {
|
||||
throw new BusinessException( "文件名不能为空");
|
||||
}
|
||||
|
||||
String extension = getFileExtension(originalFilename);
|
||||
if (extension.isEmpty()) {
|
||||
throw new BusinessException( "文件必须有扩展名");
|
||||
}
|
||||
|
||||
// 检查文件类型
|
||||
if (!isAllowedFileType(extension)) {
|
||||
throw new BusinessException( "不支持的文件类型: " + extension);
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建目录结构:uploadPath/subDir/yyyy/MM/dd/
|
||||
String dateDir = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
|
||||
String fullSubDir = subDir + "/" + dateDir;
|
||||
Path uploadDir = Paths.get(uploadPath, fullSubDir);
|
||||
|
||||
if (!Files.exists(uploadDir)) {
|
||||
Files.createDirectories(uploadDir);
|
||||
}
|
||||
|
||||
// 生成新文件名
|
||||
String newFileName = UUID.randomUUID().toString().replace("-", "") + "." + extension;
|
||||
Path filePath = uploadDir.resolve(newFileName);
|
||||
|
||||
// 保存文件
|
||||
file.transferTo(filePath.toFile());
|
||||
|
||||
// 返回相对路径
|
||||
return fullSubDir + "/" + newFileName;
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("文件上传失败: {}", e.getMessage(), e);
|
||||
throw new BusinessException( "文件上传失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*
|
||||
* @param filePath 文件相对路径
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
public boolean deleteFile(String filePath) {
|
||||
if (filePath == null || filePath.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Path fullPath = Paths.get(uploadPath, filePath);
|
||||
return Files.deleteIfExists(fullPath);
|
||||
} catch (IOException e) {
|
||||
log.error("删除文件失败: {}", e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
*
|
||||
* @param filename 文件名
|
||||
* @return 扩展名(小写)
|
||||
*/
|
||||
public String getFileExtension(String filename) {
|
||||
if (filename == null || filename.trim().isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
int lastDotIndex = filename.lastIndexOf('.');
|
||||
if (lastDotIndex == -1 || lastDotIndex == filename.length() - 1) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return filename.substring(lastDotIndex + 1).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为允许的文件类型
|
||||
*
|
||||
* @param extension 文件扩展名
|
||||
* @return 是否允许
|
||||
*/
|
||||
public boolean isAllowedFileType(String extension) {
|
||||
if (extension == null || extension.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String lowerExtension = extension.toLowerCase();
|
||||
return ALLOWED_IMAGE_TYPES.contains(lowerExtension) ||
|
||||
ALLOWED_DOCUMENT_TYPES.contains(lowerExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为图片文件
|
||||
*
|
||||
* @param extension 文件扩展名
|
||||
* @return 是否为图片
|
||||
*/
|
||||
public boolean isImageFile(String extension) {
|
||||
if (extension == null || extension.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return ALLOWED_IMAGE_TYPES.contains(extension.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件完整路径
|
||||
*
|
||||
* @param relativePath 相对路径
|
||||
* @return 完整路径
|
||||
*/
|
||||
public String getFullPath(String relativePath) {
|
||||
if (relativePath == null || relativePath.trim().isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return Paths.get(uploadPath, relativePath).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件是否存在
|
||||
*
|
||||
* @param relativePath 相对路径
|
||||
* @return 是否存在
|
||||
*/
|
||||
public boolean fileExists(String relativePath) {
|
||||
if (relativePath == null || relativePath.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return Files.exists(Paths.get(uploadPath, relativePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件大小
|
||||
*
|
||||
* @param size 文件大小(字节)
|
||||
* @return 格式化后的大小
|
||||
*/
|
||||
public static String formatFileSize(long size) {
|
||||
if (size < 1024) {
|
||||
return size + " B";
|
||||
} else if (size < 1024 * 1024) {
|
||||
return String.format("%.1f KB", size / 1024.0);
|
||||
} else if (size < 1024 * 1024 * 1024) {
|
||||
return String.format("%.1f MB", size / (1024.0 * 1024.0));
|
||||
} else {
|
||||
return String.format("%.1f GB", size / (1024.0 * 1024.0 * 1024.0));
|
||||
}
|
||||
}
|
||||
}
|
168
backend/src/main/java/com/stdproject/utils/JwtUtils.java
Normal file
168
backend/src/main/java/com/stdproject/utils/JwtUtils.java
Normal file
@ -0,0 +1,168 @@
|
||||
package com.stdproject.utils;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JWT工具类
|
||||
* 用于生成、解析和验证JWT令牌
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Component
|
||||
public class JwtUtils {
|
||||
|
||||
@Value("${spring.security.jwt.secret}")
|
||||
private String secret;
|
||||
|
||||
@Value("${spring.security.jwt.expiration-ms}")
|
||||
private Long expirationMs;
|
||||
|
||||
/**
|
||||
* 生成JWT令牌
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param userId 用户ID
|
||||
* @return JWT令牌
|
||||
*/
|
||||
public String generateToken(String username, String userId) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("userId", userId);
|
||||
claims.put("username", username);
|
||||
return createToken(claims, username);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建JWT令牌
|
||||
*
|
||||
* @param claims 声明
|
||||
* @param subject 主题
|
||||
* @return JWT令牌
|
||||
*/
|
||||
private String createToken(Map<String, Object> claims, String subject) {
|
||||
Date now = new Date();
|
||||
Date expiryDate = new Date(now.getTime() + expirationMs);
|
||||
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setSubject(subject)
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(expiryDate)
|
||||
.signWith(getSigningKey(), SignatureAlgorithm.HS512)
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JWT令牌中获取用户名
|
||||
*
|
||||
* @param token JWT令牌
|
||||
* @return 用户名
|
||||
*/
|
||||
public String getUsernameFromToken(String token) {
|
||||
return getClaimFromToken(token, Claims::getSubject);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JWT令牌中获取用户ID
|
||||
*
|
||||
* @param token JWT令牌
|
||||
* @return 用户ID
|
||||
*/
|
||||
public String getUserIdFromToken(String token) {
|
||||
return getClaimFromToken(token, claims -> claims.get("userId", String.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JWT令牌中获取过期时间
|
||||
*
|
||||
* @param token JWT令牌
|
||||
* @return 过期时间
|
||||
*/
|
||||
public Date getExpirationDateFromToken(String token) {
|
||||
return getClaimFromToken(token, Claims::getExpiration);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JWT令牌中获取声明
|
||||
*
|
||||
* @param token JWT令牌
|
||||
* @param claimsResolver 声明解析器
|
||||
* @param <T> 返回类型
|
||||
* @return 声明值
|
||||
*/
|
||||
public <T> T getClaimFromToken(String token, ClaimsResolver<T> claimsResolver) {
|
||||
final Claims claims = getAllClaimsFromToken(token);
|
||||
return claimsResolver.resolve(claims);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JWT令牌中获取所有声明
|
||||
*
|
||||
* @param token JWT令牌
|
||||
* @return 所有声明
|
||||
*/
|
||||
private Claims getAllClaimsFromToken(String token) {
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(getSigningKey())
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查JWT令牌是否过期
|
||||
*
|
||||
* @param token JWT令牌
|
||||
* @return 是否过期
|
||||
*/
|
||||
public Boolean isTokenExpired(String token) {
|
||||
final Date expiration = getExpirationDateFromToken(token);
|
||||
return expiration.before(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证JWT令牌
|
||||
*
|
||||
* @param token JWT令牌
|
||||
* @param username 用户名
|
||||
* @return 是否有效
|
||||
*/
|
||||
public Boolean validateToken(String token, String username) {
|
||||
try {
|
||||
final String tokenUsername = getUsernameFromToken(token);
|
||||
return (username.equals(tokenUsername) && !isTokenExpired(token));
|
||||
} catch (JwtException | IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取签名密钥
|
||||
*
|
||||
* @return 签名密钥
|
||||
*/
|
||||
private SecretKey getSigningKey() {
|
||||
byte[] keyBytes = secret.getBytes();
|
||||
return Keys.hmacShaKeyFor(keyBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 声明解析器接口
|
||||
*
|
||||
* @param <T> 返回类型
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ClaimsResolver<T> {
|
||||
T resolve(Claims claims);
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package com.stdproject.utils;
|
||||
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 密码工具类
|
||||
* 用于密码的加密和验证
|
||||
*
|
||||
* @author StdProject
|
||||
*/
|
||||
@Component
|
||||
public class PasswordUtils {
|
||||
|
||||
private static final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||
|
||||
/**
|
||||
* 加密密码
|
||||
*
|
||||
* @param rawPassword 原始密码
|
||||
* @return 加密后的密码
|
||||
*/
|
||||
public static String encodePassword(String rawPassword) {
|
||||
return passwordEncoder.encode(rawPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证密码
|
||||
*
|
||||
* @param rawPassword 原始密码
|
||||
* @param encodedPassword 加密后的密码
|
||||
* @return 是否匹配
|
||||
*/
|
||||
public static boolean matches(String rawPassword, String encodedPassword) {
|
||||
return passwordEncoder.matches(rawPassword, encodedPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机密码
|
||||
*
|
||||
* @param length 密码长度
|
||||
* @return 随机密码
|
||||
*/
|
||||
public static String generateRandomPassword(int length) {
|
||||
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
StringBuilder password = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
int index = (int) (Math.random() * chars.length());
|
||||
password.append(chars.charAt(index));
|
||||
}
|
||||
return password.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证密码强度
|
||||
*
|
||||
* @param password 密码
|
||||
* @return 是否符合强度要求
|
||||
*/
|
||||
public static boolean isStrongPassword(String password) {
|
||||
if (password == null || password.length() < 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean hasUpper = false;
|
||||
boolean hasLower = false;
|
||||
boolean hasDigit = false;
|
||||
boolean hasSpecial = false;
|
||||
|
||||
for (char c : password.toCharArray()) {
|
||||
if (Character.isUpperCase(c)) {
|
||||
hasUpper = true;
|
||||
} else if (Character.isLowerCase(c)) {
|
||||
hasLower = true;
|
||||
} else if (Character.isDigit(c)) {
|
||||
hasDigit = true;
|
||||
} else if (!Character.isLetterOrDigit(c)) {
|
||||
hasSpecial = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasUpper && hasLower && hasDigit && hasSpecial;
|
||||
}
|
||||
}
|
14
backend/src/main/resources/.env
Normal file
14
backend/src/main/resources/.env
Normal file
@ -0,0 +1,14 @@
|
||||
# 数据库配置
|
||||
DB_URL=jdbc:mysql://your-host:3306/your-db
|
||||
DB_USERNAME=your-username
|
||||
DB_PASSWORD=your-password
|
||||
|
||||
# JWT配置
|
||||
JWT_SECRET=your-super-secret-key
|
||||
JWT_EXPIRATION=86400000
|
||||
|
||||
# 环境配置
|
||||
SPRING_PROFILES_ACTIVE=dev
|
||||
|
||||
# CORS配置
|
||||
CORS_ALLOWED_ORIGINS=https://your-frontend.com
|
193
backend/src/main/resources/application.yml
Normal file
193
backend/src/main/resources/application.yml
Normal file
@ -0,0 +1,193 @@
|
||||
server:
|
||||
port: 8080
|
||||
servlet:
|
||||
context-path: /
|
||||
encoding:
|
||||
charset: UTF-8
|
||||
enabled: true
|
||||
force: true
|
||||
compression:
|
||||
enabled: true
|
||||
mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
|
||||
min-response-size: 1024
|
||||
|
||||
spring:
|
||||
profiles:
|
||||
active: ${SPRING_PROFILES_ACTIVE:dev}
|
||||
application:
|
||||
name: stdproject-backend
|
||||
datasource:
|
||||
url: ${DB_URL:jdbc:mysql://121.37.111.42:3306/gisbi-demodb?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true}
|
||||
username: ${DB_USERNAME:root}
|
||||
password: ${DB_PASSWORD:mysql_F8ysiK@2024}
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
# HikariCP连接池配置
|
||||
hikari:
|
||||
pool-name: StdProjectHikariCP
|
||||
minimum-idle: 5
|
||||
maximum-pool-size: 20
|
||||
auto-commit: true
|
||||
idle-timeout: 30000
|
||||
max-lifetime: 1800000
|
||||
connection-timeout: 30000
|
||||
connection-test-query: SELECT 1
|
||||
# url: jdbc:sqlite:D:/Trae_space/StdProject/backend/db/project.db
|
||||
# username: # SQLite 不需要用户名
|
||||
# password: # SQLite 不需要密码
|
||||
# driver-class-name: org.sqlite.JDBC
|
||||
# hikari:
|
||||
# pool-name: StdProjectHikariCP
|
||||
# minimum-idle: 5
|
||||
# maximum-pool-size: 20
|
||||
# auto-commit: true
|
||||
# idle-timeout: 30000
|
||||
# max-lifetime: 1800000
|
||||
# connection-timeout: 30000
|
||||
# connection-test-query: SELECT 1
|
||||
|
||||
cache:
|
||||
jcache:
|
||||
config: classpath:ehcache.xml # 指定Ehcache配置文件路径
|
||||
security:
|
||||
cors:
|
||||
allowed-origins: ${CORS_ALLOWED_ORIGINS:http://localhost:3000,http://localhost:8080}
|
||||
max-age: ${CORS_MAX_AGE:3600} # 预检请求的缓存时间(秒)
|
||||
jwt:
|
||||
enabled: ${JWT_ENABLED:true} # 控制是否启用JWT认证
|
||||
secret: ${JWT_SECRET:YourJWTSecretKeyForStdProjectBackendApplicationWhichIsVeryLongAndSecure2024!@#$%^&*()}
|
||||
expiration-ms: ${JWT_EXPIRATION:86400000} # Token 过期时间 (例如: 24小时)
|
||||
refresh-expiration-ms: ${JWT_REFRESH_EXPIRATION:604800000} # 刷新Token过期时间 (例如: 7天)
|
||||
|
||||
mybatis-plus:
|
||||
mapper-locations: classpath*:/mapper/**/*.xml # MyBatis Mapper XML文件位置
|
||||
type-aliases-package: com.stdproject.entity
|
||||
global-config:
|
||||
db-config:
|
||||
id-type: ASSIGN_ID # ID生成策略,assign_id 表示手动分配ID,通常使用雪花算法等
|
||||
table-prefix: ${DB_TABLE_PREFIX:} # 如果表名有统一前缀,可以在这里配置
|
||||
logic-delete-field: deleted # 逻辑删除字段名
|
||||
logic-delete-value: 1 # 逻辑删除值
|
||||
logic-not-delete-value: 0 # 逻辑未删除值
|
||||
banner: false # 关闭MyBatis-Plus启动横幅
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true # 开启驼峰命名转换
|
||||
cache-enabled: true # 开启二级缓存
|
||||
lazy-loading-enabled: true # 开启延迟加载
|
||||
multiple-result-sets-enabled: true # 开启多结果集
|
||||
use-column-label: true # 使用列标签
|
||||
use-generated-keys: true # 使用生成的主键
|
||||
auto-mapping-behavior: partial # 自动映射行为
|
||||
default-executor-type: simple # 默认执行器类型
|
||||
default-statement-timeout: 25000 # 默认语句超时时间
|
||||
log-impl: ${MYBATIS_LOG_IMPL:org.apache.ibatis.logging.nologging.NoLoggingImpl} # SQL日志实现
|
||||
|
||||
logging:
|
||||
config: classpath:logback-spring.xml # Logback配置文件
|
||||
level:
|
||||
root: ${LOG_LEVEL_ROOT:INFO}
|
||||
com.stdproject: ${LOG_LEVEL_APP:DEBUG}
|
||||
org.springframework.security: ${LOG_LEVEL_SECURITY:WARN}
|
||||
org.hibernate.SQL: ${LOG_LEVEL_SQL:WARN}
|
||||
org.hibernate.type.descriptor.sql.BasicBinder: ${LOG_LEVEL_SQL_PARAMS:WARN}
|
||||
pattern:
|
||||
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
|
||||
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
path: /v3/api-docs
|
||||
enabled: ${SWAGGER_ENABLED:true}
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
enabled: ${SWAGGER_UI_ENABLED:true}
|
||||
operations-sorter: alpha
|
||||
tags-sorter: alpha
|
||||
try-it-out-enabled: true
|
||||
filter: true
|
||||
default-consumes-media-type: application/json
|
||||
default-produces-media-type: application/json
|
||||
show-actuator: ${SWAGGER_SHOW_ACTUATOR:true}
|
||||
packages-to-scan: com.stdproject.controller
|
||||
|
||||
# 管理端点配置
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: ${ACTUATOR_ENDPOINTS:health,info,metrics}
|
||||
base-path: /actuator
|
||||
endpoint:
|
||||
health:
|
||||
show-details: ${ACTUATOR_HEALTH_DETAILS:when-authorized}
|
||||
info:
|
||||
enabled: true
|
||||
info:
|
||||
env:
|
||||
enabled: true
|
||||
java:
|
||||
enabled: true
|
||||
os:
|
||||
enabled: true
|
||||
|
||||
# 应用信息配置
|
||||
info:
|
||||
app:
|
||||
name: ${spring.application.name}
|
||||
description: StdProject Backend Application
|
||||
version: 1.0.0
|
||||
encoding: UTF-8
|
||||
java:
|
||||
version: ${java.version}
|
||||
|
||||
---
|
||||
# 开发环境配置
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: dev
|
||||
jpa:
|
||||
show-sql: true
|
||||
security:
|
||||
jwt:
|
||||
enabled: false
|
||||
logging:
|
||||
level:
|
||||
com.stdproject: DEBUG
|
||||
org.hibernate.SQL: DEBUG
|
||||
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
enabled: true
|
||||
|
||||
---
|
||||
# 生产环境配置
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: prod
|
||||
jpa:
|
||||
show-sql: false
|
||||
security:
|
||||
jwt:
|
||||
enabled: true
|
||||
logging:
|
||||
level:
|
||||
root: WARN
|
||||
com.stdproject: INFO
|
||||
org.springframework.security: ERROR
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl
|
||||
springdoc:
|
||||
api-docs:
|
||||
enabled: false
|
||||
swagger-ui:
|
||||
enabled: false
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info
|
58
backend/src/main/resources/ehcache.xml
Normal file
58
backend/src/main/resources/ehcache.xml
Normal file
@ -0,0 +1,58 @@
|
||||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.ehcache.org/v3"
|
||||
xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd">
|
||||
|
||||
<!-- 默认缓存配置 -->
|
||||
<cache-template name="default">
|
||||
<expiry>
|
||||
<ttl unit="seconds">3600</ttl> <!-- 默认1小时过期 -->
|
||||
</expiry>
|
||||
<resources>
|
||||
<heap unit="entries">1000</heap> <!-- 堆内存中最多缓存1000个条目 -->
|
||||
<offheap unit="MB">10</offheap> <!-- 堆外内存最多10MB -->
|
||||
</resources>
|
||||
</cache-template>
|
||||
|
||||
<!-- 用户信息缓存 -->
|
||||
<cache alias="userCache" uses-template="default">
|
||||
<expiry>
|
||||
<ttl unit="minutes">30</ttl> <!-- 用户信息缓存30分钟 -->
|
||||
</expiry>
|
||||
</cache>
|
||||
|
||||
<!-- 角色信息缓存 -->
|
||||
<cache alias="roleCache" uses-template="default">
|
||||
<expiry>
|
||||
<ttl unit="minutes">60</ttl> <!-- 角色信息缓存60分钟 -->
|
||||
</expiry>
|
||||
</cache>
|
||||
|
||||
<!-- 菜单信息缓存 -->
|
||||
<cache alias="menuCache" uses-template="default">
|
||||
<expiry>
|
||||
<ttl unit="minutes">60</ttl> <!-- 菜单信息缓存60分钟 -->
|
||||
</expiry>
|
||||
</cache>
|
||||
|
||||
<!-- 权限信息缓存 -->
|
||||
<cache alias="permissionCache" uses-template="default">
|
||||
<expiry>
|
||||
<ttl unit="minutes">60</ttl> <!-- 权限信息缓存60分钟 -->
|
||||
</expiry>
|
||||
</cache>
|
||||
|
||||
<!-- 数据字典缓存 -->
|
||||
<cache alias="dictionaryCache" uses-template="default">
|
||||
<expiry>
|
||||
<ttl unit="hours">2</ttl> <!-- 数据字典缓存2小时 -->
|
||||
</expiry>
|
||||
</cache>
|
||||
|
||||
<!-- 组织机构缓存 -->
|
||||
<cache alias="organizationCache" uses-template="default">
|
||||
<expiry>
|
||||
<ttl unit="hours">1</ttl> <!-- 组织机构缓存1小时 -->
|
||||
</expiry>
|
||||
</cache>
|
||||
|
||||
</config>
|
64
backend/src/main/resources/logback-spring.xml
Normal file
64
backend/src/main/resources/logback-spring.xml
Normal file
@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||
|
||||
<springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="stdproject"/>
|
||||
<property name="LOG_PATH" value="logs/${APP_NAME}"/>
|
||||
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 文件输出 -->
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_PATH}/info.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_PATH}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>30</maxHistory> <!-- 日志文件保留天数 -->
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>INFO</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_PATH}/error.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_PATH}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>ERROR</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<!-- MyBatis Log -->
|
||||
<logger name="com.stdproject.mapper" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="FILE"/>
|
||||
<appender-ref ref="ERROR_FILE"/>
|
||||
</logger>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="FILE"/>
|
||||
<appender-ref ref="ERROR_FILE"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
14
backend/target/classes/.env
Normal file
14
backend/target/classes/.env
Normal file
@ -0,0 +1,14 @@
|
||||
# 数据库配置
|
||||
DB_URL=jdbc:mysql://your-host:3306/your-db
|
||||
DB_USERNAME=your-username
|
||||
DB_PASSWORD=your-password
|
||||
|
||||
# JWT配置
|
||||
JWT_SECRET=your-super-secret-key
|
||||
JWT_EXPIRATION=86400000
|
||||
|
||||
# 环境配置
|
||||
SPRING_PROFILES_ACTIVE=dev
|
||||
|
||||
# CORS配置
|
||||
CORS_ALLOWED_ORIGINS=https://your-frontend.com
|
193
backend/target/classes/application.yml
Normal file
193
backend/target/classes/application.yml
Normal file
@ -0,0 +1,193 @@
|
||||
server:
|
||||
port: 8080
|
||||
servlet:
|
||||
context-path: /
|
||||
encoding:
|
||||
charset: UTF-8
|
||||
enabled: true
|
||||
force: true
|
||||
compression:
|
||||
enabled: true
|
||||
mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
|
||||
min-response-size: 1024
|
||||
|
||||
spring:
|
||||
profiles:
|
||||
active: ${SPRING_PROFILES_ACTIVE:dev}
|
||||
application:
|
||||
name: stdproject-backend
|
||||
datasource:
|
||||
url: ${DB_URL:jdbc:mysql://121.37.111.42:3306/gisbi-demodb?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true}
|
||||
username: ${DB_USERNAME:root}
|
||||
password: ${DB_PASSWORD:mysql_F8ysiK@2024}
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
# HikariCP连接池配置
|
||||
hikari:
|
||||
pool-name: StdProjectHikariCP
|
||||
minimum-idle: 5
|
||||
maximum-pool-size: 20
|
||||
auto-commit: true
|
||||
idle-timeout: 30000
|
||||
max-lifetime: 1800000
|
||||
connection-timeout: 30000
|
||||
connection-test-query: SELECT 1
|
||||
# url: jdbc:sqlite:D:/Trae_space/StdProject/backend/db/project.db
|
||||
# username: # SQLite 不需要用户名
|
||||
# password: # SQLite 不需要密码
|
||||
# driver-class-name: org.sqlite.JDBC
|
||||
# hikari:
|
||||
# pool-name: StdProjectHikariCP
|
||||
# minimum-idle: 5
|
||||
# maximum-pool-size: 20
|
||||
# auto-commit: true
|
||||
# idle-timeout: 30000
|
||||
# max-lifetime: 1800000
|
||||
# connection-timeout: 30000
|
||||
# connection-test-query: SELECT 1
|
||||
|
||||
cache:
|
||||
jcache:
|
||||
config: classpath:ehcache.xml # 指定Ehcache配置文件路径
|
||||
security:
|
||||
cors:
|
||||
allowed-origins: ${CORS_ALLOWED_ORIGINS:http://localhost:3000,http://localhost:8080}
|
||||
max-age: ${CORS_MAX_AGE:3600} # 预检请求的缓存时间(秒)
|
||||
jwt:
|
||||
enabled: ${JWT_ENABLED:true} # 控制是否启用JWT认证
|
||||
secret: ${JWT_SECRET:YourJWTSecretKeyForStdProjectBackendApplicationWhichIsVeryLongAndSecure2024!@#$%^&*()}
|
||||
expiration-ms: ${JWT_EXPIRATION:86400000} # Token 过期时间 (例如: 24小时)
|
||||
refresh-expiration-ms: ${JWT_REFRESH_EXPIRATION:604800000} # 刷新Token过期时间 (例如: 7天)
|
||||
|
||||
mybatis-plus:
|
||||
mapper-locations: classpath*:/mapper/**/*.xml # MyBatis Mapper XML文件位置
|
||||
type-aliases-package: com.stdproject.entity
|
||||
global-config:
|
||||
db-config:
|
||||
id-type: ASSIGN_ID # ID生成策略,assign_id 表示手动分配ID,通常使用雪花算法等
|
||||
table-prefix: ${DB_TABLE_PREFIX:} # 如果表名有统一前缀,可以在这里配置
|
||||
logic-delete-field: deleted # 逻辑删除字段名
|
||||
logic-delete-value: 1 # 逻辑删除值
|
||||
logic-not-delete-value: 0 # 逻辑未删除值
|
||||
banner: false # 关闭MyBatis-Plus启动横幅
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true # 开启驼峰命名转换
|
||||
cache-enabled: true # 开启二级缓存
|
||||
lazy-loading-enabled: true # 开启延迟加载
|
||||
multiple-result-sets-enabled: true # 开启多结果集
|
||||
use-column-label: true # 使用列标签
|
||||
use-generated-keys: true # 使用生成的主键
|
||||
auto-mapping-behavior: partial # 自动映射行为
|
||||
default-executor-type: simple # 默认执行器类型
|
||||
default-statement-timeout: 25000 # 默认语句超时时间
|
||||
log-impl: ${MYBATIS_LOG_IMPL:org.apache.ibatis.logging.nologging.NoLoggingImpl} # SQL日志实现
|
||||
|
||||
logging:
|
||||
config: classpath:logback-spring.xml # Logback配置文件
|
||||
level:
|
||||
root: ${LOG_LEVEL_ROOT:INFO}
|
||||
com.stdproject: ${LOG_LEVEL_APP:DEBUG}
|
||||
org.springframework.security: ${LOG_LEVEL_SECURITY:WARN}
|
||||
org.hibernate.SQL: ${LOG_LEVEL_SQL:WARN}
|
||||
org.hibernate.type.descriptor.sql.BasicBinder: ${LOG_LEVEL_SQL_PARAMS:WARN}
|
||||
pattern:
|
||||
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
|
||||
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
path: /v3/api-docs
|
||||
enabled: ${SWAGGER_ENABLED:true}
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
enabled: ${SWAGGER_UI_ENABLED:true}
|
||||
operations-sorter: alpha
|
||||
tags-sorter: alpha
|
||||
try-it-out-enabled: true
|
||||
filter: true
|
||||
default-consumes-media-type: application/json
|
||||
default-produces-media-type: application/json
|
||||
show-actuator: ${SWAGGER_SHOW_ACTUATOR:true}
|
||||
packages-to-scan: com.stdproject.controller
|
||||
|
||||
# 管理端点配置
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: ${ACTUATOR_ENDPOINTS:health,info,metrics}
|
||||
base-path: /actuator
|
||||
endpoint:
|
||||
health:
|
||||
show-details: ${ACTUATOR_HEALTH_DETAILS:when-authorized}
|
||||
info:
|
||||
enabled: true
|
||||
info:
|
||||
env:
|
||||
enabled: true
|
||||
java:
|
||||
enabled: true
|
||||
os:
|
||||
enabled: true
|
||||
|
||||
# 应用信息配置
|
||||
info:
|
||||
app:
|
||||
name: ${spring.application.name}
|
||||
description: StdProject Backend Application
|
||||
version: 1.0.0
|
||||
encoding: UTF-8
|
||||
java:
|
||||
version: ${java.version}
|
||||
|
||||
---
|
||||
# 开发环境配置
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: dev
|
||||
jpa:
|
||||
show-sql: true
|
||||
security:
|
||||
jwt:
|
||||
enabled: false
|
||||
logging:
|
||||
level:
|
||||
com.stdproject: DEBUG
|
||||
org.hibernate.SQL: DEBUG
|
||||
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
enabled: true
|
||||
|
||||
---
|
||||
# 生产环境配置
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: prod
|
||||
jpa:
|
||||
show-sql: false
|
||||
security:
|
||||
jwt:
|
||||
enabled: true
|
||||
logging:
|
||||
level:
|
||||
root: WARN
|
||||
com.stdproject: INFO
|
||||
org.springframework.security: ERROR
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl
|
||||
springdoc:
|
||||
api-docs:
|
||||
enabled: false
|
||||
swagger-ui:
|
||||
enabled: false
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info
|
BIN
backend/target/classes/com/stdproject/ProjectApplication.class
Normal file
BIN
backend/target/classes/com/stdproject/ProjectApplication.class
Normal file
Binary file not shown.
Binary file not shown.
BIN
backend/target/classes/com/stdproject/common/Constants.class
Normal file
BIN
backend/target/classes/com/stdproject/common/Constants.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
backend/target/classes/com/stdproject/common/OperationLog.class
Normal file
BIN
backend/target/classes/com/stdproject/common/OperationLog.class
Normal file
Binary file not shown.
Binary file not shown.
BIN
backend/target/classes/com/stdproject/common/PageRequest.class
Normal file
BIN
backend/target/classes/com/stdproject/common/PageRequest.class
Normal file
Binary file not shown.
BIN
backend/target/classes/com/stdproject/common/Result.class
Normal file
BIN
backend/target/classes/com/stdproject/common/Result.class
Normal file
Binary file not shown.
BIN
backend/target/classes/com/stdproject/common/ResultCode.class
Normal file
BIN
backend/target/classes/com/stdproject/common/ResultCode.class
Normal file
Binary file not shown.
BIN
backend/target/classes/com/stdproject/config/CacheConfig.class
Normal file
BIN
backend/target/classes/com/stdproject/config/CacheConfig.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
backend/target/classes/com/stdproject/config/SwaggerConfig.class
Normal file
BIN
backend/target/classes/com/stdproject/config/SwaggerConfig.class
Normal file
Binary file not shown.
BIN
backend/target/classes/com/stdproject/config/WebConfig.class
Normal file
BIN
backend/target/classes/com/stdproject/config/WebConfig.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user