Spring Security 是安全框架(负责认证与授权),JWT 是无状态令牌(负责身份传递),二者结合是前后端分离 / 微服务最主流的安全方案。


JWT(JSON Web Token)由三部分组成,用.分隔:Header.Payload.Signature
结构示意图
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. // Header(头部)
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. // Payload(载荷)
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c // Signature(签名)密钥安全:
密钥长度 ≥256 位,生产环境使用环境变量 / 配置中心存储,避免硬编码
定期轮换密钥,可使用 RSA 非对称加密增强安全性
令牌管理:
设置合理过期时间(如 1 小时),使用刷新令牌机制延长会话
前端存储 JWT 在 HttpOnly Cookie 中,防止 XSS 攻击
敏感操作可使用短期令牌,降低泄露风险
密码安全:
强制使用 BCrypt/Argon2 等自适应哈希算法加密存储密码
实施密码复杂度策略(≥12 位,大小写 + 数字 + 特殊字符)
权限控制:
遵循最小权限原则,角色设计清晰,避免权限冗余
同时启用 URL 级和方法级权限控制,双重保障
Spring Security + JWT 组合实现了 认证与授权分离、无状态设计 和 灵活权限控制,是现代 Web 应用的安全标配。通过 JWT 过滤器拦截请求、验证令牌、设置安全上下文,Spring Security 负责权限校验和安全防护,二者协同工作,为前后端分离和微服务架构提供高效、安全的认证解决方案
我们基于之前的项目代码继续追加缺失的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>表结构
sys_config 系统配置表
sys_dept 部门表
sys_dict 字典表
sys_dict_data 字典数据表
sys_menu 菜单表
sys_role 角色表
sys_role_menu 角色菜单关系表
sys_user 用户表
sys_user_role 用户角色关系表对应的 SQL 语句,MySQL 版本
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_config
-- ----------------------------
DROP TABLE IF EXISTS `sys_config`;
CREATE TABLE `sys_config` (
`config_id` bigint NOT NULL AUTO_INCREMENT COMMENT '配置ID',
`config_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '系统内置: Y N ',
`config_key` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '键',
`config_value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '值',
`config_name` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
`status_flag` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1-启用,2-禁用',
`version_flag` bigint NULL DEFAULT NULL COMMENT '乐观锁',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'N' COMMENT '删除标记:Y-已删除,N-未删除',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
`update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
`update_user` bigint NULL DEFAULT NULL COMMENT '修改人',
PRIMARY KEY (`config_id`) USING BTREE,
UNIQUE INDEX `param_key_idx`(`config_key` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统配置表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_config
-- ----------------------------
-- ----------------------------
-- Table structure for sys_dept
-- ----------------------------
DROP TABLE IF EXISTS `sys_dept`;
CREATE TABLE `sys_dept` (
`dept_id` bigint NOT NULL AUTO_INCREMENT COMMENT '部门ID',
`pid` bigint NOT NULL COMMENT '上级部门',
`ancestors` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '祖级列表',
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称',
`sort` int NULL DEFAULT 0 COMMENT '排序',
`leader` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '负责人',
`phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话',
`email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱',
`status_flag` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1-启用,2-禁用',
`version_flag` bigint NULL DEFAULT NULL COMMENT '乐观锁',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'N' COMMENT '删除标记:Y-已删除,N-未删除',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
`update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
`update_user` bigint NULL DEFAULT NULL COMMENT '修改人',
PRIMARY KEY (`dept_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '部门表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_dept
-- ----------------------------
INSERT INTO `sys_dept` VALUES (1, 0, '0', '总部', 0, 'zongbu', '18888888888', '18888888888@163.com', 1, NULL, 'N', '2024-12-02 15:35:36', NULL, '2024-12-02 15:36:08', NULL);
INSERT INTO `sys_dept` VALUES (2, 1, '0,1', '研发部', 0, 'yanfa', '18888888888', '18888888888@163.com', 1, NULL, 'N', '2024-12-02 15:35:36', NULL, '2024-12-02 15:35:59', NULL);
INSERT INTO `sys_dept` VALUES (3, 1, '0,1', '营销部', 1, 'xiaoshou', '18888888888', NULL, 1, NULL, 'N', '2024-12-02 15:35:36', NULL, '2024-12-02 15:35:57', NULL);
INSERT INTO `sys_dept` VALUES (4, 1, '0,1', '产品部', 1, 'chanpin', NULL, NULL, 1, NULL, 'N', '2024-12-02 15:35:36', NULL, '2024-12-02 15:35:36', NULL);
-- ----------------------------
-- Table structure for sys_dict
-- ----------------------------
DROP TABLE IF EXISTS `sys_dict`;
CREATE TABLE `sys_dict` (
`dict_id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典名称',
`type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典类型',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述',
`status_flag` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1-启用,2-禁用',
`version_flag` bigint NULL DEFAULT NULL COMMENT '乐观锁',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'N' COMMENT '删除标记:Y-已删除,N-未删除',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
`update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
`update_user` bigint NULL DEFAULT NULL COMMENT '修改人',
PRIMARY KEY (`dict_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '数据字典' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_dict
-- ----------------------------
INSERT INTO `sys_dict` VALUES (1, '用户性别', 'sys_user_sex', '用户性别', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:12', NULL);
INSERT INTO `sys_dict` VALUES (2, '菜单状态', 'sys_show_hide', '菜单状态', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:14', NULL);
INSERT INTO `sys_dict` VALUES (3, '系统开关', 'sys_normal_disable', '系统开关', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:15', NULL);
INSERT INTO `sys_dict` VALUES (4, '任务状态', 'sys_job_status', '任务状态', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:16', NULL);
INSERT INTO `sys_dict` VALUES (5, '任务分组', 'sys_job_group', '任务分组', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:17', NULL);
INSERT INTO `sys_dict` VALUES (6, '系统是否', 'sys_yes_no', '系统是否', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:18', NULL);
INSERT INTO `sys_dict` VALUES (7, '通知类型', 'sys_notice_type', '通知类型', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:19', NULL);
INSERT INTO `sys_dict` VALUES (8, '通知状态', 'sys_notice_status', '通知状态', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:21', NULL);
INSERT INTO `sys_dict` VALUES (9, '操作类型', 'sys_oper_type', '操作类型', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:22', NULL);
INSERT INTO `sys_dict` VALUES (10, '系统状态', 'sys_common_status', '系统状态', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:24', NULL);
INSERT INTO `sys_dict` VALUES (11, '用户岗位', 'sys_user_post', '用户岗位', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:25', NULL);
INSERT INTO `sys_dict` VALUES (12, '菜单类型', 'sys_menu_type', '权限类型:0->目录;1->菜单;2->按钮', 1, NULL, 'N', '2024-12-02 14:51:06', NULL, '2024-12-02 14:51:27', NULL);
-- ----------------------------
-- Table structure for sys_dict_data
-- ----------------------------
DROP TABLE IF EXISTS `sys_dict_data`;
CREATE TABLE `sys_dict_data` (
`dict_data_id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
`dict_sort` int NULL DEFAULT 0 COMMENT '字典排序',
`dict_label` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典标签',
`dict_value` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典键值',
`dict_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典类型',
`css_class` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '样式属性(其他样式扩展)',
`list_class` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '表格回显样式',
`is_default` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'N' COMMENT '是否默认,Y:是 N:否',
`status_flag` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1-启用,2-禁用',
`version_flag` bigint NULL DEFAULT NULL COMMENT '乐观锁',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'N' COMMENT '删除标记:Y-已删除,N-未删除',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
`update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
`update_user` bigint NULL DEFAULT NULL COMMENT '修改人',
PRIMARY KEY (`dict_data_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 37 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '字典数据表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_dict_data
-- ----------------------------
INSERT INTO `sys_dict_data` VALUES (1, 1, '男', '0', 'sys_user_sex', '', '', 'Y', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:52:51', NULL);
INSERT INTO `sys_dict_data` VALUES (2, 2, '女', '1', 'sys_user_sex', '', '', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:52:52', NULL);
INSERT INTO `sys_dict_data` VALUES (3, 3, '未知', '2', 'sys_user_sex', '', '', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:52:52', NULL);
INSERT INTO `sys_dict_data` VALUES (4, 1, '显示', '1', 'sys_show_hide', '', 'primary', 'Y', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:52:53', NULL);
INSERT INTO `sys_dict_data` VALUES (5, 2, '隐藏', '0', 'sys_show_hide', '', 'danger', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:52:54', NULL);
INSERT INTO `sys_dict_data` VALUES (6, 1, '正常', '1', 'sys_normal_disable', '', 'primary', 'Y', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:52:55', NULL);
INSERT INTO `sys_dict_data` VALUES (7, 2, '停用', '0', 'sys_normal_disable', '', 'danger', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:52:55', NULL);
INSERT INTO `sys_dict_data` VALUES (8, 1, '正常', '1', 'sys_job_status', '', 'primary', 'Y', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:52:56', NULL);
INSERT INTO `sys_dict_data` VALUES (9, 2, '暂停', '1', 'sys_job_status', '', 'danger', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:52:58', NULL);
INSERT INTO `sys_dict_data` VALUES (10, 1, '默认', 'DEFAULT', 'sys_job_group', '', 'primary', 'Y', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:00', NULL);
INSERT INTO `sys_dict_data` VALUES (11, 2, '系统', 'SYSTEM', 'sys_job_group', '', 'primary', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:02', NULL);
INSERT INTO `sys_dict_data` VALUES (12, 1, '是', 'Y', 'sys_yes_no', '', 'primary', 'Y', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:05', NULL);
INSERT INTO `sys_dict_data` VALUES (13, 2, '否', 'N', 'sys_yes_no', '', 'danger', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:07', NULL);
INSERT INTO `sys_dict_data` VALUES (14, 1, '通知', '1', 'sys_notice_type', '', 'warning', 'Y', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:08', NULL);
INSERT INTO `sys_dict_data` VALUES (15, 2, '公告', '2', 'sys_notice_type', '', 'success', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:10', NULL);
INSERT INTO `sys_dict_data` VALUES (16, 1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:12', NULL);
INSERT INTO `sys_dict_data` VALUES (17, 2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:13', NULL);
INSERT INTO `sys_dict_data` VALUES (18, 1, '新增', '1', 'sys_oper_type', '', 'info', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:16', NULL);
INSERT INTO `sys_dict_data` VALUES (19, 2, '修改', '2', 'sys_oper_type', '', 'info', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:18', NULL);
INSERT INTO `sys_dict_data` VALUES (20, 3, '删除', '3', 'sys_oper_type', '', 'danger', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:21', NULL);
INSERT INTO `sys_dict_data` VALUES (21, 4, '授权', '4', 'sys_oper_type', '', 'primary', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:22', NULL);
INSERT INTO `sys_dict_data` VALUES (22, 5, '导出', '5', 'sys_oper_type', '', 'warning', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:23', NULL);
INSERT INTO `sys_dict_data` VALUES (23, 6, '导入', '6', 'sys_oper_type', '', 'warning', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:24', NULL);
INSERT INTO `sys_dict_data` VALUES (24, 7, '强退', '7', 'sys_oper_type', '', 'danger', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:26', NULL);
INSERT INTO `sys_dict_data` VALUES (25, 8, '生成代码', '8', 'sys_oper_type', '', 'warning', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:27', NULL);
INSERT INTO `sys_dict_data` VALUES (26, 9, '清空数据', '9', 'sys_oper_type', '', 'danger', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:28', NULL);
INSERT INTO `sys_dict_data` VALUES (27, 1, '成功', '1', 'sys_common_status', '', 'primary', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:30', NULL);
INSERT INTO `sys_dict_data` VALUES (28, 2, '失败', '0', 'sys_common_status', '', 'danger', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:32', NULL);
INSERT INTO `sys_dict_data` VALUES (29, 1, '董事长', '1', 'sys_user_post', NULL, NULL, 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:33', NULL);
INSERT INTO `sys_dict_data` VALUES (30, 2, '项目经理', '2', 'sys_user_post', NULL, NULL, 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:35', NULL);
INSERT INTO `sys_dict_data` VALUES (31, 3, '人力资源', '3', 'sys_user_post', NULL, NULL, 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:36', NULL);
INSERT INTO `sys_dict_data` VALUES (32, 4, '产品经理', '4', 'sys_user_post', NULL, NULL, 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:38', NULL);
INSERT INTO `sys_dict_data` VALUES (33, 5, '普通员工', '5', 'sys_user_post', NULL, NULL, 'Y', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:39', NULL);
INSERT INTO `sys_dict_data` VALUES (34, 1, '目录', '0', 'sys_menu_type', NULL, 'primary', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:53:41', NULL);
INSERT INTO `sys_dict_data` VALUES (35, 2, '菜单', '1', 'sys_menu_type', NULL, 'success', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:54:55', NULL);
INSERT INTO `sys_dict_data` VALUES (36, 3, '按钮', '2', 'sys_menu_type', NULL, 'warning', 'N', 1, NULL, 'N', '2024-12-02 14:52:18', NULL, '2024-12-02 14:54:57', NULL);
-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
`menu_id` bigint NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
`pid` bigint NULL DEFAULT NULL COMMENT '父级菜单id',
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称',
`permission` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限标识',
`icon` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图标',
`type` int NULL DEFAULT NULL COMMENT '权限类型,0:目录 1:菜单 2:按钮(接口绑定权限)',
`uri` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '前端资源路径',
`sort` int NULL DEFAULT NULL COMMENT '排序',
`outer_link` int NULL DEFAULT 0 COMMENT '是否为外链, 0:否 1:是',
`path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路由地址',
`status_flag` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1-启用,2-禁用',
`version_flag` bigint NULL DEFAULT NULL COMMENT '乐观锁',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'N' COMMENT '删除标记:Y-已删除,N-未删除',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
`update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
`update_user` bigint NULL DEFAULT NULL COMMENT '修改人',
PRIMARY KEY (`menu_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1036 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户权限表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_menu
-- ----------------------------
INSERT INTO `sys_menu` VALUES (1, 0, '系统管理', '', 'system', 0, '', 1, 0, 'system', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (100, 1, '用户管理', 'sys_user_page', 'user', 1, 'system/user/index', 1, 0, 'user', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (101, 1, '角色管理', 'sys_role_page', 'peoples', 1, 'system/role/index', 2, 0, 'role', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (102, 1, '菜单管理', 'sys_menu_page', 'tree-table', 1, 'system/menu/index', 3, 0, 'menu', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (103, 1, '部门管理', 'sys_dept_page', 'tree', 1, 'system/dept/index', 4, 0, 'dept', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (105, 1, '字典管理', 'sys_dict_page', 'dict', 1, 'system/dict/index', 6, 0, 'dict', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (106, 1, '参数设置', 'sys_config_page', 'edit', 1, 'system/config/index', 7, 0, 'config', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1001, 100, '用户查询', 'sys_user_get', '', 2, '', 1, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1002, 100, '用户新增', 'sys_user_add', '', 2, '', 2, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1003, 100, '用户修改', 'sys_user_edit', '', 2, '', 3, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1004, 100, '用户删除', 'sys_user_del', '', 2, '', 4, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1005, 100, '用户导出', 'sys_user_export', '', 2, '', 5, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1006, 100, '用户导入', 'sys_user_import', '', 2, '', 6, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1007, 100, '重置密码', 'sys_user_reset', '', 2, '', 7, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1008, 101, '角色查询', 'sys_role_get', '', 2, '', 1, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1009, 101, '角色新增', 'sys_role_add', '', 2, '', 2, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1010, 101, '角色修改', 'sys_role_edit', '', 2, '', 3, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1011, 101, '角色删除', 'sys_role_del', '', 2, '', 4, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1012, 101, '角色导出', 'sys_role_export', '', 2, '', 5, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1013, 102, '菜单查询', 'sys_menu_get', '', 2, '', 1, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1014, 102, '菜单新增', 'sys_menu_add', '', 2, '', 2, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1015, 102, '菜单修改', 'sys_menu_edit', '', 2, '', 3, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1016, 102, '菜单删除', 'sys_menu_del', '', 2, '', 4, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1017, 103, '部门查询', 'sys_dept_get', '', 2, '', 1, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1018, 103, '部门新增', 'sys_dept_add', '', 2, '', 2, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1019, 103, '部门修改', 'sys_dept_edit', '', 2, '', 3, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1020, 103, '部门删除', 'sys_dept_del', '', 2, '', 4, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1026, 105, '字典查询', 'sys_dict_get', '', 2, '', 1, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1027, 105, '字典新增', 'sys_dict_add', '', 2, '', 2, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1028, 105, '字典修改', 'sys_dict_edit', '', 2, '', 3, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1029, 105, '字典删除', 'sys_dict_del', '', 2, '', 4, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1030, 105, '字典导出', 'sys_dict_export', '', 2, '', 5, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1031, 106, '参数查询', 'sys_config_get', '', 2, '', 1, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1032, 106, '参数新增', 'sys_config_add', '', 2, '', 2, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1033, 106, '参数修改', 'sys_config_edit', '', 2, '', 3, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1034, 106, '参数删除', 'sys_config_del', '', 2, '', 4, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
INSERT INTO `sys_menu` VALUES (1035, 106, '参数导出', 'sys_config_export', '', 2, '', 5, 0, '', 1, NULL, 'N', '2024-12-02 14:56:50', NULL, NULL, NULL);
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`role_id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称',
`role_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色权限字符',
`data_scope` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 )',
`description` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述',
`user_count` int NULL DEFAULT NULL COMMENT '后台用户数量',
`sort` int NULL DEFAULT 0 COMMENT '排序',
`status_flag` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1-启用,2-禁用',
`version_flag` bigint NULL DEFAULT NULL COMMENT '乐观锁',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'N' COMMENT '删除标记:Y-已删除,N-未删除',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
`update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
`update_user` bigint NULL DEFAULT NULL COMMENT '修改人',
PRIMARY KEY (`role_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, '管理员', 'ROOT', '1', '超级管理员', 1, 0, 1, NULL, 'N', '2024-12-02 10:57:02', NULL, NULL, NULL);
INSERT INTO `sys_role` VALUES (2, '测试', 'TEST', '1', '测试角色', 1, 0, 1, NULL, 'N', '2024-12-02 15:21:49', NULL, NULL, NULL);
-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu` (
`role_id` bigint NULL DEFAULT NULL COMMENT '角色ID',
`menu_id` bigint NULL DEFAULT NULL COMMENT '菜单ID',
`status_flag` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1-启用,2-禁用',
`version_flag` bigint NULL DEFAULT NULL COMMENT '乐观锁',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'N' COMMENT '删除标记:Y-已删除,N-未删除',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
`update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
`update_user` bigint NULL DEFAULT NULL COMMENT '修改人'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色和菜单关系表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_role_menu
-- ----------------------------
INSERT INTO `sys_role_menu` VALUES (2, 1, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 100, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 101, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 102, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 103, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 106, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 500, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 501, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1001, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1002, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1003, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1004, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1005, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1006, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1007, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1008, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1009, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1010, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1011, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1012, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1013, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1014, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1015, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1016, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1017, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1018, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1019, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1020, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1031, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1032, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1033, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1034, 1, NULL, 'N', NULL, NULL, NULL, NULL);
INSERT INTO `sys_role_menu` VALUES (2, 1035, 1, NULL, 'N', NULL, NULL, NULL, NULL);
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名',
`password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码',
`dept_id` bigint NULL DEFAULT NULL COMMENT '所属部门',
`dept_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '所属部门名称',
`post_ids` json NULL COMMENT '岗位组',
`icon` varchar(500) 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(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号',
`nick_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称',
`sex` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '性别,0:男 1:女 2:未知',
`note` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注信息',
`login_time` datetime NULL DEFAULT NULL COMMENT '最后登录时间',
`login_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '最后登陆IP',
`status_flag` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1-启用,2-禁用',
`version_flag` bigint NULL DEFAULT NULL COMMENT '乐观锁',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'N' COMMENT '删除标记:Y-已删除,N-未删除',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
`update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
`update_user` bigint NULL DEFAULT NULL COMMENT '修改人',
PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 102 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'admin', '$2a$10$Wr0wX8Ueiox9ndNXsrx8JOfe3sc6QIsTW4JW/rPR6.7iAzrdKLYSe', 1, '总部', '[]', 'https://cdn-icons-png.flaticon.com/512/3135/3135715.png', 'test@qq.com', '17777777777', 'Admin', '0', NULL, '2024-11-20 17:25:53', '192.168.31.7', 1, NULL, 'N', '2019-09-29 13:55:30', NULL, '2024-12-02 10:59:46', 1);
INSERT INTO `sys_user` VALUES (101, 'guest', '$2a$10$Wr0wX8Ueiox9ndNXsrx8JOfe3sc6QIsTW4JW/rPR6.7iAzrdKLYSe', 2, '研发部', '[\"3\"]', '', 'test@qq.com', '17777777777', 'test', '1', NULL, '2024-11-20 17:32:24', '192.168.31.7', 1, NULL, 'N', '2019-09-29 13:55:30', NULL, '2024-12-02 10:59:49', 101);
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`user_id` bigint NULL DEFAULT NULL COMMENT '用户ID',
`role_id` bigint NULL DEFAULT NULL COMMENT '角色ID',
`status_flag` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1-启用,2-禁用',
`version_flag` bigint NULL DEFAULT NULL COMMENT '乐观锁',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'N' COMMENT '删除标记:Y-已删除,N-未删除',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
`update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
`update_user` bigint NULL DEFAULT NULL COMMENT '修改人'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户和角色关系表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (101, 2, 1, NULL, 'N', NULL, NULL, NULL, NULL);
SET FOREIGN_KEY_CHECKS = 1;我们使用 MyBatis-Plus Generator UI 代码生成器
创建一个名为 generator 的目录专门用于启动代码生成器,pom.xml 配置如下
<?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.5.13</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lusifer</groupId>
<artifactId>generator</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>generator</name>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.github.davidfantasy</groupId>
<artifactId>mybatis-plus-generator-ui</artifactId>
<version>2.0.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>在 src/test/java 创建 com.lusifer.generator.GeneratorUIServer 用于启动服务器,因为依赖范围是 test
package com.lusifer.generator;
import com.github.davidfantasy.mybatisplus.generatorui.GeneratorConfig;
import com.github.davidfantasy.mybatisplus.generatorui.MybatisPlusToolsApplication;
import com.github.davidfantasy.mybatisplus.generatorui.mbp.NameConverter;
public class GeneratorUIServer {
public static void main(String[] args) {
// 构建代码生成器核心配置
GeneratorConfig config =
GeneratorConfig.builder()
// 数据库连接 URL,推荐追加编码、时区等参数
.jdbcUrl(
"jdbc:mysql://192.168.203.128:3306/mycrmeb?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8")
// 数据库登录用户名
.userName("root")
// 数据库登录密码
.password("123456")
// MySQL 8.0 + 版本驱动类
.driverClassName("com.mysql.cj.jdbc.Driver")
// 数据库 schema(仅 MSSQL、PGSQL、ORACLE、DB2 需要配置)
// .schemaName("myBusiness")
// 数据库表前缀,生成实体类时自动去除(如 t_user → User)
// .tablePrefix("t_")
// 自定义命名规则,可重写方法修改类名生成规范
.nameConverter(
new NameConverter() {
/**
* 自定义 Service 类命名规则
*
* @param entityName 处理后的实体类名称
* @return 最终生成的 Service 类名
*/
@Override
public String serviceNameConvert(String entityName) {
return entityName + "Service";
}
/**
* 自定义 Controller 类命名规则
*
* @param entityName 处理后的实体类名称
* @return 最终生成的 Controller 类名
*/
@Override
public String controllerNameConvert(String entityName) {
return entityName + "Controller";
}
})
// 生成所有 Java 文件的根包名
.basePackage("com.lusifer.crmeb")
// 可视化界面启动端口
.port(8068)
.build();
// 启动代码生成器服务
MybatisPlusToolsApplication.run(config);
}
}访问 http://localhost:8068 地址
Entity
Entity 超类名称:com.lusifer.crmeb.rule.pojo.entity.BaseBusinessEntity
Entity 公共字段:version_flag del_flag create_time create_user update_time update_user
Mapper.xml

Mapper.java
com.baomidou.mybatisplus.core.mapper.BaseMapper
Service
com.baomidou.mybatisplus.extension.service.IService
ServiceImpl
com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
Controller

注意:生成后将代码复制到项目中
由于在配置文件中设置了 mapping 路径的位置,注意观察自己的配置文件,所以将 Mapper.xml 放在 mapper/mapping 下



代码需要进行简单的修改,下面给出修改后的案例
Entity
特别注意:statusFlag 字段的数据类型应该是【Character】,而不是生成的【byte】
package com.lusifer.crmeb.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.lusifer.crmeb.rule.pojo.entity.BaseBusinessEntity;
import java.io.Serial;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 系统配置表
*
* @author Lusifer
* @since 2026-05-04
*/
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("sys_config")
public class SysConfig extends BaseBusinessEntity {
@Serial private static final long serialVersionUID = 1L;
/** 配置ID */
@TableId(value = "config_id", type = IdType.ASSIGN_ID)
private Long configId;
/** 系统内置: Y N */
private String configType;
/** 键 */
private String configKey;
/** 值 */
private String configValue;
/** 名称 */
private String configName;
/** 备注 */
private String remark;
/** 状态:1-启用,2-禁用 */
private Character statusFlag;
}Mapper
package com.lusifer.crmeb.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lusifer.crmeb.entity.SysConfig;
/**
* 系统配置表 Mapper 接口
*
* @author Lusifer
* @since 2026-05-04
*/
public interface SysConfigMapper extends BaseMapper<SysConfig> {}Mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lusifer.crmeb.mapper.SysConfigMapper">
</mapper>Service
package com.lusifer.crmeb.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.lusifer.crmeb.entity.SysConfig;
/**
* 系统配置表 服务类
*
* @author Lusifer
* @since 2026-05-04
*/
public interface SysConfigService extends IService<SysConfig> {}ServiceImpl
package com.lusifer.crmeb.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lusifer.crmeb.entity.SysConfig;
import com.lusifer.crmeb.mapper.SysConfigMapper;
import com.lusifer.crmeb.service.SysConfigService;
import org.springframework.stereotype.Service;
/**
* 系统配置表 服务实现类
*
* @author Lusifer
* @since 2026-05-04
*/
@Service
public class SysConfigServiceImpl extends ServiceImpl<SysConfigMapper, SysConfig>
implements SysConfigService {}Controller
package com.lusifer.crmeb.controller;
import com.lusifer.crmeb.service.SysConfigService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
* 系统配置表 前端控制器
*
* @author Lusifer
* @since 2026-05-04
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/sys/config")
public class SysConfigController {
private final SysConfigService sysConfigService;
}修改 application.yml 配置文件
security:
jwt:
# JWT 秘钥
# 可以使用密码生成器生成
key: ZajKtj8qZudAxojmvUsTNWV4FQgkSOkY0oReC2g7MC5zm1qelsv0VCH1gYfIZEdx
# JWT 有效期(单位:秒)
ttl: 7200
# 白名单列表(因为集成了 swagger 需要将下列路劲配置到白名单)
ignore-urls:
- /v3/api-docs/**
- /doc.html
- /swagger-resources/**
- /webjars/**
- /swagger-ui/**
- /swagger-ui.html
- /auth/login
- /auth/captcha
- /ws/**com.lusifer.crmeb.config.properties.SecurityProperties
package com.lusifer.crmeb.config.properties;
import java.util.List;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/** Security Properties */
@Data
@Component
@ConfigurationProperties(prefix = "security")
public class SecurityProperties {
/** 白名单 URL 集合 */
private List<String> ignoreUrls;
/** JWT 配置 */
private JwtProperty jwt;
/** JWT 配置 */
@Data
public static class JwtProperty {
/** JWT 密钥 */
private String key;
/** JWT 过期时间 */
private Long ttl;
}
}com.lusifer.crmeb.rule.constants.SecurityConstants
package com.lusifer.crmeb.rule.constants;
/** 安全模块常量 */
public interface SecurityConstants {
/** 验证码缓存前缀 */
String CAPTCHA_CODE_PREFIX = "captcha_code:";
/** 角色和权限缓存前缀 */
String ROLE_PERMS_PREFIX = "role_perms:";
/** 黑名单Token缓存前缀 */
String BLACKLIST_TOKEN_PREFIX = "token:blacklist:";
/** 登录路径 */
String LOGIN_PATH = "/auth/login";
/** JWT Token 前缀 */
String JWT_TOKEN_PREFIX = "Bearer ";
}com.lusifer.crmeb.exception.enums.SecurityExceptionEnum
package com.lusifer.crmeb.exception.enums;
import com.lusifer.crmeb.exception.AbstractExceptionEnum;
import lombok.Getter;
/** 安全相关错误 */
@Getter
public enum SecurityExceptionEnum implements AbstractExceptionEnum {
/** 未登录 */
UNAUTHORIZED("401", "暂未登录或 token 已失效"),
/** 未授权 */
FORBIDDEN("403", "没有相关权限");
/** 错误编码 */
private final String errorCode;
/** 提示用户信息 */
private final String userTip;
SecurityExceptionEnum(String errorCode, String userTip) {
this.errorCode = errorCode;
this.userTip = userTip;
}
}com.lusifer.crmeb.exception.enums.SysExceptionEnum
package com.lusifer.crmeb.exception.enums;
import com.lusifer.crmeb.exception.AbstractExceptionEnum;
import lombok.Getter;
/** 系统模块异常枚举 */
@Getter
public enum SysExceptionEnum implements AbstractExceptionEnum {
ERROR_USERNAME_OR_PASSWORD("1001", "用户名或密码错误"),
USER_DISABLED("1002", "您的账号已被停用"),
USER_NOT_EXIST("1003", "用户不存在");
/** 错误编码 */
private final String errorCode;
/** 提示用户信息 */
private final String userTip;
SysExceptionEnum(String errorCode, String userTip) {
this.errorCode = errorCode;
this.userTip = userTip;
}
}com.lusifer.crmeb.component.security.handle.RestAuthenticationEntryPoint
package com.lusifer.crmeb.component.security.handle;
import cn.hutool.json.JSONUtil;
import com.lusifer.crmeb.rule.pojo.response.ErrorResponseData;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
/** 当未登录或者 token 失效访问接口时,自定义的返回结果 */
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException)
throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response
.getWriter()
.println(
JSONUtil.toJsonStr(
new ErrorResponseData<>("401", "暂未登录或 token 已失效", authException.getMessage())));
response.getWriter().flush();
}
}com.lusifer.crmeb.component.security.handle.RestfulAccessDeniedHandler
package com.lusifer.crmeb.component.security.handle;
import cn.hutool.json.JSONUtil;
import com.lusifer.crmeb.rule.pojo.response.ErrorResponseData;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
/** 当访问接口没有权限时,自定义的返回结果 */
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(
HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)
throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response
.getWriter()
.println(JSONUtil.toJsonStr(new ErrorResponseData<>("403", "没有相关权限", e.getMessage())));
response.getWriter().flush();
}
}处理枚举 com.lusifer.crmeb.rule.enums.StatusEnum
package com.lusifer.crmeb.rule.enums;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.lusifer.crmeb.rule.base.ReadableEnum;
import lombok.Getter;
/** 公共状态,一般用来表示开启和关闭 */
@Getter
public enum StatusEnum implements ReadableEnum<StatusEnum> {
/** 启用 */
ENABLE('1', "启用"),
/** 禁用 */
DISABLE('2', "禁用");
@EnumValue @JsonValue private final Character code;
private final String message;
StatusEnum(Character code, String message) {
this.code = code;
this.message = message;
}
/** 根据 code 获取枚举 */
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static StatusEnum codeToEnum(Character code) {
if (null != code) {
for (StatusEnum item : StatusEnum.values()) {
if (item.getCode().equals(code)) {
return item;
}
}
}
return null;
}
@Override
public Object getKey() {
return this.code;
}
@Override
public Object getName() {
return this.message;
}
@Override
public StatusEnum parseToEnum(String originValue) {
if (ObjectUtil.isEmpty(originValue)) {
return null;
}
for (StatusEnum value : StatusEnum.values()) {
if (value.code.equals(originValue.charAt(0))) {
return value;
}
}
return null;
}
}com.lusifer.crmeb.component.security.model.SysUserDetails
package com.lusifer.crmeb.component.security.model;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.lusifer.crmeb.entity.SysUser;
import com.lusifer.crmeb.rule.enums.StatusEnum;
import java.util.Collection;
import java.util.Set;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
/** SpringSecurity 需要的用户详情 */
@Data
@NoArgsConstructor
@JsonIgnoreProperties({
"enabled",
"accountNonExpired",
"accountNonLocked",
"credentialsNonExpired",
"authorities",
"password"
})
public class SysUserDetails implements UserDetails {
private String username;
private SysUser sysUser;
private Set<String> permissions;
private Set<String> roles;
private Integer dataScope;
public SysUserDetails(
SysUser user,
Set<String> permissions,
Set<String> roles,
String username,
Integer dataScope) {
this.sysUser = user;
this.permissions = permissions;
this.roles = roles;
this.username = username;
this.dataScope = dataScope;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 返回当前用户的权限
return permissions.stream()
.filter(StrUtil::isNotEmpty)
.map(SimpleGrantedAuthority::new)
.toList();
}
@Override
public String getPassword() {
return sysUser.getPassword();
}
@Override
public String getUsername() {
return this.username;
}
/**
* 是否可用,禁用的用户不能身份验证
*
* @return 是否可用
*/
@Override
public boolean isEnabled() {
return StatusEnum.ENABLE.getKey().equals(sysUser.getStatusFlag());
}
}com.lusifer.crmeb.component.security.filter.JwtValidationFilter
package com.lusifer.crmeb.component.security.filter;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import cn.hutool.jwt.RegisteredPayload;
import com.lusifer.crmeb.component.security.model.SysUserDetails;
import com.lusifer.crmeb.exception.base.ServiceException;
import com.lusifer.crmeb.exception.enums.SecurityExceptionEnum;
import com.lusifer.crmeb.rule.constants.SecurityConstants;
import jakarta.annotation.Nonnull;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
/** JWT 登录授权过滤器 */
@Slf4j
public class JwtValidationFilter extends OncePerRequestFilter {
private final UserDetailsService userDetailsService;
// 密钥
private final byte[] secretKey;
public JwtValidationFilter(UserDetailsService userDetailsService, String secretKey) {
this.userDetailsService = userDetailsService;
this.secretKey = secretKey.getBytes();
}
@Override
protected void doFilterInternal(
HttpServletRequest request, @Nonnull HttpServletResponse response, @Nonnull FilterChain chain)
throws ServletException, IOException {
// 获取请求 token
String token = request.getHeader(HttpHeaders.AUTHORIZATION);
try {
// 如果请求头中没有 Authorization 信息,或者 Authorization 以 Bearer 开头,则认为是匿名用户
if (CharSequenceUtil.isBlank(token)
|| !token.startsWith(SecurityConstants.JWT_TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
// 去除 Bearer 前缀
token = token.substring(SecurityConstants.JWT_TOKEN_PREFIX.length());
// 解析 Token
JWT jwt = JWTUtil.parseToken(token);
// 检查 Token 是否有效(验签 + 是否过期)
boolean isValidate = jwt.setKey(secretKey).validate(0);
if (!isValidate) {
log.error("JwtValidationFilter error: token is invalid");
throw new ServiceException(SecurityExceptionEnum.UNAUTHORIZED);
}
JSONObject payloads = jwt.getPayloads();
String username = payloads.getStr(RegisteredPayload.SUBJECT);
SysUserDetails userDetails =
(SysUserDetails) this.userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
log.error("JwtValidationFilter error: {}", e.getMessage());
SecurityContextHolder.clearContext();
throw new ServiceException(SecurityExceptionEnum.UNAUTHORIZED);
}
// Token 有效或无 Token 时继续执行过滤链
chain.doFilter(request, response);
}
}com.lusifer.crmeb.config.ProjectSecurityAutoConfiguration
package com.lusifer.crmeb.config;
import com.lusifer.crmeb.component.security.filter.JwtValidationFilter;
import com.lusifer.crmeb.component.security.handle.RestAuthenticationEntryPoint;
import com.lusifer.crmeb.component.security.handle.RestfulAccessDeniedHandler;
import com.lusifer.crmeb.config.properties.SecurityProperties;
import lombok.RequiredArgsConstructor;
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.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
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;
/** Spring Security 权限配置 */
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true) // 开启方法级别的权限控制
@RequiredArgsConstructor
public class ProjectSecurityAutoConfiguration {
private final RestfulAccessDeniedHandler restfulAccessDeniedHandler;
private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;
private final SecurityProperties securityProperties;
private final UserDetailsService userDetailsService;
@Bean
protected SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// 忽略的路径
http.authorizeHttpRequests(
requestMatcherRegistry ->
requestMatcherRegistry
.requestMatchers(securityProperties.getIgnoreUrls().toArray(new String[0]))
.permitAll()
.anyRequest()
.authenticated());
http
// 由于使用的是 JWT,我们这里不需要 CSRF
.csrf(AbstractHttpConfigurer::disable)
// 禁用 Session
.sessionManagement(
configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
// 添加自定义未授权和未登录结果返回
http.exceptionHandling(
customizer ->
customizer
// 处理未授权
.accessDeniedHandler(restfulAccessDeniedHandler)
// 处理未登录
.authenticationEntryPoint(restAuthenticationEntryPoint));
// JWT 校验过滤器
http.addFilterBefore(
new JwtValidationFilter(userDetailsService, securityProperties.getJwt().getKey()),
UsernamePasswordAuthenticationFilter.class);
return http.build();
}
/**
* AuthenticationManager 手动注入
*
* @param authenticationConfiguration 认证配置
*/
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
/** 强散列哈希加密实现 */
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}添加配置到 org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.lusifer.crmeb.config.ProjectMyBatisPlusAutoConfiguration
com.lusifer.crmeb.config.ProjectOpenApiAutoConfiguration
com.lusifer.crmeb.config.ProjectSecurityAutoConfigurationMapper
package com.lusifer.crmeb.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lusifer.crmeb.entity.SysUser;
/**
* 用户表 Mapper 接口
*
* @author Lusifer
* @since 2026-05-04
*/
public interface SysUserMapper extends BaseMapper<SysUser> {
SysUser selectByUsername(String username);
}Mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lusifer.crmeb.mapper.SysUserMapper">
<select id="selectByUsername" resultType="com.lusifer.crmeb.entity.SysUser">
select *
from sys_user
where username = #{username}
</select>
</mapper>Mapper
package com.lusifer.crmeb.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lusifer.crmeb.entity.SysMenu;
import java.util.Set;
/**
* 用户权限表 Mapper 接口
*
* @author Lusifer
* @since 2026-05-04
*/
public interface SysMenuMapper extends BaseMapper<SysMenu> {
Set<String> getMenuPermission(Long useId);
}Mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lusifer.crmeb.mapper.SysMenuMapper">
<select id="getMenuPermission" resultType="java.lang.String">
SELECT DISTINCT p.`permission`
FROM sys_user_role ar
LEFT JOIN sys_role r ON ar.role_id = r.role_id
LEFT JOIN sys_role_menu rp ON r.role_id = rp.role_id
LEFT JOIN sys_menu p ON rp.menu_id = p.menu_id
WHERE ar.user_id = #{userId}
AND p.`permission` IS NOT NULL
</select>
</mapper>Mapper
package com.lusifer.crmeb.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lusifer.crmeb.entity.SysUserRole;
import java.util.Set;
/**
* 用户和角色关系表 Mapper 接口
*
* @author Lusifer
* @since 2026-05-04
*/
public interface SysUserRoleMapper extends BaseMapper<SysUserRole> {
Set<String> listRoleKeyByUserId(Long userId);
Integer getMaximumDataScope(Set<String> roles);
}Mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lusifer.crmeb.mapper.SysUserRoleMapper">
<select id="listRoleKeyByUserId" resultType="java.lang.String">
SELECT DISTINCT r.role_key
FROM sys_user_role ur
LEFT JOIN sys_role r ON r.role_id = ur.role_id
WHERE ur.user_id = #{userId}
</select>
<select id="getMaximumDataScope" resultType="java.lang.Integer">
SELECT
min(data_scope)
FROM
sys_role
<where>
<choose>
<when test="roles!=null and roles.size>0">
AND role_key IN
<foreach collection="roles" item="role" separator="," open="(" close=")">
#{role}
</foreach>
</when>
<otherwise>
role_id = -1
</otherwise>
</choose>
</where>
</select>
</mapper>com.lusifer.crmeb.component.security.service.UserDetailsServiceImpl
package com.lusifer.crmeb.component.security.service;
import cn.hutool.core.bean.BeanUtil;
import com.lusifer.crmeb.component.security.model.SysUserDetails;
import com.lusifer.crmeb.entity.SysUser;
import com.lusifer.crmeb.exception.base.ServiceException;
import com.lusifer.crmeb.exception.enums.SysExceptionEnum;
import com.lusifer.crmeb.mapper.SysMenuMapper;
import com.lusifer.crmeb.mapper.SysUserMapper;
import com.lusifer.crmeb.mapper.SysUserRoleMapper;
import com.lusifer.crmeb.rule.enums.StatusEnum;
import java.util.HashSet;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/** 用户详情服务 */
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final SysUserMapper sysUserMapper;
private final SysMenuMapper sysMenuMapper;
private final SysUserRoleMapper sysUserRoleMapper;
@Override
@Cacheable(value = "user_details", key = "#username", unless = "#result == null ")
public SysUserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 获取登录用户信息
SysUser user = sysUserMapper.selectByUsername(username);
// 用户不存在
if (BeanUtil.isEmpty(user)) {
throw new ServiceException(SysExceptionEnum.USER_NOT_EXIST);
}
Long userId = user.getUserId();
// 用户停用
if (StatusEnum.DISABLE.getKey().equals(user.getStatusFlag())) {
throw new ServiceException(SysExceptionEnum.USER_DISABLED);
}
// 获取角色
Set<String> roles = sysUserRoleMapper.listRoleKeyByUserId(userId);
// 获取数据范围标识
Integer dataScope = sysUserRoleMapper.getMaximumDataScope(roles);
Set<String> permissions = new HashSet<>();
// 如果 roles 包含 root 则拥有所有权限
if (roles.contains("ROOT")) {
permissions.add("*:*:*");
} else {
// 获取菜单权限标识
permissions = sysMenuMapper.getMenuPermission(userId);
// 过滤空字符串
permissions.remove("");
}
return new SysUserDetails(user, permissions, roles, username, dataScope);
}
}com.lusifer.crmeb.auth
controller
request
responsecom.lusifer.crmeb.auth.request.SysUserLoginRequest
package com.lusifer.crmeb.auth.request;
import lombok.Data;
/** 用户登录参数 */
@Data
public class SysUserLoginRequest {
/** 用户 */
private String username;
/** 密码 */
private String password;
/** 图形验证码 */
private String code;
/** 唯一标识 */
private String uuid = "";
}com.lusifer.crmeb.auth.response.SysUserLoginVo
package com.lusifer.crmeb.auth.response;
import lombok.Data;
@Data
public class SysUserLoginVo {
/** token */
private String token;
/** token 类型,如:Bearer */
private String tokenType;
/** 过期时间(单位:秒),如:604800 */
private Long expiration;
/** 刷新 token */
private String refreshToken;
}com.lusifer.crmeb.auth.response.SysUserInfoVo
package com.lusifer.crmeb.auth.response;
import cn.hutool.core.date.DatePattern;
import cn.hutool.json.JSONArray;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
@Data
public class SysUserInfoVo implements Serializable {
@Serial private static final long serialVersionUID = 1L;
/** 用户ID */
private Long userId;
/** 所属部门 */
private Long deptId;
/** 所属部门名称 */
private String deptName;
/** 所属岗位 */
private JSONArray postIds;
/** 登陆用户名 */
private String username;
/** 头像 */
private String icon;
/** 邮箱 */
private String email;
/** 手机号码 */
private String phone;
/** 昵称 */
private String nickName;
/** 性别 */
private String sex;
/** 备注信息 */
private String note;
/** 最后登陆IP */
private String loginIp;
/** 最后登录时间 */
@JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN, timezone = "GMT+8")
private Date loginTime;
/** 启用状态,0:禁用 1:启用 */
private String statusFlag;
}com.lusifer.crmeb.auth.response.UserInfoResult
package com.lusifer.crmeb.auth.response;
import java.util.Set;
import lombok.Data;
@Data
public class UserInfoResult {
/** 用户名 */
private String username;
/** 用户详细信息 */
private SysUserInfoVo userInfo;
/** 权限集 */
private Set<String> permissions;
/** 角色集 */
private Set<String> roles;
}com.lusifer.crmeb.component.security.util.JwtUtils
package com.lusifer.crmeb.component.security.util;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.jwt.JWTUtil;
import cn.hutool.jwt.RegisteredPayload;
import com.lusifer.crmeb.component.security.model.SysUserDetails;
import com.lusifer.crmeb.config.properties.SecurityProperties;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
/** JWT 工具类 */
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtUtils {
private final SecurityProperties securityProperties;
/** 权限(角色 Code)集合 */
private static final String AUTHORITIES = "authorities";
/**
* 生成 JWT Token
*
* @param authentication 用户认证信息
* @return Token 字符串
*/
public String createToken(Authentication authentication) {
SysUserDetails userDetails = (SysUserDetails) authentication.getPrincipal();
Map<String, Object> payload = new HashMap<>();
// Claims 中添加角色信息
Set<String> roles =
userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());
payload.put(AUTHORITIES, roles);
Date now = new Date();
// JWT 的签发时间
payload.put(RegisteredPayload.ISSUED_AT, now);
// 设置过期时间 -1 表示永不过期
Long ttl = securityProperties.getJwt().getTtl();
if (ttl != -1) {
Date expiration = DateUtil.offsetSecond(now, ttl.intValue());
// JWT 的过期时间,这个过期时间必须要大于签发时间
payload.put(RegisteredPayload.EXPIRES_AT, expiration);
}
// JWT 所面向的用户
payload.put(RegisteredPayload.SUBJECT, authentication.getName());
// JWT 的唯一身份标识
payload.put(RegisteredPayload.JWT_ID, IdUtil.simpleUUID());
byte[] key = securityProperties.getJwt().getKey().getBytes();
return JWTUtil.createToken(payload, key);
}
}com.lusifer.crmeb.component.security.util.SecurityUtils
package com.lusifer.crmeb.component.security.util;
import com.lusifer.crmeb.component.security.model.SysUserDetails;
import com.lusifer.crmeb.entity.SysUser;
import com.lusifer.crmeb.exception.base.ServiceException;
import com.lusifer.crmeb.exception.enums.SecurityExceptionEnum;
import java.util.Set;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
/** 安全服务工具类 */
public class SecurityUtils {
public static UserDetails getUserDetails() {
try {
return (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
} catch (Exception e) {
throw new ServiceException(SecurityExceptionEnum.UNAUTHORIZED);
}
}
public static SysUserDetails getSysUserDetails() {
try {
return (SysUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
} catch (Exception e) {
throw new ServiceException(SecurityExceptionEnum.UNAUTHORIZED);
}
}
/**
* 是否超级管理员
*
* <p>超级管理员忽视任何权限判断
*/
public static boolean isRoot() {
Set<String> roles = getRoles();
return roles.contains("ROOT");
}
/**
* 获取当前 用户id
*
* @return 用户id
*/
public static Long getUserId() {
return getSysUser().getUserId();
}
private static Set<String> getRoles() {
return getSysUserDetails().getRoles();
}
public static SysUser getSysUser() {
try {
SecurityContext ctx = SecurityContextHolder.getContext();
Authentication auth = ctx.getAuthentication();
SysUserDetails sysUserDetails = (SysUserDetails) auth.getPrincipal();
return sysUserDetails.getSysUser();
} catch (Exception e) {
throw new ServiceException(SecurityExceptionEnum.UNAUTHORIZED);
}
}
public static Integer getDataScope() {
return getSysUserDetails().getDataScope();
}
public static Long getDeptId() {
return getSysUserDetails().getSysUser().getDeptId();
}
}com.lusifer.crmeb.service.SysUserService
package com.lusifer.crmeb.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.lusifer.crmeb.auth.response.SysUserLoginVo;
import com.lusifer.crmeb.auth.response.UserInfoResult;
import com.lusifer.crmeb.entity.SysUser;
/**
* 用户表 服务类
*
* @author Lusifer
* @since 2026-05-04
*/
public interface SysUserService extends IService<SysUser> {
/**
* 登录
*
* @param username 账号
* @param password 密码
* @return 用户登录视图对象
*/
SysUserLoginVo login(String username, String password);
/**
* 获取用户信息
*
* @return 用户信息视图对象
*/
UserInfoResult getInfo();
}com.lusifer.crmeb.service.impl.SysUserServiceImpl
package com.lusifer.crmeb.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lusifer.crmeb.auth.response.SysUserInfoVo;
import com.lusifer.crmeb.auth.response.SysUserLoginVo;
import com.lusifer.crmeb.auth.response.UserInfoResult;
import com.lusifer.crmeb.component.security.model.SysUserDetails;
import com.lusifer.crmeb.component.security.util.JwtUtils;
import com.lusifer.crmeb.component.security.util.SecurityUtils;
import com.lusifer.crmeb.config.properties.SecurityProperties;
import com.lusifer.crmeb.entity.SysUser;
import com.lusifer.crmeb.exception.base.ServiceException;
import com.lusifer.crmeb.exception.enums.SysExceptionEnum;
import com.lusifer.crmeb.mapper.SysUserMapper;
import com.lusifer.crmeb.rule.constants.SecurityConstants;
import com.lusifer.crmeb.service.SysUserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.stereotype.Service;
/**
* 用户表 服务实现类
*
* @author Lusifer
* @since 2026-05-04
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser>
implements SysUserService {
private final SecurityProperties securityProperties;
private final AuthenticationManager authenticationManager;
private final JwtUtils jwtUtils;
@Override
public SysUserLoginVo login(String username, String password) {
SysUserLoginVo res = new SysUserLoginVo();
String accessToken;
Long expiration = securityProperties.getJwt().getTtl();
// 密码需要客户端加密后传递
try {
// 该方法会去调用 UserDetailsServiceImpl.loadUserByUsername
Authentication authenticate =
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password));
SecurityContextHolder.getContext().setAuthentication(authenticate);
// 认证成功后生成 JWT 令牌
accessToken = jwtUtils.createToken(authenticate);
res.setToken(accessToken);
res.setExpiration(expiration);
res.setTokenType(CharSequenceUtil.trim(SecurityConstants.JWT_TOKEN_PREFIX));
return res;
} catch (Exception e) {
log.error("登录异常:{}", e.getMessage());
throw new ServiceException(SysExceptionEnum.ERROR_USERNAME_OR_PASSWORD);
}
}
@Override
public UserInfoResult getInfo() {
SysUserDetails sysUserDetails = SecurityUtils.getSysUserDetails();
SysUser user = sysUserDetails.getSysUser();
UserInfoResult result = new UserInfoResult();
result.setUsername(user.getUsername());
result.setPermissions(sysUserDetails.getPermissions());
result.setRoles(sysUserDetails.getRoles());
result.setUserInfo(BeanUtil.copyProperties(user, SysUserInfoVo.class));
return result;
}
}com.lusifer.crmeb.auth.controller.LoginController
package com.lusifer.crmeb.auth.controller;
import com.lusifer.crmeb.auth.request.SysUserLoginRequest;
import com.lusifer.crmeb.auth.response.SysUserLoginVo;
import com.lusifer.crmeb.auth.response.UserInfoResult;
import com.lusifer.crmeb.rule.pojo.response.ResponseData;
import com.lusifer.crmeb.rule.pojo.response.SuccessResponseData;
import com.lusifer.crmeb.service.SysUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
/** 登录控制器 */
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/auth")
@Tag(name = "认证中心")
public class LoginController {
private final SysUserService sysUserService;
@Operation(summary = "登录")
@PostMapping(value = "/login")
public ResponseData<SysUserLoginVo> login(@RequestBody SysUserLoginRequest request) {
return new SuccessResponseData<>(
sysUserService.login(request.getUsername(), request.getPassword()));
}
@Operation(summary = "获取当前用户信息")
@GetMapping(value = "/info")
public ResponseData<UserInfoResult> getInfo() {
UserInfoResult result = sysUserService.getInfo();
return new SuccessResponseData<>(result);
}
@Operation(summary = "注销")
@PostMapping(value = "/logout")
public ResponseData<?> logout(HttpServletRequest request) {
// 需要 将当前用户token 设置无效
SecurityContextHolder.clearContext();
return new SuccessResponseData<>();
}
}如果忘记密码,可以使用 Spring Security 提供的 PasswordEncoder 工具进行密码加密处理,将生成的新密码复制到数据库
创建 JUnit 测试类 com.lusifer.crmeb.test.PasswordEncoderTests
package com.lusifer.crmeb.test;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;
@SpringBootTest
class PasswordEncoderTests {
@Resource private PasswordEncoder passwordEncoder;
@Test
void testEncoderPassword() {
System.out.println(passwordEncoder.encode("123456"));
}
}
没有权限的效果

要访问该资源需要在请求头上追加访问令牌,在 Header 中追加如下参数
参数名:Authorization
参数值:Bearer + 空格 + 令牌
