diff --git a/.gitignore b/.gitignore index 4bb37679..18f34bde 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,3 @@ docker/*/data/ docker/minio/config docker/xxljob/logs application-youlai.yml -youlai_admin_template.sql \ No newline at end of file diff --git a/pom.xml b/pom.xml index 8933c16c..f4ce3378 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,6 @@ 3.16.3 - 4.1.0 @@ -62,6 +61,8 @@ 2.14.5 + + 4.8.1.B @@ -271,13 +272,20 @@ ${caffeine.version} + + + com.github.binarywang + weixin-java-miniapp + ${weixin.java.miniapp.version} + + - + diff --git a/sql/mysql/youlai_admin.sql b/sql/mysql/youlai_admin.sql index 5cea7735..c7d2b564 100644 --- a/sql/mysql/youlai_admin.sql +++ b/sql/mysql/youlai_admin.sql @@ -10,7 +10,7 @@ CREATE DATABASE IF NOT EXISTS youlai_admin CHARACTER SET utf8mb4 DEFAULT COLLATE -- ---------------------------- --- 2. 创建表 && 数据初始化 +-- 2. 创建表 && 数据初始化 -- ---------------------------- USE youlai_admin; @@ -557,3 +557,25 @@ INSERT INTO `sys_user_notice` VALUES (7, 7, 2, 1, NULL, now(), now(), 0); INSERT INTO `sys_user_notice` VALUES (8, 8, 2, 1, NULL, now(), now(), 0); INSERT INTO `sys_user_notice` VALUES (9, 9, 2, 1, NULL, now(), now(), 0); INSERT INTO `sys_user_notice` VALUES (10, 10, 2, 1, NULL, now(), now(), 0); + +-- ---------------------------- +-- Table structure for sys_user_social +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user_social`; +CREATE TABLE `sys_user_social` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `user_id` bigint NOT NULL COMMENT '用户ID', + `platform` varchar(20) NOT NULL COMMENT '平台类型(WECHAT_MINI/WECHAT_MP/ALIPAY/QQ/APPLE)', + `openid` varchar(64) NOT NULL COMMENT '平台openid', + `unionid` varchar(64) DEFAULT NULL COMMENT '微信unionid', + `nickname` varchar(64) DEFAULT NULL COMMENT '第三方昵称', + `avatar` varchar(255) DEFAULT NULL COMMENT '第三方头像URL', + `session_key` varchar(128) DEFAULT NULL COMMENT '微信session_key', + `verified` tinyint(1) DEFAULT 1 COMMENT '是否已验证(1-已验证 0-未验证)', + `create_time` datetime DEFAULT NULL COMMENT '绑定时间', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_platform_openid` (`platform`, `openid`), + KEY `idx_user_id` (`user_id`), + KEY `idx_unionid` (`unionid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户第三方账号绑定表'; diff --git a/sql/mysql/youlai_admin_template.sql b/sql/mysql/youlai_admin_template.sql new file mode 100644 index 00000000..ab691bd8 --- /dev/null +++ b/sql/mysql/youlai_admin_template.sql @@ -0,0 +1,520 @@ +# YouLai_Admin 数据库(MySQL 5.7 ~ MySQL 8.x) +# Copyright (c) 2021-present, youlai.tech + + +-- ---------------------------- +-- 1. 创建数据库 +-- ---------------------------- +CREATE DATABASE IF NOT EXISTS youlai_admin_template CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; + + +-- ---------------------------- +-- 2. 创建表 && 数据初始化 +-- ---------------------------- +USE youlai_admin_template; + +SET NAMES utf8mb4; # 设置字符集 +SET FOREIGN_KEY_CHECKS = 0; # 关闭外键检查,加快导入速度 + +-- ---------------------------- +-- Table structure for sys_dept +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dept`; +CREATE TABLE `sys_dept` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(100) NOT NULL COMMENT '部门名称', + `code` varchar(100) NOT NULL COMMENT '部门编号', + `parent_id` bigint DEFAULT 0 COMMENT '父节点id', + `tree_path` varchar(255) NOT NULL COMMENT '父节点id路径', + `sort` smallint DEFAULT 0 COMMENT '显示顺序', + `status` tinyint DEFAULT 1 COMMENT '状态(1-正常 0-禁用)', + `create_by` bigint NULL COMMENT '创建人ID', + `create_time` datetime NULL COMMENT '创建时间', + `update_by` bigint NULL COMMENT '修改人ID', + `update_time` datetime NULL COMMENT '更新时间', + `is_deleted` tinyint DEFAULT 0 COMMENT '逻辑删除标识(1-已删除 0-未删除)', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_code`(`code` ASC) USING BTREE COMMENT '部门编号唯一索引' +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '部门管理表'; + +-- ---------------------------- +-- Records of sys_dept +-- ---------------------------- +INSERT INTO `sys_dept` VALUES (1, '有来技术', 'YOULAI', 0, '0', 1, 1, 1, NULL, 1, now(), 0); +INSERT INTO `sys_dept` VALUES (2, '研发部门', 'RD001', 1, '0,1', 1, 1, 2, NULL, 2, now(), 0); +INSERT INTO `sys_dept` VALUES (3, '测试部门', 'QA001', 1, '0,1', 1, 1, 2, NULL, 2, now(), 0); + +-- ---------------------------- +-- Table structure for sys_dict +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict`; +CREATE TABLE `sys_dict` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键 ', + `dict_code` varchar(50) COMMENT '类型编码', + `name` varchar(50) COMMENT '类型名称', + `status` tinyint(1) DEFAULT '0' COMMENT '状态(0:正常;1:禁用)', + `remark` varchar(255) COMMENT '备注', + `create_time` datetime COMMENT '创建时间', + `create_by` bigint COMMENT '创建人ID', + `update_time` datetime COMMENT '更新时间', + `update_by` bigint COMMENT '修改人ID', + `is_deleted` tinyint DEFAULT '0' COMMENT '是否删除(1-删除,0-未删除)', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_dict_code` (`dict_code`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据字典类型表'; +-- ---------------------------- +-- Records of sys_dict +-- ---------------------------- +INSERT INTO `sys_dict` VALUES (1, 'gender', '性别', 1, NULL, now() , 1,now(), 1,0); +INSERT INTO `sys_dict` VALUES (2, 'notice_type', '通知类型', 1, NULL, now(), 1,now(), 1,0); +INSERT INTO `sys_dict` VALUES (3, 'notice_level', '通知级别', 1, NULL, now(), 1,now(), 1,0); + + +-- ---------------------------- +-- Table structure for sys_dict_item +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict_item`; +CREATE TABLE `sys_dict_item` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `dict_code` varchar(50) COMMENT '关联字典编码,与sys_dict表中的dict_code对应', + `value` varchar(50) COMMENT '字典项值', + `label` varchar(100) COMMENT '字典项标签', + `tag_type` varchar(50) COMMENT '标签类型,用于前端样式展示(如success、warning等)', + `status` tinyint DEFAULT '0' COMMENT '状态(1-正常,0-禁用)', + `sort` int DEFAULT '0' COMMENT '排序', + `remark` varchar(255) COMMENT '备注', + `create_time` datetime COMMENT '创建时间', + `create_by` bigint COMMENT '创建人ID', + `update_time` datetime COMMENT '更新时间', + `update_by` bigint COMMENT '修改人ID', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据字典项表'; + +-- ---------------------------- +-- Records of sys_dict_item +-- ---------------------------- +INSERT INTO `sys_dict_item` VALUES (1, 'gender', '1', '男', 'primary', 1, 1, NULL, now(), 1,now(),1); +INSERT INTO `sys_dict_item` VALUES (2, 'gender', '2', '女', 'danger', 1, 2, NULL, now(), 1,now(),1); +INSERT INTO `sys_dict_item` VALUES (3, 'gender', '0', '保密', 'info', 1, 3, NULL, now(), 1,now(),1); +INSERT INTO `sys_dict_item` VALUES (4, 'notice_type', '1', '系统升级', 'success', 1, 1, '', now(), 1,now(),1); +INSERT INTO `sys_dict_item` VALUES (5, 'notice_type', '2', '系统维护', 'primary', 1, 2, '', now(), 1,now(),1); +INSERT INTO `sys_dict_item` VALUES (6, 'notice_type', '3', '安全警告', 'danger', 1, 3, '', now(), 1,now(),1); +INSERT INTO `sys_dict_item` VALUES (7, 'notice_type', '4', '假期通知', 'success', 1, 4, '', now(), 1,now(),1); +INSERT INTO `sys_dict_item` VALUES (8, 'notice_type', '5', '公司新闻', 'primary', 1, 5, '', now(), 1,now(),1); +INSERT INTO `sys_dict_item` VALUES (9, 'notice_type', '99', '其他', 'info', 1, 99, '', now(), 1,now(),1); +INSERT INTO `sys_dict_item` VALUES (10, 'notice_level', 'L', '低', 'info', 1, 1, '', now(), 1,now(),1); +INSERT INTO `sys_dict_item` VALUES (11, 'notice_level', 'M', '中', 'warning', 1, 2, '', now(), 1,now(),1); +INSERT INTO `sys_dict_item` VALUES (12, 'notice_level', 'H', '高', 'danger', 1, 3, '', now(), 1,now(),1); + +-- ---------------------------- +-- Table structure for sys_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_menu`; +CREATE TABLE `sys_menu` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID', + `parent_id` bigint NOT NULL COMMENT '父菜单ID', + `tree_path` varchar(255) COMMENT '父节点ID路径', + `name` varchar(64) NOT NULL COMMENT '菜单名称', + `type` char(1) NOT NULL COMMENT '菜单类型(C-目录 M-菜单 B-按钮)', + `route_name` varchar(255) COMMENT '路由名称(Vue Router 中用于命名路由)', + `route_path` varchar(128) COMMENT '路由路径(Vue Router 中定义的 URL 路径)', + `component` varchar(128) COMMENT '组件路径(组件页面完整路径,相对于 src/views/,缺省后缀 .vue)', + `perm` varchar(128) COMMENT '【按钮】权限标识', + `always_show` tinyint DEFAULT 0 COMMENT '【目录】只有一个子路由是否始终显示(1-是 0-否)', + `keep_alive` tinyint DEFAULT 0 COMMENT '【菜单】是否开启页面缓存(1-是 0-否)', + `visible` tinyint(1) DEFAULT 1 COMMENT '显示状态(1-显示 0-隐藏)', + `sort` int DEFAULT 0 COMMENT '排序', + `icon` varchar(64) COMMENT '菜单图标', + `redirect` varchar(128) COMMENT '跳转路径', + `create_time` datetime NULL COMMENT '创建时间', + `update_time` datetime NULL COMMENT '更新时间', + `params` varchar(255) NULL COMMENT '路由参数', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '系统菜单表'; + +-- ---------------------------- +-- Records of sys_menu +-- ---------------------------- +-- 顶级目录:系统管理/代码生成/平台文档/接口文档 +INSERT INTO `sys_menu` VALUES (1, 0, '0', '系统管理', 'C', '', '/system', 'Layout', NULL, NULL, NULL, 1, 1, 'system', '/system/user', now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2, 0, '0', '代码生成', 'C', '', '/codegen', 'Layout', NULL, NULL, NULL, 1, 2, 'code', '/codegen/index', now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (4, 0, '0', '平台文档', 'C', '', '/doc', 'Layout', NULL, NULL, NULL, 1, 4, 'document', '', now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (5, 0, '0', '接口文档', 'C', '', '/api', 'Layout', NULL, NULL, NULL, 1, 5, 'api', '', now(), now(), NULL); + +-- 系统管理 +INSERT INTO `sys_menu` VALUES (210, 1, '0,1', '用户管理', 'M', 'User', 'user', 'system/user/index', NULL, NULL, 1, 1, 1, 'el-icon-User', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2101, 210, '0,1,210', '用户查询', 'B', NULL, '', NULL, 'sys:user:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2102, 210, '0,1,210', '用户新增', 'B', NULL, '', NULL, 'sys:user:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2103, 210, '0,1,210', '用户编辑', 'B', NULL, '', NULL, 'sys:user:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2104, 210, '0,1,210', '用户删除', 'B', NULL, '', NULL, 'sys:user:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2105, 210, '0,1,210', '重置密码', 'B', NULL, '', NULL, 'sys:user:reset-password', NULL, NULL, 1, 5, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2106, 210, '0,1,210', '用户导入', 'B', NULL, '', NULL, 'sys:user:import', NULL, NULL, 1, 6, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2107, 210, '0,1,210', '用户导出', 'B', NULL, '', NULL, 'sys:user:export', NULL, NULL, 1, 7, '', NULL, now(), now(), NULL); + +INSERT INTO `sys_menu` VALUES (220, 1, '0,1', '角色管理', 'M', 'Role', 'role', 'system/role/index', NULL, NULL, 1, 1, 2, 'role', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2201, 220, '0,1,220', '角色查询', 'B', NULL, '', NULL, 'sys:role:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2202, 220, '0,1,220', '角色新增', 'B', NULL, '', NULL, 'sys:role:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2203, 220, '0,1,220', '角色编辑', 'B', NULL, '', NULL, 'sys:role:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2204, 220, '0,1,220', '角色删除', 'B', NULL, '', NULL, 'sys:role:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2205, 220, '0,1,220', '角色分配权限', 'B', NULL, '', NULL, 'sys:role:assign', NULL, NULL, 1, 5, '', NULL, now(), now(), NULL); + +INSERT INTO `sys_menu` VALUES (230, 1, '0,1', '菜单管理', 'M', 'SysMenu', 'menu', 'system/menu/index', NULL, NULL, 1, 1, 3, 'menu', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2301, 230, '0,1,230', '菜单查询', 'B', NULL, '', NULL, 'sys:menu:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2302, 230, '0,1,230', '菜单新增', 'B', NULL, '', NULL, 'sys:menu:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2303, 230, '0,1,230', '菜单编辑', 'B', NULL, '', NULL, 'sys:menu:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2304, 230, '0,1,230', '菜单删除', 'B', NULL, '', NULL, 'sys:menu:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL); + +INSERT INTO `sys_menu` VALUES (240, 1, '0,1', '部门管理', 'M', 'Dept', 'dept', 'system/dept/index', NULL, NULL, 1, 1, 4, 'tree', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2401, 240, '0,1,240', '部门查询', 'B', NULL, '', NULL, 'sys:dept:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2402, 240, '0,1,240', '部门新增', 'B', NULL, '', NULL, 'sys:dept:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2403, 240, '0,1,240', '部门编辑', 'B', NULL, '', NULL, 'sys:dept:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2404, 240, '0,1,240', '部门删除', 'B', NULL, '', NULL, 'sys:dept:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL); + +INSERT INTO `sys_menu` VALUES (250, 1, '0,1', '字典管理', 'M', 'Dict', 'dict', 'system/dict/index', NULL, NULL, 1, 1, 5, 'dict', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2501, 250, '0,1,250', '字典查询', 'B', NULL, '', NULL, 'sys:dict:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2502, 250, '0,1,250', '字典新增', 'B', NULL, '', NULL, 'sys:dict:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2503, 250, '0,1,250', '字典编辑', 'B', NULL, '', NULL, 'sys:dict:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2504, 250, '0,1,250', '字典删除', 'B', NULL, '', NULL, 'sys:dict:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL); + +INSERT INTO `sys_menu` VALUES (251, 1, '0,1', '字典项', 'M', 'DictItem', 'dict-item', 'system/dict/dict-item', NULL, 0, 1, 0, 6, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2511, 251, '0,1,251', '字典项查询', 'B', NULL, '', NULL, 'sys:dict-item:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2512, 251, '0,1,251', '字典项新增', 'B', NULL, '', NULL, 'sys:dict-item:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2513, 251, '0,1,251', '字典项编辑', 'B', NULL, '', NULL, 'sys:dict-item:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2514, 251, '0,1,251', '字典项删除', 'B', NULL, '', NULL, 'sys:dict-item:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL); + +INSERT INTO `sys_menu` VALUES (260, 1, '0,1', '系统日志', 'M', 'Log', 'log', 'system/log/index', NULL, 0, 1, 1, 7, 'document', NULL, now(), now(), NULL); + +INSERT INTO `sys_menu` VALUES (270, 1, '0,1', '系统配置', 'M', 'Config', 'config', 'system/config/index', NULL, 0, 1, 1, 8, 'setting', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2701, 270, '0,1,270', '系统配置查询', 'B', NULL, '', NULL, 'sys:config:list', 0, 1, 1, 1, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2702, 270, '0,1,270', '系统配置新增', 'B', NULL, '', NULL, 'sys:config:create', 0, 1, 1, 2, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2703, 270, '0,1,270', '系统配置修改', 'B', NULL, '', NULL, 'sys:config:update', 0, 1, 1, 3, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2704, 270, '0,1,270', '系统配置删除', 'B', NULL, '', NULL, 'sys:config:delete', 0, 1, 1, 4, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2705, 270, '0,1,270', '系统配置刷新', 'B', NULL, '', NULL, 'sys:config:refresh', 0, 1, 1, 5, '', NULL, now(), now(), NULL); + +INSERT INTO `sys_menu` VALUES (280, 1, '0,1', '通知公告', 'M', 'Notice', 'notice', 'system/notice/index', NULL, NULL, NULL, 1, 9, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2801, 280, '0,1,280', '通知查询', 'B', NULL, '', NULL, 'sys:notice:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2802, 280, '0,1,280', '通知新增', 'B', NULL, '', NULL, 'sys:notice:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2803, 280, '0,1,280', '通知编辑', 'B', NULL, '', NULL, 'sys:notice:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2804, 280, '0,1,280', '通知删除', 'B', NULL, '', NULL, 'sys:notice:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2805, 280, '0,1,280', '通知发布', 'B', NULL, '', NULL, 'sys:notice:publish', 0, 1, 1, 5, '', NULL, now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (2806, 280, '0,1,280', '通知撤回', 'B', NULL, '', NULL, 'sys:notice:revoke', 0, 1, 1, 6, '', NULL, now(), now(), NULL); + +-- 代码生成 +INSERT INTO `sys_menu` VALUES (310, 2, '0,2', '代码生成', 'M', 'Codegen', 'codegen', 'codegen/index', NULL, NULL, 1, 1, 1, 'code', NULL, now(), now(), NULL); + +-- 平台文档(外链通过 route_path 识别) +INSERT INTO `sys_menu` VALUES (501, 4, '0,4', '平台文档(外链)', 'M', NULL, 'https://juejin.cn/post/7228990409909108793', '', NULL, NULL, NULL, 1, 1, 'document', '', now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (502, 4, '0,4', '后端文档', 'M', NULL, 'https://youlai.blog.csdn.net/article/details/145178880', '', NULL, NULL, NULL, 1, 2, 'document', '', now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (503, 4, '0,4', '移动端文档', 'M', NULL, 'https://youlai.blog.csdn.net/article/details/143222890', '', NULL, NULL, NULL, 1, 3, 'document', '', now(), now(), NULL); +INSERT INTO `sys_menu` VALUES (504, 4, '0,4', '内部文档', 'M', NULL, 'internal-doc', 'demo/internal-doc', NULL, NULL, NULL, 1, 4, 'document', '', now(), now(), NULL); + +-- 接口文档 +INSERT INTO `sys_menu` VALUES (601, 5, '0,5', 'Apifox', 'M', 'Apifox', 'apifox', 'demo/api/apifox', NULL, NULL, 1, 1, 1, 'api', '', now(), now(), NULL); + +-- ---------------------------- +-- Table structure for sys_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role`; +CREATE TABLE `sys_role` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(64) NOT NULL COMMENT '角色名称', + `code` varchar(32) NOT NULL COMMENT '角色编码', + `sort` int NULL COMMENT '显示顺序', + `status` tinyint(1) DEFAULT 1 COMMENT '角色状态(1-正常 0-停用)', + `data_scope` tinyint NULL COMMENT '数据权限(1-所有数据 2-部门及子部门数据 3-本部门数据 4-本人数据 5-自定义部门数据)', + `create_by` bigint NULL COMMENT '创建人 ID', + `create_time` datetime NULL COMMENT '创建时间', + `update_by` bigint NULL COMMENT '更新人ID', + `update_time` datetime NULL COMMENT '更新时间', + `is_deleted` tinyint(1) DEFAULT 0 COMMENT '逻辑删除标识(0-未删除 1-已删除)', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_name`(`name` ASC) USING BTREE COMMENT '角色名称唯一索引', + UNIQUE INDEX `uk_code`(`code` ASC) USING BTREE COMMENT '角色编码唯一索引' +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '系统角色表'; + +-- ---------------------------- +-- Records of sys_role +-- ---------------------------- +INSERT INTO `sys_role` VALUES (1, '超级管理员', 'ROOT', 1, 1, 1, NULL, now(), NULL, now(), 0); +INSERT INTO `sys_role` VALUES (2, '系统管理员', 'ADMIN', 2, 1, 1, NULL, now(), NULL, NULL, 0); +INSERT INTO `sys_role` VALUES (3, '访问游客', 'GUEST', 3, 1, 3, NULL, now(), NULL, now(), 0); +INSERT INTO `sys_role` VALUES (4, '部门主管', 'DEPT_MANAGER', 4, 1, 2, NULL, now(), NULL, now(), 0); +INSERT INTO `sys_role` VALUES (5, '部门成员', 'DEPT_MEMBER', 5, 1, 3, NULL, now(), NULL, now(), 0); +INSERT INTO `sys_role` VALUES (6, '普通员工', 'EMPLOYEE', 6, 1, 4, NULL, now(), NULL, now(), 0); +INSERT INTO `sys_role` VALUES (7, '自定义权限用户', 'CUSTOM_USER', 7, 1, 5, NULL, now(), NULL, now(), 0); + +-- ---------------------------- +-- Table structure for sys_role_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_menu`; +CREATE TABLE `sys_role_menu` ( + `role_id` bigint NOT NULL COMMENT '角色ID', + `menu_id` bigint NOT NULL COMMENT '菜单ID', + UNIQUE INDEX `uk_roleid_menuid`(`role_id` ASC, `menu_id` ASC) USING BTREE COMMENT '角色菜单唯一索引' +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '角色菜单关联表'; + +-- ---------------------------- +-- Table structure for sys_role_dept +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_dept`; +CREATE TABLE `sys_role_dept` ( + `role_id` bigint NOT NULL COMMENT '角色ID', + `dept_id` bigint NOT NULL COMMENT '部门ID', + UNIQUE INDEX `uk_roleid_deptid`(`role_id` ASC, `dept_id` ASC) USING BTREE COMMENT '角色部门唯一索引' +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '角色部门关联表'; + +-- ---------------------------- +-- Records of sys_role_dept +-- ---------------------------- +INSERT IGNORE INTO `sys_role_dept` VALUES (7, 1); +INSERT IGNORE INTO `sys_role_dept` VALUES (7, 2); + +-- ============================================ +-- 系统管理员角色菜单权限(role_id=2) +-- 顶级目录 +INSERT INTO `sys_role_menu` VALUES (2, 1), (2, 2), (2, 4), (2, 5); +-- 系统管理 +INSERT INTO `sys_role_menu` VALUES (2, 210), (2, 2101), (2, 2102), (2, 2103), (2, 2104), (2, 2105), (2, 2106), (2, 2107); +INSERT INTO `sys_role_menu` VALUES (2, 220), (2, 2201), (2, 2202), (2, 2203), (2, 2204), (2, 2205); +INSERT INTO `sys_role_menu` VALUES (2, 230), (2, 2301), (2, 2302), (2, 2303), (2, 2304); +INSERT INTO `sys_role_menu` VALUES (2, 240), (2, 2401), (2, 2402), (2, 2403), (2, 2404); +INSERT INTO `sys_role_menu` VALUES (2, 250), (2, 2501), (2, 2502), (2, 2503), (2, 2504); +INSERT INTO `sys_role_menu` VALUES (2, 251), (2, 2511), (2, 2512), (2, 2513), (2, 2514); +INSERT INTO `sys_role_menu` VALUES (2, 260); +INSERT INTO `sys_role_menu` VALUES (2, 270), (2, 2701), (2, 2702), (2, 2703), (2, 2704), (2, 2705); +INSERT INTO `sys_role_menu` VALUES (2, 280), (2, 2801), (2, 2802), (2, 2803), (2, 2804), (2, 2805), (2, 2806); + +INSERT IGNORE INTO `sys_role_menu` VALUES (4, 1); +INSERT IGNORE INTO `sys_role_menu` VALUES (4, 210), (4, 2101), (4, 2102), (4, 2103), (4, 2104), (4, 2105), (4, 2106), (4, 2107); +INSERT IGNORE INTO `sys_role_menu` VALUES (4, 220), (4, 2201), (4, 2202), (4, 2203), (4, 2204), (4, 2205); + +INSERT IGNORE INTO `sys_role_menu` VALUES (5, 1); +INSERT IGNORE INTO `sys_role_menu` VALUES (5, 210), (5, 2101), (5, 2102), (5, 2103), (5, 2104), (5, 2105), (5, 2106), (5, 2107); +INSERT IGNORE INTO `sys_role_menu` VALUES (5, 220), (5, 2201), (5, 2202), (5, 2203), (5, 2204), (5, 2205); + +INSERT IGNORE INTO `sys_role_menu` VALUES (6, 1); +INSERT IGNORE INTO `sys_role_menu` VALUES (6, 210), (6, 2101), (6, 2102), (6, 2103), (6, 2104), (6, 2105), (6, 2106), (6, 2107); +INSERT IGNORE INTO `sys_role_menu` VALUES (6, 220), (6, 2201), (6, 2202), (6, 2203), (6, 2204), (6, 2205); + +INSERT IGNORE INTO `sys_role_menu` VALUES (7, 1); +INSERT IGNORE INTO `sys_role_menu` VALUES (7, 210), (7, 2101), (7, 2102), (7, 2103), (7, 2104), (7, 2105), (7, 2106), (7, 2107); +INSERT IGNORE INTO `sys_role_menu` VALUES (7, 220), (7, 2201), (7, 2202), (7, 2203), (7, 2204), (7, 2205); +-- 代码生成 +INSERT INTO `sys_role_menu` VALUES (2, 310); +-- 平台文档 +INSERT INTO `sys_role_menu` VALUES (2, 501), (2, 502), (2, 503), (2, 504); +-- 接口文档 +INSERT INTO `sys_role_menu` VALUES (2, 601); + +-- ---------------------------- +-- Table structure for sys_user +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user`; +CREATE TABLE `sys_user` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `username` varchar(64) COMMENT '用户名', + `nickname` varchar(64) COMMENT '昵称', + `gender` tinyint(1) DEFAULT 1 COMMENT '性别((1-男 2-女 0-保密)', + `password` varchar(100) COMMENT '密码', + `dept_id` int COMMENT '部门ID', + `avatar` varchar(255) COMMENT '用户头像', + `mobile` varchar(20) COMMENT '联系方式', + `status` tinyint(1) DEFAULT 1 COMMENT '状态(1-正常 0-禁用)', + `email` varchar(128) COMMENT '用户邮箱', + `create_time` datetime COMMENT '创建时间', + `create_by` bigint COMMENT '创建人ID', + `update_time` datetime COMMENT '更新时间', + `update_by` bigint COMMENT '修改人ID', + `is_deleted` tinyint(1) DEFAULT 0 COMMENT '逻辑删除标识(0-未删除 1-已删除)', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '系统用户表'; + +-- ---------------------------- +-- Records of sys_user +-- ---------------------------- +INSERT INTO `sys_user` VALUES (1, 'root', '有来技术', 0, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', NULL, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345677', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0); +INSERT INTO `sys_user` VALUES (2, 'admin', '系统管理员', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345678', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0); +INSERT INTO `sys_user` VALUES (3, 'test', '测试小用户', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 3, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345679', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0); +INSERT INTO `sys_user` VALUES (4, 'dept_manager', '部门主管', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345680', 1, 'manager@youlaitech.com', now(), NULL, now(), NULL, 0); +INSERT INTO `sys_user` VALUES (5, 'dept_member', '部门成员', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345681', 1, 'member@youlaitech.com', now(), NULL, now(), NULL, 0); +INSERT INTO `sys_user` VALUES (6, 'employee', '普通员工', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 2, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345682', 1, 'employee@youlaitech.com', now(), NULL, now(), NULL, 0); +INSERT INTO `sys_user` VALUES (7, 'custom_user', '自定义权限用户', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 3, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345683', 1, 'custom@youlaitech.com', now(), NULL, now(), NULL, 0); + +-- ---------------------------- +-- Table structure for sys_user_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user_role`; +CREATE TABLE `sys_user_role` ( + `user_id` bigint NOT NULL COMMENT '用户ID', + `role_id` bigint NOT NULL COMMENT '角色ID', + PRIMARY KEY (`user_id`, `role_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '用户角色关联表'; + +-- ---------------------------- +-- Records of sys_user_role +-- ---------------------------- +INSERT INTO `sys_user_role` VALUES (1, 1); +INSERT INTO `sys_user_role` VALUES (2, 2); +INSERT INTO `sys_user_role` VALUES (3, 3); +INSERT IGNORE INTO `sys_user_role` VALUES (4, 4); +INSERT IGNORE INTO `sys_user_role` VALUES (5, 5); +INSERT IGNORE INTO `sys_user_role` VALUES (6, 6); +INSERT IGNORE INTO `sys_user_role` VALUES (7, 7); + +-- ---------------------------- +-- Table structure for sys_log +-- ---------------------------- +DROP TABLE IF EXISTS `sys_log`; +CREATE TABLE `sys_log` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `module` varchar(50) NOT NULL COMMENT '日志模块', + `request_method` varchar(64) NOT NULL COMMENT '请求方式', + `request_params` text COMMENT '请求参数(批量请求参数可能会超过text)', + `response_content` mediumtext COMMENT '返回参数', + `content` varchar(255) NOT NULL COMMENT '日志内容', + `request_uri` varchar(255) COMMENT '请求路径', + `method` varchar(255) COMMENT '方法名', + `ip` varchar(45) COMMENT 'IP地址', + `province` varchar(100) COMMENT '省份', + `city` varchar(100) COMMENT '城市', + `execution_time` bigint COMMENT '执行时间(ms)', + `browser` varchar(100) COMMENT '浏览器', + `browser_version` varchar(100) COMMENT '浏览器版本', + `os` varchar(100) COMMENT '终端系统', + `create_by` bigint COMMENT '创建人ID', + `create_time` datetime COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_create_time` (`create_time`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COMMENT='系统操作日志表'; + +-- ---------------------------- +-- Table structure for gen_table +-- ---------------------------- +DROP TABLE IF EXISTS `gen_table`; +CREATE TABLE `gen_table` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `table_name` varchar(100) NOT NULL COMMENT '表名', + `module_name` varchar(100) COMMENT '模块名', + `package_name` varchar(255) NOT NULL COMMENT '包名', + `business_name` varchar(100) NOT NULL COMMENT '业务名', + `entity_name` varchar(100) NOT NULL COMMENT '实体类名', + `author` varchar(50) NOT NULL COMMENT '作者', + `parent_menu_id` bigint COMMENT '上级菜单ID,对应sys_menu的id ', + `remove_table_prefix` varchar(20) COMMENT '要移除的表前缀,如: sys_', + `page_type` varchar(20) COMMENT '页面类型(classic|curd)', + `create_time` datetime COMMENT '创建时间', + `update_time` datetime COMMENT '更新时间', + `is_deleted` tinyint(4) DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_tablename` (`table_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='代码生成配置表'; + +-- ---------------------------- +-- Table structure for gen_table_column +-- ---------------------------- +DROP TABLE IF EXISTS `gen_table_column`; +CREATE TABLE `gen_table_column` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `table_id` bigint NOT NULL COMMENT '关联的表配置ID', + `column_name` varchar(100) , + `column_type` varchar(50) , + `column_length` int , + `field_name` varchar(100) NOT NULL COMMENT '字段名称', + `field_type` varchar(100) COMMENT '字段类型', + `field_sort` int COMMENT '字段排序', + `field_comment` varchar(255) COMMENT '字段描述', + `max_length` int , + `is_required` tinyint(1) COMMENT '是否必填', + `is_show_in_list` tinyint(1) DEFAULT '0' COMMENT '是否在列表显示', + `is_show_in_form` tinyint(1) DEFAULT '0' COMMENT '是否在表单显示', + `is_show_in_query` tinyint(1) DEFAULT '0' COMMENT '是否在查询条件显示', + `query_type` tinyint COMMENT '查询方式', + `form_type` tinyint COMMENT '表单类型', + `dict_type` varchar(50) COMMENT '字典类型', + `create_time` datetime COMMENT '创建时间', + `update_time` datetime COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_table_id` (`table_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='代码生成字段配置表'; + +-- ---------------------------- +-- 系统配置表 +-- ---------------------------- +DROP TABLE IF EXISTS `sys_config`; +CREATE TABLE `sys_config` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `config_name` varchar(50) NOT NULL COMMENT '配置名称', + `config_key` varchar(50) NOT NULL COMMENT '配置key', + `config_value` varchar(100) NOT NULL COMMENT '配置值', + `remark` varchar(255) COMMENT '备注', + `create_time` datetime COMMENT '创建时间', + `create_by` bigint COMMENT '创建人ID', + `update_time` datetime COMMENT '更新时间', + `update_by` bigint COMMENT '更新人ID', + `is_deleted` tinyint(4) DEFAULT '0' NOT NULL COMMENT '逻辑删除标识(0-未删除 1-已删除)', + PRIMARY KEY (`id`) +) ENGINE=InnoDB COMMENT='系统配置表'; + +INSERT INTO `sys_config` VALUES (1, '系统限流QPS', 'IP_QPS_THRESHOLD_LIMIT', '10', '单个IP请求的最大每秒查询数(QPS)阈值Key', now(), 1, NULL, NULL, 0); + +-- ---------------------------- +-- 通知公告表 +-- ---------------------------- +DROP TABLE IF EXISTS `sys_notice`; +CREATE TABLE `sys_notice` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `title` varchar(50) COMMENT '通知标题', + `content` text COMMENT '通知内容', + `type` tinyint NOT NULL COMMENT '通知类型(关联字典编码:notice_type)', + `level` varchar(5) NOT NULL COMMENT '通知等级(字典code:notice_level)', + `target_type` tinyint NOT NULL COMMENT '目标类型(1: 全体, 2: 指定)', + `target_user_ids` varchar(255) COMMENT '目标人ID集合(多个使用英文逗号,分割)', + `publisher_id` bigint COMMENT '发布人ID', + `publish_status` tinyint DEFAULT '0' COMMENT '发布状态(0: 未发布, 1: 已发布, -1: 已撤回)', + `publish_time` datetime COMMENT '发布时间', + `revoke_time` datetime COMMENT '撤回时间', + `create_by` bigint NOT NULL COMMENT '创建人ID', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_by` bigint COMMENT '更新人ID', + `update_time` datetime COMMENT '更新时间', + `is_deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除(0: 未删除, 1: 已删除)', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统通知公告表'; + +INSERT INTO `sys_notice` VALUES (1, 'v3.0.0 版本发布 - 多租户功能上线', '

🎉 新版本发布,主要更新内容:

1. 新增多租户功能,支持租户隔离和数据管理

2. 优化系统性能,提升响应速度

3. 完善权限管理,增强安全性

4. 修复已知问题,提升系统稳定性

', 1, 'H', 1, NULL, 1, 1, '2024-12-15 10:00:00', NULL, 1, '2024-12-15 10:00:00', 1, '2024-12-15 10:00:00', 0); +INSERT INTO `sys_notice` VALUES (2, '系统维护通知 - 2024年12月20日', '

⏰ 系统维护通知

系统将于 2024年12月20日(本周五)凌晨 2:00-4:00 进行例行维护升级。

维护期间系统将暂停服务,请提前做好数据备份工作。

给您带来的不便,敬请谅解!

', 2, 'H', 1, NULL, 1, 1, '2024-12-18 14:30:00', NULL, 1, '2024-12-18 14:30:00', 1, '2024-12-18 14:30:00', 0); +INSERT INTO `sys_notice` VALUES (3, '安全提醒 - 防范钓鱼邮件', '

⚠️ 安全提醒

近期发现有不法分子通过钓鱼邮件进行网络攻击,请大家提高警惕:

1. 不要点击来源不明的邮件链接

2. 不要下载可疑附件

3. 遇到可疑邮件请及时联系IT部门

4. 定期修改密码,使用强密码策略

', 3, 'H', 1, NULL, 1, 1, '2024-12-10 09:00:00', NULL, 1, '2024-12-10 09:00:00', 1, '2024-12-10 09:00:00', 0); +INSERT INTO `sys_notice` VALUES (4, '元旦假期安排通知', '

📅 元旦假期安排

根据国家法定节假日安排,公司元旦假期时间为:

2024年12月30日(周一)至 2025年1月1日(周三),共3天。

2024年12月29日(周日)正常上班。

祝大家元旦快乐,假期愉快!

', 4, 'M', 1, NULL, 1, 1, '2024-12-25 16:00:00', NULL, 1, '2024-12-25 16:00:00', 1, '2024-12-25 16:00:00', 0); +INSERT INTO `sys_notice` VALUES (5, '新产品发布会邀请', '

🎊 新产品发布会邀请

公司将于 2025年1月15日下午14:00 在总部会议室举办新产品发布会。

届时将展示最新研发的产品和技术成果,欢迎全体员工参加。

请各部门提前安排好工作,准时参加。

', 5, 'M', 1, NULL, 1, 1, '2024-12-28 11:00:00', NULL, 1, '2024-12-28 11:00:00', 1, '2024-12-28 11:00:00', 0); +INSERT INTO `sys_notice` VALUES (6, 'v2.16.1 版本更新', '

✨ 版本更新

v2.16.1 版本已发布,主要修复内容:

1. 修复 WebSocket 重复连接导致的后台线程阻塞问题

2. 优化通知公告功能,提升用户体验

3. 修复部分已知bug

建议尽快更新到最新版本。

', 1, 'M', 1, NULL, 1, 1, '2024-12-05 15:30:00', NULL, 1, '2024-12-05 15:30:00', 1, '2024-12-05 15:30:00', 0); +INSERT INTO `sys_notice` VALUES (7, '年终总结会议通知', '

📋 年终总结会议通知

各部门年终总结会议将于 2024年12月30日上午9:00 召开。

请各部门负责人提前准备好年度工作总结和下年度工作计划。

会议地点:总部大会议室

', 5, 'M', 2, '1,2', 1, 1, '2024-12-22 10:00:00', NULL, 1, '2024-12-22 10:00:00', 1, '2024-12-22 10:00:00', 0); +INSERT INTO `sys_notice` VALUES (8, '系统功能优化完成', '

✅ 系统功能优化

已完成以下功能优化:

1. 优化用户管理界面,提升操作体验

2. 增强数据导出功能,支持更多格式

3. 优化搜索功能,提升查询效率

4. 修复部分界面显示问题

', 1, 'L', 1, NULL, 1, 1, '2024-12-12 14:20:00', NULL, 1, '2024-12-12 14:20:00', 1, '2024-12-12 14:20:00', 0); +INSERT INTO `sys_notice` VALUES (9, '员工培训计划', '

📚 员工培训计划

为提升员工专业技能,公司将于 2025年1月8日-10日 组织技术培训。

培训内容:

1. 新技术框架应用

2. 代码规范与最佳实践

3. 系统架构设计

请各部门合理安排工作,确保培训顺利进行。

', 5, 'M', 1, NULL, 1, 1, '2024-12-20 09:30:00', NULL, 1, '2024-12-20 09:30:00', 1, '2024-12-20 09:30:00', 0); +INSERT INTO `sys_notice` VALUES (10, '数据备份提醒', '

💾 数据备份提醒

请各部门注意定期备份重要数据,建议每周至少备份一次。

备份方式:

1. 使用系统自带备份功能

2. 手动导出重要数据

3. 联系IT部门协助备份

数据安全,人人有责!

', 3, 'L', 1, NULL, 1, 1, '2024-12-08 08:00:00', NULL, 1, '2024-12-08 08:00:00', 1, '2024-12-08 08:00:00', 0); + +-- ---------------------------- +-- 用户通知公告表 +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user_notice`; +CREATE TABLE `sys_user_notice` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id', + `notice_id` bigint NOT NULL COMMENT '公共通知id', + `user_id` bigint NOT NULL COMMENT '用户id', + `is_read` bigint DEFAULT '0' COMMENT '读取状态(0: 未读, 1: 已读)', + `read_time` datetime COMMENT '阅读时间', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime COMMENT '更新时间', + `is_deleted` tinyint DEFAULT '0' COMMENT '逻辑删除(0: 未删除, 1: 已删除)', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户通知公告关联表'; + +INSERT INTO `sys_user_notice` VALUES (1, 1, 2, 1, NULL, now(), now(), 0); +INSERT INTO `sys_user_notice` VALUES (2, 2, 2, 1, NULL, now(), now(), 0); +INSERT INTO `sys_user_notice` VALUES (3, 3, 2, 1, NULL, now(), now(), 0); +INSERT INTO `sys_user_notice` VALUES (4, 4, 2, 1, NULL, now(), now(), 0); +INSERT INTO `sys_user_notice` VALUES (5, 5, 2, 1, NULL, now(), now(), 0); +INSERT INTO `sys_user_notice` VALUES (6, 6, 2, 1, NULL, now(), now(), 0); +INSERT INTO `sys_user_notice` VALUES (7, 7, 2, 1, NULL, now(), now(), 0); +INSERT INTO `sys_user_notice` VALUES (8, 8, 2, 1, NULL, now(), now(), 0); +INSERT INTO `sys_user_notice` VALUES (9, 9, 2, 1, NULL, now(), now(), 0); +INSERT INTO `sys_user_notice` VALUES (10, 10, 2, 1, NULL, now(), now(), 0); diff --git a/src/main/java/com/youlai/boot/auth/controller/AuthController.java b/src/main/java/com/youlai/boot/auth/controller/AuthController.java index 93a75a76..215395a0 100644 --- a/src/main/java/com/youlai/boot/auth/controller/AuthController.java +++ b/src/main/java/com/youlai/boot/auth/controller/AuthController.java @@ -1,6 +1,7 @@ package com.youlai.boot.auth.controller; import com.youlai.boot.auth.model.vo.CaptchaVO; +import com.youlai.boot.auth.model.vo.WechatLoginResult; import com.youlai.boot.auth.model.dto.LoginRequest; import com.youlai.boot.common.enums.LogModuleEnum; import com.youlai.boot.core.web.Result; @@ -67,6 +68,39 @@ public class AuthController { return Result.success(); } + @Operation(summary = "微信小程序登录(个人小程序)") + @PostMapping("/wechat-miniapp/login") + @Log(value = "微信小程序登录", module = LogModuleEnum.LOGIN) + public Result loginByWechatMini( + @Parameter(description = "微信登录code", example = "xxx") @RequestParam String code + ) { + WechatLoginResult result = authService.loginByWechatMini(code); + return Result.success(result); + } + + @Operation(summary = "微信小程序一键登录(企业小程序)") + @PostMapping("/wechat-miniapp/phone-login") + @Log(value = "微信小程序一键登录", module = LogModuleEnum.LOGIN) + public Result loginByWechatMiniWithPhone( + @Parameter(description = "微信登录code", example = "xxx") @RequestParam String loginCode, + @Parameter(description = "手机号授权code", example = "xxx") @RequestParam String phoneCode + ) { + AuthenticationToken result = authService.wechatMiniLoginWithPhone(loginCode, phoneCode); + return Result.success(result); + } + + @Operation(summary = "微信小程序绑定手机号") + @PostMapping("/wechat-miniapp/bind-mobile") + @Log(value = "微信小程序绑定手机号", module = LogModuleEnum.LOGIN) + public Result bindMobileForWechatMini( + @Parameter(description = "微信openid") @RequestParam String openid, + @Parameter(description = "手机号", example = "18812345678") @RequestParam String mobile, + @Parameter(description = "短信验证码", example = "1234") @RequestParam String code + ) { + AuthenticationToken result = authService.bindMobileForWechatMini(openid, mobile, code); + return Result.success(result); + } + @Operation(summary = "退出登录") @DeleteMapping("/logout") @Log(value = "退出登录", module = LogModuleEnum.LOGIN) diff --git a/src/main/java/com/youlai/boot/auth/model/vo/WechatLoginResult.java b/src/main/java/com/youlai/boot/auth/model/vo/WechatLoginResult.java new file mode 100644 index 00000000..3e4d4012 --- /dev/null +++ b/src/main/java/com/youlai/boot/auth/model/vo/WechatLoginResult.java @@ -0,0 +1,60 @@ +package com.youlai.boot.auth.model.vo; + +import com.youlai.boot.security.model.AuthenticationToken; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 微信小程序登录结果 + */ +@Data +@Schema(description = "微信小程序登录结果") +public class WechatLoginResult { + + @Schema(description = "是否新用户") + private Boolean isNewUser; + + @Schema(description = "是否需要绑定手机号") + private Boolean needBindMobile; + + @Schema(description = "微信openid(绑定手机号时需要)") + private String openid; + + @Schema(description = "访问令牌") + private String accessToken; + + @Schema(description = "刷新令牌") + private String refreshToken; + + @Schema(description = "令牌类型") + private String tokenType; + + @Schema(description = "过期时间(秒)") + private Long expiresIn; + + /** + * 创建需要绑定手机号的结果 + */ + public static WechatLoginResult needBindMobile(String openid) { + WechatLoginResult result = new WechatLoginResult(); + result.setIsNewUser(true); + result.setNeedBindMobile(true); + result.setOpenid(openid); + return result; + } + + /** + * 创建登录成功的结果 + */ + public static WechatLoginResult success(AuthenticationToken token) { + WechatLoginResult result = new WechatLoginResult(); + result.setIsNewUser(false); + result.setNeedBindMobile(false); + result.setAccessToken(token.getAccessToken()); + result.setRefreshToken(token.getRefreshToken()); + result.setTokenType(token.getTokenType()); + result.setExpiresIn(token.getExpiresIn()); + return result; + } + +} diff --git a/src/main/java/com/youlai/boot/auth/service/AuthService.java b/src/main/java/com/youlai/boot/auth/service/AuthService.java index 8a7d47cb..9a87fd38 100644 --- a/src/main/java/com/youlai/boot/auth/service/AuthService.java +++ b/src/main/java/com/youlai/boot/auth/service/AuthService.java @@ -1,6 +1,7 @@ package com.youlai.boot.auth.service; import com.youlai.boot.auth.model.vo.CaptchaVO; +import com.youlai.boot.auth.model.vo.WechatLoginResult; import com.youlai.boot.security.model.AuthenticationToken; /** @@ -55,4 +56,31 @@ public interface AuthService { * @return 登录结果 */ AuthenticationToken loginBySms(String mobile, String code); + + /** + * 微信小程序登录(个人小程序) + * + * @param code 微信登录code + * @return 登录结果 + */ + WechatLoginResult loginByWechatMini(String code); + + /** + * 微信小程序一键登录(企业小程序) + * + * @param loginCode 微信登录code + * @param phoneCode 手机号授权code + * @return 登录结果 + */ + AuthenticationToken wechatMiniLoginWithPhone(String loginCode, String phoneCode); + + /** + * 微信小程序绑定手机号 + * + * @param openid 微信openid + * @param mobile 手机号 + * @param smsCode 短信验证码 + * @return 登录结果 + */ + AuthenticationToken bindMobileForWechatMini(String openid, String mobile, String smsCode); } diff --git a/src/main/java/com/youlai/boot/auth/service/impl/AuthServiceImpl.java b/src/main/java/com/youlai/boot/auth/service/impl/AuthServiceImpl.java index fc1a826b..c80a7aa9 100644 --- a/src/main/java/com/youlai/boot/auth/service/impl/AuthServiceImpl.java +++ b/src/main/java/com/youlai/boot/auth/service/impl/AuthServiceImpl.java @@ -1,21 +1,30 @@ package com.youlai.boot.auth.service.impl; +import cn.binarywang.wx.miniapp.api.WxMaService; import cn.hutool.captcha.AbstractCaptcha; import cn.hutool.captcha.CaptchaUtil; import cn.hutool.captcha.generator.CodeGenerator; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import com.youlai.boot.auth.model.vo.CaptchaVO; +import com.youlai.boot.auth.model.vo.WechatLoginResult; import com.youlai.boot.auth.service.AuthService; import com.youlai.boot.common.constant.RedisConstants; import com.youlai.boot.common.enums.CaptchaTypeEnum; import com.youlai.boot.config.property.CaptchaProperties; -import com.youlai.boot.support.sms.enums.SmsTypeEnum; -import com.youlai.boot.support.sms.service.SmsService; +import com.youlai.boot.security.exception.NeedBindMobileException; import com.youlai.boot.security.model.AuthenticationToken; import com.youlai.boot.security.model.SmsAuthenticationToken; +import com.youlai.boot.security.model.SysUserDetails; +import com.youlai.boot.security.model.WechatMiniAuthenticationToken; import com.youlai.boot.security.token.TokenManager; import com.youlai.boot.security.util.SecurityUtils; +import com.youlai.boot.support.sms.enums.SmsTypeEnum; +import com.youlai.boot.support.sms.service.SmsService; +import com.youlai.boot.system.enums.SocialPlatformEnum; +import com.youlai.boot.system.model.entity.User; +import com.youlai.boot.system.service.UserSocialService; +import com.youlai.boot.system.service.UserService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; @@ -24,8 +33,10 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.awt.*; +import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -50,6 +61,9 @@ public class AuthServiceImpl implements AuthService { private final SmsService smsService; private final RedisTemplate redisTemplate; + private final UserSocialService userSocialService; + private final UserService userService; + private final WxMaService wxMaService; /** * 用户名密码登录 @@ -198,4 +212,150 @@ public class AuthServiceImpl implements AuthService { return tokenManager.refreshToken(refreshToken); } + /** + * 微信小程序登录(个人小程序) + */ + @Override + public WechatLoginResult loginByWechatMini(String code) { + WechatMiniAuthenticationToken token = new WechatMiniAuthenticationToken(code); + + try { + Authentication authentication = authenticationManager.authenticate(token); + AuthenticationToken authToken = tokenManager.generateToken(authentication); + SecurityContextHolder.getContext().setAuthentication(authentication); + return WechatLoginResult.success(authToken); + } catch (NeedBindMobileException e) { + return WechatLoginResult.needBindMobile(e.getOpenid()); + } + } + + /** + * 微信小程序一键登录(企业小程序) + */ + @Override + @Transactional(rollbackFor = Exception.class) + public AuthenticationToken wechatMiniLoginWithPhone(String loginCode, String phoneCode) { + // 1. 用 loginCode 换取 openid + cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult session; + try { + session = wxMaService.jsCode2SessionInfo(loginCode); + } catch (Exception e) { + log.error("微信小程序一键登录失败:获取openid异常,loginCode={}", loginCode, e); + throw new IllegalArgumentException("微信登录失败:" + e.getMessage()); + } + String openid = session.getOpenid(); + + // 2. 用 phoneCode 换取手机号 + cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo phoneInfo; + try { + phoneInfo = wxMaService.getUserService().getPhoneNoInfo(phoneCode); + } catch (Exception e) { + log.error("微信小程序一键登录失败:获取手机号异常,phoneCode={}", phoneCode, e); + throw new IllegalArgumentException("获取手机号失败:" + e.getMessage()); + } + String mobile = phoneInfo.getPhoneNumber(); + + log.info("微信小程序一键登录:openid={}, mobile={}", openid, mobile); + + // 3. 查询或创建用户 + User user = userService.lambdaQuery() + .eq(User::getMobile, mobile) + .one(); + + if (user == null) { + user = new User(); + user.setMobile(mobile); + user.setUsername("wx_" + IdUtil.fastSimpleUUID().substring(0, 8)); + user.setNickname(phoneInfo.getNickName() != null ? phoneInfo.getNickName() : "微信用户"); + user.setAvatar(phoneInfo.getAvatarUrl()); + user.setStatus(1); + user.setIsDeleted(0); + user.setCreateTime(LocalDateTime.now()); + user.setUpdateTime(LocalDateTime.now()); + userService.save(user); + log.info("微信小程序一键登录:创建新用户,mobile={}, userId={}", mobile, user.getId()); + } + + // 4. 绑定 openid + userSocialService.bindOrUpdate( + user.getId(), + SocialPlatformEnum.WECHAT_MINI, + openid, + session.getUnionid(), + user.getNickname(), + user.getAvatar(), + session.getSessionKey() + ); + + // 5. 生成 token + SysUserDetails userDetails = new SysUserDetails(userService.getAuthInfoByMobile(mobile)); + AuthenticationToken authToken = tokenManager.generateToken(userDetails); + SecurityContextHolder.getContext().setAuthentication( + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()) + ); + + log.info("微信小程序一键登录成功:mobile={}, openid={}", mobile, openid); + + return authToken; + } + + /** + * 微信小程序绑定手机号 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public AuthenticationToken bindMobileForWechatMini(String openid, String mobile, String smsCode) { + // 1. 验证短信验证码 + String cacheKey = StrUtil.format(RedisConstants.Captcha.SMS_LOGIN_CODE, mobile); + String cachedCode = (String) redisTemplate.opsForValue().get(cacheKey); + + if (!StrUtil.equals(smsCode, cachedCode)) { + throw new IllegalArgumentException("验证码错误"); + } + + // 删除验证码 + redisTemplate.delete(cacheKey); + + // 2. 查询或创建用户 + User user = userService.lambdaQuery() + .eq(User::getMobile, mobile) + .one(); + + if (user == null) { + // 创建新用户 + user = new User(); + user.setMobile(mobile); + user.setUsername("wx_" + IdUtil.fastSimpleUUID().substring(0, 8)); + user.setNickname("微信用户"); + user.setStatus(1); + user.setIsDeleted(0); + user.setCreateTime(LocalDateTime.now()); + user.setUpdateTime(LocalDateTime.now()); + userService.save(user); + log.info("微信小程序绑定手机号:创建新用户,mobile={}, userId={}", mobile, user.getId()); + } + + // 3. 绑定第三方账号 + userSocialService.bindOrUpdate( + user.getId(), + SocialPlatformEnum.WECHAT_MINI, + openid, + null, + null, + null, + null + ); + + // 4. 生成token + SysUserDetails userDetails = new SysUserDetails(userService.getAuthInfoByMobile(mobile)); + AuthenticationToken authToken = tokenManager.generateToken(userDetails); + SecurityContextHolder.getContext().setAuthentication( + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()) + ); + + log.info("微信小程序绑定手机号成功:mobile={}, openid={}", mobile, openid); + + return authToken; + } + } diff --git a/src/main/java/com/youlai/boot/config/SecurityConfig.java b/src/main/java/com/youlai/boot/config/SecurityConfig.java index fe3539ce..0cc3f458 100644 --- a/src/main/java/com/youlai/boot/config/SecurityConfig.java +++ b/src/main/java/com/youlai/boot/config/SecurityConfig.java @@ -1,5 +1,6 @@ package com.youlai.boot.config; +import cn.binarywang.wx.miniapp.api.WxMaService; import cn.hutool.captcha.generator.CodeGenerator; import cn.hutool.core.util.ArrayUtil; import com.youlai.boot.config.property.SecurityProperties; @@ -9,9 +10,11 @@ import com.youlai.boot.security.filter.TokenAuthenticationFilter; import com.youlai.boot.security.handler.MyAccessDeniedHandler; import com.youlai.boot.security.handler.MyAuthenticationEntryPoint; import com.youlai.boot.security.provider.SmsAuthenticationProvider; +import com.youlai.boot.security.provider.WechatMiniAuthenticationProvider; import com.youlai.boot.security.token.TokenManager; import com.youlai.boot.security.service.SysUserDetailsService; import com.youlai.boot.system.service.ConfigService; +import com.youlai.boot.system.service.UserSocialService; import com.youlai.boot.system.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; @@ -54,6 +57,9 @@ public class SecurityConfig { private final ConfigService configService; private final SecurityProperties securityProperties; + private final WxMaService wxMaService; + private final UserSocialService userSocialService; + /** * 配置安全过滤链 SecurityFilterChain */ @@ -128,17 +134,27 @@ public class SecurityConfig { return new SmsAuthenticationProvider(userService, redisTemplate); } + /** + * 微信小程序认证 Provider + */ + @Bean + public WechatMiniAuthenticationProvider wechatMiniAuthenticationProvider() { + return new WechatMiniAuthenticationProvider(wxMaService, userSocialService); + } + /** * 认证管理器 */ @Bean public AuthenticationManager authenticationManager( DaoAuthenticationProvider daoAuthenticationProvider, - SmsAuthenticationProvider smsAuthenticationProvider + SmsAuthenticationProvider smsAuthenticationProvider, + WechatMiniAuthenticationProvider wechatMiniAuthenticationProvider ) { return new ProviderManager( daoAuthenticationProvider, - smsAuthenticationProvider + smsAuthenticationProvider, + wechatMiniAuthenticationProvider ); } } diff --git a/src/main/java/com/youlai/boot/config/WxMaConfiguration.java b/src/main/java/com/youlai/boot/config/WxMaConfiguration.java new file mode 100644 index 00000000..76c495dc --- /dev/null +++ b/src/main/java/com/youlai/boot/config/WxMaConfiguration.java @@ -0,0 +1,31 @@ +package com.youlai.boot.config; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; +import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 微信小程序配置 + */ +@Configuration +@EnableConfigurationProperties(WxMaProperties.class) +public class WxMaConfiguration { + + @Bean + public WxMaService wxMaService(WxMaProperties properties) { + WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl(); + config.setAppid(properties.getAppid()); + config.setSecret(properties.getSecret()); + config.setToken(properties.getToken()); + config.setAesKey(properties.getAesKey()); + config.setMsgDataFormat(properties.getMsgDataFormat()); + + WxMaService service = new WxMaServiceImpl(); + service.setWxMaConfig(config); + return service; + } + +} diff --git a/src/main/java/com/youlai/boot/config/WxMaProperties.java b/src/main/java/com/youlai/boot/config/WxMaProperties.java new file mode 100644 index 00000000..532e0df9 --- /dev/null +++ b/src/main/java/com/youlai/boot/config/WxMaProperties.java @@ -0,0 +1,38 @@ +package com.youlai.boot.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 微信小程序配置属性 + */ +@Data +@ConfigurationProperties(prefix = "wx.miniapp") +public class WxMaProperties { + + /** + * 小程序appid + */ + private String appid; + + /** + * 小程序Secret + */ + private String secret; + + /** + * 小程序token + */ + private String token; + + /** + * 小程序EncodingAESKey + */ + private String aesKey; + + /** + * 消息格式 + */ + private String msgDataFormat; + +} diff --git a/src/main/java/com/youlai/boot/security/exception/NeedBindMobileException.java b/src/main/java/com/youlai/boot/security/exception/NeedBindMobileException.java new file mode 100644 index 00000000..7d38fd21 --- /dev/null +++ b/src/main/java/com/youlai/boot/security/exception/NeedBindMobileException.java @@ -0,0 +1,28 @@ +package com.youlai.boot.security.exception; + +import org.springframework.security.core.AuthenticationException; + +/** + * 需要绑定手机号异常 + */ +public class NeedBindMobileException extends AuthenticationException { + + private final String openid; + + private final String sessionKey; + + public NeedBindMobileException(String openid, String sessionKey) { + super("需要绑定手机号"); + this.openid = openid; + this.sessionKey = sessionKey; + } + + public String getOpenid() { + return openid; + } + + public String getSessionKey() { + return sessionKey; + } + +} diff --git a/src/main/java/com/youlai/boot/security/model/WechatMiniAuthenticationToken.java b/src/main/java/com/youlai/boot/security/model/WechatMiniAuthenticationToken.java new file mode 100644 index 00000000..b32af476 --- /dev/null +++ b/src/main/java/com/youlai/boot/security/model/WechatMiniAuthenticationToken.java @@ -0,0 +1,73 @@ +package com.youlai.boot.security.model; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; + +import java.io.Serial; +import java.util.Collection; + +/** + * 微信小程序认证 Token + */ +public class WechatMiniAuthenticationToken extends AbstractAuthenticationToken { + + @Serial + private static final long serialVersionUID = 622L; + + /** + * 认证信息 + * 未认证时:微信code + * 已认证时:SysUserDetails 用户详情 + */ + private final Object principal; + + /** + * 凭证信息 + * 未认证时:null + * 已认证时:null + */ + private final Object credentials; + + /** + * 创建未认证的 Token + * + * @param code 微信小程序code + */ + public WechatMiniAuthenticationToken(String code) { + super(AuthorityUtils.NO_AUTHORITIES); + this.principal = code; + this.credentials = null; + setAuthenticated(false); + } + + /** + * 创建已认证的 Token + * + * @param principal 用户详情(SysUserDetails) + * @param authorities 授权信息 + */ + public WechatMiniAuthenticationToken(Object principal, Collection authorities) { + super(authorities); + this.principal = principal; + this.credentials = null; + super.setAuthenticated(true); + } + + /** + * 创建已认证的 Token(静态工厂方法) + */ + public static WechatMiniAuthenticationToken authenticated(Object principal, Collection authorities) { + return new WechatMiniAuthenticationToken(principal, authorities); + } + + @Override + public Object getCredentials() { + return this.credentials; + } + + @Override + public Object getPrincipal() { + return this.principal; + } +} diff --git a/src/main/java/com/youlai/boot/security/provider/WechatMiniAuthenticationProvider.java b/src/main/java/com/youlai/boot/security/provider/WechatMiniAuthenticationProvider.java new file mode 100644 index 00000000..c822f118 --- /dev/null +++ b/src/main/java/com/youlai/boot/security/provider/WechatMiniAuthenticationProvider.java @@ -0,0 +1,93 @@ +package com.youlai.boot.security.provider; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; +import cn.hutool.core.util.ObjectUtil; +import com.youlai.boot.security.exception.NeedBindMobileException; +import com.youlai.boot.security.model.SysUserDetails; +import com.youlai.boot.security.model.UserAuthInfo; +import com.youlai.boot.security.model.WechatMiniAuthenticationToken; +import com.youlai.boot.system.enums.SocialPlatformEnum; +import com.youlai.boot.system.model.entity.UserSocial; +import com.youlai.boot.system.service.UserSocialService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.DisabledException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +/** + * 微信小程序认证 Provider + */ +@Slf4j +@RequiredArgsConstructor +public class WechatMiniAuthenticationProvider implements AuthenticationProvider { + + private final WxMaService wxMaService; + private final UserSocialService userSocialService; + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + String code = (String) authentication.getPrincipal(); + + if (code == null || code.isEmpty()) { + log.warn("微信小程序登录失败:code为空"); + throw new IllegalArgumentException("code不能为空"); + } + + try { + // 1. 用 code 换取 openid + WxMaJscode2SessionResult session = wxMaService.jsCode2SessionInfo(code); + String openid = session.getOpenid(); + String sessionKey = session.getSessionKey(); + + log.info("微信小程序登录:openid={}", openid); + + // 2. 根据 openid 查询绑定信息 + UserSocial userSocial = userSocialService.getByPlatformAndOpenid(SocialPlatformEnum.WECHAT_MINI, openid); + + if (userSocial == null) { + // 未绑定,抛出异常提示需要绑定手机号 + log.info("微信小程序登录:用户未绑定手机号,openid={}", openid); + throw new NeedBindMobileException(openid, sessionKey); + } + + // 3. 获取用户认证信息 + UserAuthInfo userAuthInfo = userSocialService.getAuthInfoByOpenid(SocialPlatformEnum.WECHAT_MINI, openid); + + if (userAuthInfo == null) { + log.warn("微信小程序登录失败:用户不存在,openid={}", openid); + throw new UsernameNotFoundException("用户不存在"); + } + + // 4. 检查用户状态 + if (ObjectUtil.notEqual(userAuthInfo.getStatus(), 1)) { + log.warn("微信小程序登录失败:用户已禁用,username={}", userAuthInfo.getUsername()); + throw new DisabledException("用户已被禁用"); + } + + // 5. 更新 session_key + userSocialService.updateSessionKey(userSocial.getId(), sessionKey); + + // 6. 构建已认证 Token + SysUserDetails userDetails = new SysUserDetails(userAuthInfo); + + log.info("微信小程序登录成功:username={}, openid={}", userAuthInfo.getUsername(), openid); + + return WechatMiniAuthenticationToken.authenticated(userDetails, userDetails.getAuthorities()); + + } catch (WxErrorException e) { + log.error("微信小程序登录失败:调用微信接口异常,code={}", code, e); + throw new IllegalArgumentException("微信登录失败:" + e.getMessage()); + } + } + + @Override + public boolean supports(Class authentication) { + return WechatMiniAuthenticationToken.class.isAssignableFrom(authentication); + } + +} diff --git a/src/main/java/com/youlai/boot/system/enums/SocialPlatformEnum.java b/src/main/java/com/youlai/boot/system/enums/SocialPlatformEnum.java new file mode 100644 index 00000000..0779f594 --- /dev/null +++ b/src/main/java/com/youlai/boot/system/enums/SocialPlatformEnum.java @@ -0,0 +1,30 @@ +package com.youlai.boot.system.enums; + +import com.baomidou.mybatisplus.annotation.EnumValue; +import com.youlai.boot.common.base.IBaseEnum; +import lombok.Getter; + +/** + * 第三方登录平台类型枚举 + */ +@Getter +public enum SocialPlatformEnum implements IBaseEnum { + + WECHAT_MINI("WECHAT_MINI", "微信小程序"), + WECHAT_MP("WECHAT_MP", "微信公众号"), + WECHAT_OPEN("WECHAT_OPEN", "微信开放平台"), + ALIPAY("ALIPAY", "支付宝"), + QQ("QQ", "QQ"), + APPLE("APPLE", "Apple ID"); + + @EnumValue + private final String value; + + private final String label; + + SocialPlatformEnum(String value, String label) { + this.value = value; + this.label = label; + } + +} diff --git a/src/main/java/com/youlai/boot/system/mapper/UserSocialMapper.java b/src/main/java/com/youlai/boot/system/mapper/UserSocialMapper.java new file mode 100644 index 00000000..13c8ffc4 --- /dev/null +++ b/src/main/java/com/youlai/boot/system/mapper/UserSocialMapper.java @@ -0,0 +1,22 @@ +package com.youlai.boot.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.youlai.boot.security.model.UserAuthInfo; +import com.youlai.boot.system.model.entity.UserSocial; +import org.apache.ibatis.annotations.Mapper; + +/** + * 用户第三方账号绑定持久层 + */ +@Mapper +public interface UserSocialMapper extends BaseMapper { + + /** + * 根据用户ID获取认证信息 + * + * @param userId 用户ID + * @return 认证信息 + */ + UserAuthInfo getAuthInfoByUserId(Long userId); + +} diff --git a/src/main/java/com/youlai/boot/system/model/entity/UserSocial.java b/src/main/java/com/youlai/boot/system/model/entity/UserSocial.java new file mode 100644 index 00000000..f8ddff8c --- /dev/null +++ b/src/main/java/com/youlai/boot/system/model/entity/UserSocial.java @@ -0,0 +1,74 @@ +package com.youlai.boot.system.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.youlai.boot.common.base.BaseEntity; +import com.youlai.boot.system.enums.SocialPlatformEnum; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +/** + * 用户第三方账号绑定实体 + */ +@TableName("sys_user_social") +@Getter +@Setter +public class UserSocial { + + /** + * 主键ID + */ + private Long id; + + /** + * 用户ID + */ + private Long userId; + + /** + * 平台类型 + */ + private SocialPlatformEnum platform; + + /** + * 平台openid + */ + private String openid; + + /** + * 微信unionid + */ + private String unionid; + + /** + * 第三方昵称 + */ + private String nickname; + + /** + * 第三方头像URL + */ + private String avatar; + + /** + * 微信session_key + */ + private String sessionKey; + + /** + * 是否已验证(1-已验证 0-未验证) + */ + private Integer verified; + + /** + * 绑定时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/youlai/boot/system/service/UserSocialService.java b/src/main/java/com/youlai/boot/system/service/UserSocialService.java new file mode 100644 index 00000000..74f26e64 --- /dev/null +++ b/src/main/java/com/youlai/boot/system/service/UserSocialService.java @@ -0,0 +1,70 @@ +package com.youlai.boot.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.youlai.boot.security.model.UserAuthInfo; +import com.youlai.boot.system.enums.SocialPlatformEnum; +import com.youlai.boot.system.model.entity.UserSocial; + +/** + * 用户第三方账号绑定业务接口 + */ +public interface UserSocialService extends IService { + + /** + * 根据平台和openid查询绑定信息 + * + * @param platform 平台类型 + * @param openid openid + * @return 绑定信息 + */ + UserSocial getByPlatformAndOpenid(SocialPlatformEnum platform, String openid); + + /** + * 根据unionid查询绑定信息 + * + * @param unionid unionid + * @return 绑定信息 + */ + UserSocial getByUnionid(String unionid); + + /** + * 绑定或更新第三方账号 + * + * @param userId 用户ID + * @param platform 平台类型 + * @param openid openid + * @param unionid unionid + * @param nickname 昵称 + * @param avatar 头像 + * @param sessionKey session_key + * @return 绑定信息 + */ + UserSocial bindOrUpdate(Long userId, SocialPlatformEnum platform, String openid, String unionid, String nickname, String avatar, String sessionKey); + + /** + * 解绑第三方账号 + * + * @param userId 用户ID + * @param platform 平台类型 + * @return 是否成功 + */ + boolean unbind(Long userId, SocialPlatformEnum platform); + + /** + * 根据openid获取用户认证信息 + * + * @param platform 平台类型 + * @param openid openid + * @return 用户认证信息 + */ + UserAuthInfo getAuthInfoByOpenid(SocialPlatformEnum platform, String openid); + + /** + * 更新session_key + * + * @param id 绑定记录ID + * @param sessionKey session_key + */ + void updateSessionKey(Long id, String sessionKey); + +} diff --git a/src/main/java/com/youlai/boot/system/service/impl/UserSocialServiceImpl.java b/src/main/java/com/youlai/boot/system/service/impl/UserSocialServiceImpl.java new file mode 100644 index 00000000..70d0c91e --- /dev/null +++ b/src/main/java/com/youlai/boot/system/service/impl/UserSocialServiceImpl.java @@ -0,0 +1,107 @@ +package com.youlai.boot.system.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.youlai.boot.security.model.UserAuthInfo; +import com.youlai.boot.system.enums.SocialPlatformEnum; +import com.youlai.boot.system.mapper.UserSocialMapper; +import com.youlai.boot.system.model.entity.UserSocial; +import com.youlai.boot.system.service.UserSocialService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +/** + * 用户第三方账号绑定业务实现 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class UserSocialServiceImpl extends ServiceImpl implements UserSocialService { + + @Override + public UserSocial getByPlatformAndOpenid(SocialPlatformEnum platform, String openid) { + return getOne(new LambdaQueryWrapper() + .eq(UserSocial::getPlatform, platform) + .eq(UserSocial::getOpenid, openid)); + } + + @Override + public UserSocial getByUnionid(String unionid) { + if (StrUtil.isBlank(unionid)) { + return null; + } + return getOne(new LambdaQueryWrapper() + .eq(UserSocial::getUnionid, unionid)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public UserSocial bindOrUpdate(Long userId, SocialPlatformEnum platform, String openid, String unionid, String nickname, String avatar, String sessionKey) { + UserSocial userSocial = getByPlatformAndOpenid(platform, openid); + LocalDateTime now = LocalDateTime.now(); + + if (userSocial == null) { + userSocial = new UserSocial(); + userSocial.setUserId(userId); + userSocial.setPlatform(platform); + userSocial.setOpenid(openid); + userSocial.setUnionid(unionid); + userSocial.setNickname(nickname); + userSocial.setAvatar(avatar); + userSocial.setSessionKey(sessionKey); + userSocial.setVerified(1); + userSocial.setCreateTime(now); + userSocial.setUpdateTime(now); + save(userSocial); + log.info("第三方账号绑定成功:userId={}, platform={}, openid={}", userId, platform, openid); + } else { + userSocial.setUserId(userId); + userSocial.setUnionid(unionid); + userSocial.setNickname(nickname); + userSocial.setAvatar(avatar); + userSocial.setSessionKey(sessionKey); + userSocial.setUpdateTime(now); + updateById(userSocial); + log.info("第三方账号更新成功:userId={}, platform={}, openid={}", userId, platform, openid); + } + + return userSocial; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean unbind(Long userId, SocialPlatformEnum platform) { + boolean removed = remove(new LambdaQueryWrapper() + .eq(UserSocial::getUserId, userId) + .eq(UserSocial::getPlatform, platform)); + if (removed) { + log.info("第三方账号解绑成功:userId={}, platform={}", userId, platform); + } + return removed; + } + + @Override + public UserAuthInfo getAuthInfoByOpenid(SocialPlatformEnum platform, String openid) { + UserSocial userSocial = getByPlatformAndOpenid(platform, openid); + if (userSocial == null) { + return null; + } + return baseMapper.getAuthInfoByUserId(userSocial.getUserId()); + } + + @Override + public void updateSessionKey(Long id, String sessionKey) { + UserSocial userSocial = getById(id); + if (userSocial != null) { + userSocial.setSessionKey(sessionKey); + userSocial.setUpdateTime(LocalDateTime.now()); + updateById(userSocial); + } + } + +} diff --git a/src/main/java/com/youlai/boot/tool/codegen/service/impl/CodegenServiceImpl.java b/src/main/java/com/youlai/boot/tool/codegen/service/impl/CodegenServiceImpl.java index 83bb8623..d4326026 100644 --- a/src/main/java/com/youlai/boot/tool/codegen/service/impl/CodegenServiceImpl.java +++ b/src/main/java/com/youlai/boot/tool/codegen/service/impl/CodegenServiceImpl.java @@ -85,13 +85,10 @@ public class CodegenServiceImpl implements CodegenService { return templateConfig.getTemplatePath(); } if ("API".equals(templateName)) { - return "codegen/api.js.vm"; + return "codegen/frontend/js/api.js.vm"; } if ("VIEW".equals(templateName)) { - return "codegen/index.js.vue.vm"; - } - if ("API_TYPES".equals(templateName)) { - return "codegen/api-types.js.vm"; + return "codegen/frontend/js/index.js.vue.vm"; } return templateConfig.getTemplatePath(); } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 84c8bf31..b6e919b1 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -210,8 +210,11 @@ captcha: # 验证码有效期(秒) expire-seconds: 120 -# 微信小程配置 +# 微信小程序配置 wx: miniapp: - app-id: xxxxxx - app-secret: xxxxxx + appid: your-app-id + secret: your-app-secret + token: your-token + aes-key: your-aes-key + msg-data-format: JSON diff --git a/src/main/resources/codegen.yml b/src/main/resources/codegen.yml index f8cf8111..5f7b0928 100644 --- a/src/main/resources/codegen.yml +++ b/src/main/resources/codegen.yml @@ -17,45 +17,45 @@ codegen: ## 模板配置 templateConfigs: API: - templatePath: codegen/api.ts.vm + templatePath: codegen/frontend/ts/api.ts.vm subpackageName: api extension: .ts API_TYPES: - templatePath: codegen/api-types.ts.vm + templatePath: codegen/frontend/ts/api-types.ts.vm subpackageName: types extension: .ts VIEW: - templatePath: codegen/index.vue.vm + templatePath: codegen/frontend/ts/index.vue.vm subpackageName: views extension: .vue Controller: - templatePath: codegen/controller.java.vm + templatePath: codegen/backend/controller.java.vm subpackageName: controller Service: - templatePath: codegen/service.java.vm + templatePath: codegen/backend/service.java.vm subpackageName: service ServiceImpl: - templatePath: codegen/serviceImpl.java.vm + templatePath: codegen/backend/serviceImpl.java.vm subpackageName: service.impl Mapper: - templatePath: codegen/mapper.java.vm + templatePath: codegen/backend/mapper.java.vm subpackageName: mapper MapperXml: - templatePath: codegen/mapper.xml.vm + templatePath: codegen/backend/mapper.xml.vm subpackageName: mapper extension: .xml Converter: - templatePath: codegen/converter.java.vm + templatePath: codegen/backend/converter.java.vm subpackageName: converter Query: - templatePath: codegen/query.java.vm + templatePath: codegen/backend/query.java.vm subpackageName: model.query Form: - templatePath: codegen/form.java.vm + templatePath: codegen/backend/form.java.vm subpackageName: model.form Vo: - templatePath: codegen/vo.java.vm + templatePath: codegen/backend/vo.java.vm subpackageName: model.vo Entity: - templatePath: codegen/entity.java.vm + templatePath: codegen/backend/entity.java.vm subpackageName: model.entity diff --git a/src/main/resources/mapper/system/UserSocialMapper.xml b/src/main/resources/mapper/system/UserSocialMapper.xml new file mode 100644 index 00000000..2fb66fc7 --- /dev/null +++ b/src/main/resources/mapper/system/UserSocialMapper.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/templates/codegen/controller.java.vm b/src/main/resources/templates/codegen/backend/controller.java.vm similarity index 100% rename from src/main/resources/templates/codegen/controller.java.vm rename to src/main/resources/templates/codegen/backend/controller.java.vm diff --git a/src/main/resources/templates/codegen/converter.java.vm b/src/main/resources/templates/codegen/backend/converter.java.vm similarity index 99% rename from src/main/resources/templates/codegen/converter.java.vm rename to src/main/resources/templates/codegen/backend/converter.java.vm index b56f6234..ed1dd163 100644 --- a/src/main/resources/templates/codegen/converter.java.vm +++ b/src/main/resources/templates/codegen/backend/converter.java.vm @@ -17,4 +17,4 @@ public interface ${entityName}Converter{ ${entityName}Form toForm(${entityName} entity); ${entityName} toEntity(${entityName}Form formData); -} \ No newline at end of file +} diff --git a/src/main/resources/templates/codegen/entity.java.vm b/src/main/resources/templates/codegen/backend/entity.java.vm similarity index 100% rename from src/main/resources/templates/codegen/entity.java.vm rename to src/main/resources/templates/codegen/backend/entity.java.vm diff --git a/src/main/resources/templates/codegen/form.java.vm b/src/main/resources/templates/codegen/backend/form.java.vm similarity index 100% rename from src/main/resources/templates/codegen/form.java.vm rename to src/main/resources/templates/codegen/backend/form.java.vm diff --git a/src/main/resources/templates/codegen/mapper.java.vm b/src/main/resources/templates/codegen/backend/mapper.java.vm similarity index 100% rename from src/main/resources/templates/codegen/mapper.java.vm rename to src/main/resources/templates/codegen/backend/mapper.java.vm diff --git a/src/main/resources/templates/codegen/mapper.xml.vm b/src/main/resources/templates/codegen/backend/mapper.xml.vm similarity index 100% rename from src/main/resources/templates/codegen/mapper.xml.vm rename to src/main/resources/templates/codegen/backend/mapper.xml.vm diff --git a/src/main/resources/templates/codegen/query.java.vm b/src/main/resources/templates/codegen/backend/query.java.vm similarity index 100% rename from src/main/resources/templates/codegen/query.java.vm rename to src/main/resources/templates/codegen/backend/query.java.vm diff --git a/src/main/resources/templates/codegen/service.java.vm b/src/main/resources/templates/codegen/backend/service.java.vm similarity index 100% rename from src/main/resources/templates/codegen/service.java.vm rename to src/main/resources/templates/codegen/backend/service.java.vm diff --git a/src/main/resources/templates/codegen/serviceImpl.java.vm b/src/main/resources/templates/codegen/backend/serviceImpl.java.vm similarity index 100% rename from src/main/resources/templates/codegen/serviceImpl.java.vm rename to src/main/resources/templates/codegen/backend/serviceImpl.java.vm diff --git a/src/main/resources/templates/codegen/vo.java.vm b/src/main/resources/templates/codegen/backend/vo.java.vm similarity index 100% rename from src/main/resources/templates/codegen/vo.java.vm rename to src/main/resources/templates/codegen/backend/vo.java.vm diff --git a/src/main/resources/templates/codegen/api.js.vm b/src/main/resources/templates/codegen/frontend/js/api.js.vm similarity index 100% rename from src/main/resources/templates/codegen/api.js.vm rename to src/main/resources/templates/codegen/frontend/js/api.js.vm diff --git a/src/main/resources/templates/codegen/index.curd.js.vue.vm b/src/main/resources/templates/codegen/frontend/js/index.curd.js.vue.vm similarity index 100% rename from src/main/resources/templates/codegen/index.curd.js.vue.vm rename to src/main/resources/templates/codegen/frontend/js/index.curd.js.vue.vm diff --git a/src/main/resources/templates/codegen/index.js.vue.vm b/src/main/resources/templates/codegen/frontend/js/index.js.vue.vm similarity index 88% rename from src/main/resources/templates/codegen/index.js.vue.vm rename to src/main/resources/templates/codegen/frontend/js/index.js.vue.vm index ca76214a..462d2b5c 100644 --- a/src/main/resources/templates/codegen/index.js.vue.vm +++ b/src/main/resources/templates/codegen/frontend/js/index.js.vue.vm @@ -1,7 +1,7 @@