diff --git a/.vscode/launch.json b/.vscode/launch.json index 8f7c445b..6feed366 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,8 @@ "request": "launch", "mode": "debug", "program": "./restagent/restagent.go", - "console": "integratedTerminal" + "console": "integratedTerminal", + "args": ["--env", "local"] // 走开发配置 }, { "name": "DEBUG restagent", diff --git a/README.md b/README.md index 8c3dfa92..2b9f0b11 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ # ems_backend -后端 \ No newline at end of file +后端 + +## 版本发布空间 + +\\192.168.1.205\share\release\omc diff --git a/database/upgrade/20231017.sql b/database/upgrade/20231017.sql new file mode 100644 index 00000000..14511138 --- /dev/null +++ b/database/upgrade/20231017.sql @@ -0,0 +1,719 @@ +SET FOREIGN_KEY_CHECKS=0; + +CREATE TABLE `omc_db`.`sys_dept` ( + `dept_id` bigint NOT NULL AUTO_INCREMENT COMMENT '部门id', + `parent_id` bigint NULL DEFAULT 0 COMMENT '父部门id 默认0', + `ancestors` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '祖级列表', + `dept_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '部门名称', + `order_num` 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` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '部门状态(0停用 1正常)', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', + `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` bigint NULL DEFAULT 0 COMMENT '创建时间', + `update_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` bigint NULL DEFAULT 0 COMMENT '更新时间', + PRIMARY KEY (`dept_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '部门表' ROW_FORMAT = DYNAMIC; + +CREATE TABLE `omc_db`.`sys_job` ( + `job_id` bigint NOT NULL AUTO_INCREMENT COMMENT '任务ID', + `job_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '任务名称', + `job_group` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'DEFAULT' COMMENT '任务组名', + `invoke_target` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调用目标字符串', + `target_params` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '调用目标传入参数', + `cron_expression` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT 'cron执行表达式', + `misfire_policy` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '3' COMMENT '计划执行错误策略(1立即执行 2执行一次 3放弃执行)', + `concurrent` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '是否并发执行(0禁止 1允许)', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '任务状态(0暂停 1正常)', + `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` bigint NULL DEFAULT 0 COMMENT '创建时间', + `update_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` bigint NULL DEFAULT 0 COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '备注', + PRIMARY KEY (`job_id`, `job_name`, `job_group`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '调度任务调度表' ROW_FORMAT = DYNAMIC; + +CREATE TABLE `omc_db`.`sys_job_log` ( + `job_log_id` bigint NOT NULL AUTO_INCREMENT COMMENT '任务日志ID', + `job_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务名称', + `job_group` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务组名', + `invoke_target` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调用目标字符串', + `target_params` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '调用目标传入参数', + `job_msg` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '日志信息', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '执行状态(0失败 1正常)', + `create_time` bigint NULL DEFAULT 0 COMMENT '创建时间', + `cost_time` bigint NULL DEFAULT 0 COMMENT '消耗时间(毫秒)', + PRIMARY KEY (`job_log_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '调度任务调度日志表' ROW_FORMAT = DYNAMIC; + +CREATE TABLE `omc_db`.`sys_log_login` ( + `login_id` bigint NOT NULL AUTO_INCREMENT COMMENT '登录ID', + `user_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户账号', + `ipaddr` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '登录IP地址', + `login_location` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '登录地点', + `browser` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '浏览器类型', + `os` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作系统', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '登录状态(0失败 1成功)', + `msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '提示消息', + `login_time` bigint NULL DEFAULT 0 COMMENT '登录时间', + PRIMARY KEY (`login_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统登录日志表' ROW_FORMAT = DYNAMIC; + +CREATE TABLE `omc_db`.`sys_log_operate` ( + `oper_id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志主键', + `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '模块标题', + `business_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '业务类型(0其它 1新增 2修改 3删除 4授权 5导出 6导入 7强退 8清空数据)', + `method` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '方法名称', + `request_method` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求方式', + `operator_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '操作人员类别(0其它 1后台用户 2手机端用户)', + `oper_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作人员', + `dept_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '部门名称', + `oper_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求URL', + `oper_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '主机地址', + `oper_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作地点', + `oper_param` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求参数', + `oper_msg` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作消息', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '操作状态(0异常 1正常)', + `oper_time` bigint NULL DEFAULT 0 COMMENT '操作时间', + `cost_time` bigint NULL DEFAULT 0 COMMENT '消耗时间(毫秒)', + PRIMARY KEY (`oper_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统操作日志表' ROW_FORMAT = DYNAMIC; + +CREATE TABLE `omc_db`.`sys_post` ( + `post_id` bigint NOT NULL AUTO_INCREMENT COMMENT '岗位ID', + `post_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '岗位编码', + `post_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '岗位名称', + `post_sort` int NULL DEFAULT 0 COMMENT '显示顺序', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0停用 1正常)', + `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` bigint NULL DEFAULT 0 COMMENT '创建时间', + `update_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` bigint NULL DEFAULT 0 COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`post_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '岗位信息表' ROW_FORMAT = DYNAMIC; + +CREATE TABLE `omc_db`.`sys_role_dept` ( + `role_id` bigint NOT NULL COMMENT '角色ID', + `dept_id` bigint NOT NULL COMMENT '部门ID', + PRIMARY KEY (`role_id`, `dept_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色和部门关联表' ROW_FORMAT = DYNAMIC; + +CREATE TABLE `omc_db`.`sys_user` ( + `user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `dept_id` bigint NULL DEFAULT NULL COMMENT '部门ID', + `user_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户账号', + `nick_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户昵称', + `user_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'sys' COMMENT '用户类型(sys系统用户)', + `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户邮箱', + `phonenumber` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '手机号码', + `sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '用户性别(0未知 1男 2女)', + `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '头像地址', + `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '密码', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '帐号状态(0停用 1正常)', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', + `login_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '最后登录IP', + `login_date` bigint NULL DEFAULT 0 COMMENT '最后登录时间', + `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` bigint NULL DEFAULT 0 COMMENT '创建时间', + `update_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` bigint NULL DEFAULT 0 COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`user_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户信息表' ROW_FORMAT = DYNAMIC; + +CREATE TABLE `omc_db`.`sys_user_post` ( + `user_id` bigint NOT NULL COMMENT '用户ID', + `post_id` bigint NOT NULL COMMENT '岗位ID', + PRIMARY KEY (`user_id`, `post_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户与岗位关联表' ROW_FORMAT = DYNAMIC; + + +-- 数据插入 + +INSERT INTO `omc_db`.`sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (5, '用户管理-密码锁定时间', 'sys.user.lockTime', '10', 'Y', 'AGrand', 1693911541269, '', 0, '密码锁定时间,单位分钟(默认10分钟)'); + +INSERT INTO `omc_db`.`sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (6, '用户管理-授权有效时间', 'sys.user.expiresIn', '120', 'Y', 'AGrand', 1693911541269, '', 0, '令牌有效期(默认120分钟)'); + +INSERT INTO `omc_db`.`sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (7, '用户管理-多端用户授权登录', 'sys.user.multi_login', 'true', 'Y', 'AGrand', 1693911541269, '', 0, '多端授权登录'); + +UPDATE `omc_db`.`sys_config` SET `config_name` = '用户管理-账号初始密码', `config_key` = 'sys.user.initPassword', `config_value` = 'Abcd@1234..', `config_type` = 'Y', `create_by` = 'AGrand', `create_time` = 1693908079656, `update_by` = '', `update_time` = 0, `remark` = '导入用户初始化密码 123456' WHERE `config_id` = 1; + +UPDATE `omc_db`.`sys_config` SET `config_name` = '账号自助-验证码开关', `config_key` = 'sys.account.captchaEnabled', `config_value` = 'false', `config_type` = 'Y', `create_by` = 'AGrand', `create_time` = 1693908079667, `update_by` = '', `update_time` = 0, `remark` = '是否开启验证码功能(true开启,false关闭)' WHERE `config_id` = 2; + +UPDATE `omc_db`.`sys_config` SET `config_name` = '账号自助-是否开启用户注册功能', `config_key` = 'sys.account.registerUser', `config_value` = 'false', `config_type` = 'Y', `create_by` = 'AGrand', `create_time` = 1693908079669, `update_by` = '', `update_time` = 0, `remark` = '是否开启注册用户功能(true开启,false关闭)' WHERE `config_id` = 3; + +UPDATE `omc_db`.`sys_config` SET `config_name` = '用户管理-密码最大错误次数', `config_key` = 'sys.user.maxRetryCount', `config_value` = '5', `config_type` = 'Y', `create_by` = 'AGrand', `create_time` = 1693908079680, `update_by` = '', `update_time` = 0, `remark` = '密码最大错误次数' WHERE `config_id` = 4; + +UPDATE `omc_db`.`sys_config` SET `config_name` = '测试', `config_key` = 'test', `config_value` = 'test', `config_type` = 'Y', `create_by` = 'AGrand', `create_time` = 1693911541269, `update_by` = 'AGrand', `update_time` = 1693911586418, `remark` = '测试' WHERE `config_id` = 100; + +INSERT INTO `omc_db`.`sys_dept` (`dept_id`, `parent_id`, `ancestors`, `dept_name`, `order_num`, `leader`, `phone`, `email`, `status`, `del_flag`, `create_by`, `create_time`, `update_by`, `update_time`) VALUES (100, 0, '0', '千通科技', 0, 'AGrand', '', '', '1', '0', 'AGrand', 1697091866188, '', NULL); + +INSERT INTO `omc_db`.`sys_dept` (`dept_id`, `parent_id`, `ancestors`, `dept_name`, `order_num`, `leader`, `phone`, `email`, `status`, `del_flag`, `create_by`, `create_time`, `update_by`, `update_time`) VALUES (101, 100, '0,100', '深圳总公司', 1, 'AGrand', '', '', '1', '0', 'AGrand', 1697091866192, '', NULL); + +INSERT INTO `omc_db`.`sys_dept` (`dept_id`, `parent_id`, `ancestors`, `dept_name`, `order_num`, `leader`, `phone`, `email`, `status`, `del_flag`, `create_by`, `create_time`, `update_by`, `update_time`) VALUES (102, 101, '0,100,101', '研发部门', 1, 'AGrand', '', '', '1', '0', 'AGrand', 1697091866194, 'AGrand', 1697113796216); + +INSERT INTO `omc_db`.`sys_dept` (`dept_id`, `parent_id`, `ancestors`, `dept_name`, `order_num`, `leader`, `phone`, `email`, `status`, `del_flag`, `create_by`, `create_time`, `update_by`, `update_time`) VALUES (103, 101, '0,100,101', '市场部门', 2, 'AGrand', '', '', '1', '0', 'AGrand', 1697091866197, '', NULL); + +INSERT INTO `omc_db`.`sys_dept` (`dept_id`, `parent_id`, `ancestors`, `dept_name`, `order_num`, `leader`, `phone`, `email`, `status`, `del_flag`, `create_by`, `create_time`, `update_by`, `update_time`) VALUES (104, 101, '0,100,101', '测试部门', 3, 'AGrand', '', '', '1', '0', 'AGrand', 1697091866200, '', NULL); + +INSERT INTO `omc_db`.`sys_dept` (`dept_id`, `parent_id`, `ancestors`, `dept_name`, `order_num`, `leader`, `phone`, `email`, `status`, `del_flag`, `create_by`, `create_time`, `update_by`, `update_time`) VALUES (105, 101, '0,100,101', '财务部门', 4, 'AGrand', '', '', '1', '0', 'AGrand', 1697091866203, '', NULL); + +INSERT INTO `omc_db`.`sys_dept` (`dept_id`, `parent_id`, `ancestors`, `dept_name`, `order_num`, `leader`, `phone`, `email`, `status`, `del_flag`, `create_by`, `create_time`, `update_by`, `update_time`) VALUES (106, 101, '0,100,101', '运维部门', 5, 'AGrand', '', '', '1', '0', 'AGrand', 1697091866205, '', NULL); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (29, 0, '接口跟踪', 'Interface', 'trace_type', NULL, 'blue ', '1', 'AGrand', 1697443290808, '', 0, '接口跟踪'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (30, 1, '设备跟踪', 'Device', 'trace_type', NULL, 'gold', '1', 'AGrand', 1697443307336, 'AGrand', 1697443541082, '设备跟踪'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (31, 2, '用户跟踪', 'UE', 'trace_type', NULL, 'green', '1', 'AGrand', 1697443562042, 'AGrand', 1697443566327, '用户跟踪'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (32, 0, '下载', 'DOWNLOAD', 'operation_log_type', NULL, 'pink', '1', 'AGrand', 1697444092166, 'AGrand', 1697444104048, '下载DOWNLOAD'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (33, 1, '激活', 'Activation', 'operation_log_type', NULL, 'blue ', '1', 'AGrand', 1697444134968, '', 0, 'Activation激活'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (34, 2, '新增', 'ADD', 'operation_log_type', NULL, 'cyan', '1', 'AGrand', 1697444151864, 'AGrand', 1697444156095, 'ADD'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (35, 3, '其他', 'AUTO', 'operation_log_type', NULL, 'gold', '1', 'AGrand', 1697444199990, '', 0, 'AUTO'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (36, 4, '回退', 'BACK', 'operation_log_type', NULL, 'blue ', '1', 'AGrand', 1697444219836, '', 0, 'BACK'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (37, 5, '删除', 'DELETE', 'operation_log_type', NULL, 'red', '1', 'AGrand', 1697444242927, '', 0, '删除DELETE'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (38, 6, '分配', 'Distribute', 'operation_log_type', NULL, 'yellow', '1', 'AGrand', 1697444267174, '', 0, 'Distribute'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (39, 7, '导出', 'EXPORT', 'operation_log_type', NULL, 'green', '1', 'AGrand', 1697444289982, '', 0, 'EXPORT'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (40, 8, '查询', 'SELECT', 'operation_log_type', NULL, 'gold', '1', 'AGrand', 1697444311262, '', 0, 'SELECT'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (41, 9, '设置', 'SET', 'operation_log_type', NULL, NULL, '1', 'AGrand', 1697444336102, '', 0, 'SET'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (42, 10, '更新', 'UPDATE', 'operation_log_type', NULL, 'magenta', '1', 'AGrand', 1697444358599, '', 0, 'UPDATE'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (43, 11, '上传', 'UPLOAD', 'operation_log_type', NULL, 'yellow', '1', 'AGrand', 1697444378660, '', 0, 'UPLOAD'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (44, 12, '查看', 'View', 'operation_log_type', NULL, 'purple', '1', 'AGrand', 1697444400940, 'AGrand', 1697444414022, 'View'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (45, 0, '历史告警', '0', 'alarm_status', NULL, 'orange', '1', 'AGrand', 1697444554302, '', 0, '历史告警'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (46, 1, '活动告警', '1', 'alarm_status', NULL, 'pink', '1', 'AGrand', 1697444569120, '', 0, '活动告警'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (47, 0, '登录', '0', 'security_log_type', NULL, NULL, '1', 'AGrand', 1697444719117, '', 0, 'Login'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (48, 1, '登出', '1', 'security_log_type', NULL, 'cyan', '1', 'AGrand', 1697444738749, 'AGrand', 1697444742784, 'Logout'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (49, 2, '新增', '2', 'security_log_type', NULL, 'green', '1', 'AGrand', 1697444763116, 'AGrand', 1697444766525, 'Add'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (50, 3, '更新', '3', 'security_log_type', NULL, 'lime', '1', 'AGrand', 1697444786405, 'AGrand', 1697444795272, 'Update'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (51, 4, '删除', '4', 'security_log_type', NULL, NULL, '1', 'AGrand', 1697444809133, '', 0, 'Delete'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (52, 5, '锁定', '5', 'security_log_type', NULL, NULL, '1', 'AGrand', 1697444827514, 'AGrand', 1697444838485, 'Lock'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (53, 6, '解锁', '6', 'security_log_type', NULL, 'gold', '1', 'AGrand', 1697444882860, '', 0, 'Unlock'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (54, 7, '重置', '7', 'security_log_type', NULL, 'cyan', '1', 'AGrand', 1697444906929, '', 0, 'Restart'); + +INSERT INTO `omc_db`.`sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `tag_class`, `tag_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (55, 8, '停用', '8', 'security_log_type', NULL, 'blue ', '1', 'AGrand', 1697444945199, 'AGrand', 1697445915268, 'Stop'); + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 1, `dict_label` = '未知', `dict_value` = '0', `dict_type` = 'sys_user_sex', `tag_class` = '', `tag_type` = '', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079577, `update_by` = '', `update_time` = 0, `remark` = '性别未知' WHERE `dict_code` = 1; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 2, `dict_label` = '男', `dict_value` = '1', `dict_type` = 'sys_user_sex', `tag_class` = '', `tag_type` = '', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079583, `update_by` = '', `update_time` = 0, `remark` = '性别男' WHERE `dict_code` = 2; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 3, `dict_label` = '女', `dict_value` = '2', `dict_type` = 'sys_user_sex', `tag_class` = '', `tag_type` = '', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079585, `update_by` = '', `update_time` = 0, `remark` = '性别女' WHERE `dict_code` = 3; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 1, `dict_label` = '显示', `dict_value` = '1', `dict_type` = 'sys_show_hide', `tag_class` = '', `tag_type` = 'success', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079587, `update_by` = '', `update_time` = 0, `remark` = '显示菜单' WHERE `dict_code` = 4; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 2, `dict_label` = '隐藏', `dict_value` = '0', `dict_type` = 'sys_show_hide', `tag_class` = '', `tag_type` = 'error', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079589, `update_by` = '', `update_time` = 0, `remark` = '隐藏菜单' WHERE `dict_code` = 5; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 1, `dict_label` = '正常', `dict_value` = '1', `dict_type` = 'sys_normal_disable', `tag_class` = '', `tag_type` = 'success', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079591, `update_by` = '', `update_time` = 0, `remark` = '正常状态' WHERE `dict_code` = 6; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 2, `dict_label` = '停用', `dict_value` = '0', `dict_type` = 'sys_normal_disable', `tag_class` = '', `tag_type` = 'error', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079593, `update_by` = '', `update_time` = 0, `remark` = '停用状态' WHERE `dict_code` = 7; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 1, `dict_label` = '正常', `dict_value` = '1', `dict_type` = 'sys_job_status', `tag_class` = '', `tag_type` = 'success', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079595, `update_by` = '', `update_time` = 0, `remark` = '正常状态' WHERE `dict_code` = 8; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 2, `dict_label` = '暂停', `dict_value` = '0', `dict_type` = 'sys_job_status', `tag_class` = '', `tag_type` = 'error', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079597, `update_by` = '', `update_time` = 0, `remark` = '停用状态' WHERE `dict_code` = 9; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 1, `dict_label` = '默认', `dict_value` = 'DEFAULT', `dict_type` = 'sys_job_group', `tag_class` = '', `tag_type` = '', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079599, `update_by` = '', `update_time` = 0, `remark` = '默认分组' WHERE `dict_code` = 10; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 2, `dict_label` = '系统', `dict_value` = 'SYSTEM', `dict_type` = 'sys_job_group', `tag_class` = '', `tag_type` = '', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079601, `update_by` = '', `update_time` = 0, `remark` = '系统分组' WHERE `dict_code` = 11; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 1, `dict_label` = '是', `dict_value` = 'Y', `dict_type` = 'sys_yes_no', `tag_class` = '', `tag_type` = 'processing', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079602, `update_by` = '', `update_time` = 0, `remark` = '系统默认是' WHERE `dict_code` = 12; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 2, `dict_label` = '否', `dict_value` = 'N', `dict_type` = 'sys_yes_no', `tag_class` = '', `tag_type` = 'error', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079604, `update_by` = '', `update_time` = 0, `remark` = '系统默认否' WHERE `dict_code` = 13; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 1, `dict_label` = '通知', `dict_value` = '1', `dict_type` = 'sys_notice_type', `tag_class` = '', `tag_type` = 'warning', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079606, `update_by` = '', `update_time` = 0, `remark` = '通知' WHERE `dict_code` = 14; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 2, `dict_label` = '公告', `dict_value` = '2', `dict_type` = 'sys_notice_type', `tag_class` = '', `tag_type` = 'processing', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079608, `update_by` = '', `update_time` = 0, `remark` = '公告' WHERE `dict_code` = 15; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 1, `dict_label` = '正常', `dict_value` = '1', `dict_type` = 'sys_notice_status', `tag_class` = '', `tag_type` = 'success', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079610, `update_by` = '', `update_time` = 0, `remark` = '正常状态' WHERE `dict_code` = 16; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 2, `dict_label` = '关闭', `dict_value` = '0', `dict_type` = 'sys_notice_status', `tag_class` = '', `tag_type` = 'error', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079612, `update_by` = '', `update_time` = 0, `remark` = '关闭状态' WHERE `dict_code` = 17; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 99, `dict_label` = '其他', `dict_value` = '0', `dict_type` = 'sys_oper_type', `tag_class` = '', `tag_type` = 'processing', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079613, `update_by` = '', `update_time` = 0, `remark` = '其他操作' WHERE `dict_code` = 18; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 1, `dict_label` = '新增', `dict_value` = '1', `dict_type` = 'sys_oper_type', `tag_class` = '', `tag_type` = 'processing', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079615, `update_by` = '', `update_time` = 0, `remark` = '新增操作' WHERE `dict_code` = 19; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 2, `dict_label` = '修改', `dict_value` = '2', `dict_type` = 'sys_oper_type', `tag_class` = '', `tag_type` = 'processing', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079618, `update_by` = '', `update_time` = 0, `remark` = '修改操作' WHERE `dict_code` = 20; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 3, `dict_label` = '删除', `dict_value` = '3', `dict_type` = 'sys_oper_type', `tag_class` = '', `tag_type` = 'error', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079619, `update_by` = '', `update_time` = 0, `remark` = '删除操作' WHERE `dict_code` = 21; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 4, `dict_label` = '授权', `dict_value` = '4', `dict_type` = 'sys_oper_type', `tag_class` = '', `tag_type` = 'success', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079621, `update_by` = '', `update_time` = 0, `remark` = '授权操作' WHERE `dict_code` = 22; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 5, `dict_label` = '导出', `dict_value` = '5', `dict_type` = 'sys_oper_type', `tag_class` = '', `tag_type` = 'warning', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079623, `update_by` = '', `update_time` = 0, `remark` = '导出操作' WHERE `dict_code` = 23; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 6, `dict_label` = '导入', `dict_value` = '6', `dict_type` = 'sys_oper_type', `tag_class` = '', `tag_type` = 'warning', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079625, `update_by` = '', `update_time` = 0, `remark` = '导入操作' WHERE `dict_code` = 24; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 7, `dict_label` = '强退', `dict_value` = '7', `dict_type` = 'sys_oper_type', `tag_class` = '', `tag_type` = 'error', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079627, `update_by` = '', `update_time` = 0, `remark` = '强退操作' WHERE `dict_code` = 25; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 8, `dict_label` = '清空', `dict_value` = '8', `dict_type` = 'sys_oper_type', `tag_class` = '', `tag_type` = 'error', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079630, `update_by` = '', `update_time` = 0, `remark` = '清空操作' WHERE `dict_code` = 26; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 1, `dict_label` = '成功', `dict_value` = '1', `dict_type` = 'sys_common_status', `tag_class` = '', `tag_type` = 'success', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079632, `update_by` = '', `update_time` = 0, `remark` = '正常状态' WHERE `dict_code` = 27; + +UPDATE `omc_db`.`sys_dict_data` SET `dict_sort` = 2, `dict_label` = '失败', `dict_value` = '0', `dict_type` = 'sys_common_status', `tag_class` = '', `tag_type` = 'error', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079633, `update_by` = '', `update_time` = 0, `remark` = '停用状态' WHERE `dict_code` = 28; + +INSERT INTO `omc_db`.`sys_dict_type` (`dict_id`, `dict_name`, `dict_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (101, '操作日志类型', 'operation_log_type', '1', 'AGrand', 1697443982999, '', 0, '操作日志类型'); + +INSERT INTO `omc_db`.`sys_dict_type` (`dict_id`, `dict_name`, `dict_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (102, '告警日志类型', 'alarm_status', '1', 'AGrand', 1697444486172, '', 0, 'alarm_status'); + +INSERT INTO `omc_db`.`sys_dict_type` (`dict_id`, `dict_name`, `dict_type`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (103, '安全日志类型', 'security_log_type', '1', 'AGrand', 1697444692163, '', 0, '安全日志类型'); + +UPDATE `omc_db`.`sys_dict_type` SET `dict_name` = '用户性别', `dict_type` = 'sys_user_sex', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079540, `update_by` = '', `update_time` = 0, `remark` = '用户性别列表' WHERE `dict_id` = 1; + +UPDATE `omc_db`.`sys_dict_type` SET `dict_name` = '菜单状态', `dict_type` = 'sys_show_hide', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079543, `update_by` = '', `update_time` = 0, `remark` = '菜单状态列表' WHERE `dict_id` = 2; + +UPDATE `omc_db`.`sys_dict_type` SET `dict_name` = '系统开关', `dict_type` = 'sys_normal_disable', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079544, `update_by` = '', `update_time` = 0, `remark` = '系统开关列表' WHERE `dict_id` = 3; + +UPDATE `omc_db`.`sys_dict_type` SET `dict_name` = '任务状态', `dict_type` = 'sys_job_status', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079546, `update_by` = '', `update_time` = 0, `remark` = '任务状态列表' WHERE `dict_id` = 4; + +UPDATE `omc_db`.`sys_dict_type` SET `dict_name` = '任务分组', `dict_type` = 'sys_job_group', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079548, `update_by` = '', `update_time` = 0, `remark` = '任务分组列表' WHERE `dict_id` = 5; + +UPDATE `omc_db`.`sys_dict_type` SET `dict_name` = '系统是否', `dict_type` = 'sys_yes_no', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079550, `update_by` = '', `update_time` = 0, `remark` = '系统是否列表' WHERE `dict_id` = 6; + +UPDATE `omc_db`.`sys_dict_type` SET `dict_name` = '通知类型', `dict_type` = 'sys_notice_type', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079552, `update_by` = '', `update_time` = 0, `remark` = '通知类型列表' WHERE `dict_id` = 7; + +UPDATE `omc_db`.`sys_dict_type` SET `dict_name` = '通知状态', `dict_type` = 'sys_notice_status', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079554, `update_by` = '', `update_time` = 0, `remark` = '通知状态列表' WHERE `dict_id` = 8; + +UPDATE `omc_db`.`sys_dict_type` SET `dict_name` = '操作类型', `dict_type` = 'sys_oper_type', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079556, `update_by` = '', `update_time` = 0, `remark` = '操作类型列表' WHERE `dict_id` = 9; + +UPDATE `omc_db`.`sys_dict_type` SET `dict_name` = '系统状态', `dict_type` = 'sys_common_status', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693908079557, `update_by` = '', `update_time` = 0, `remark` = '登录状态列表' WHERE `dict_id` = 10; + +UPDATE `omc_db`.`sys_dict_type` SET `dict_name` = '跟踪类型', `dict_type` = 'trace_type', `status` = '1', `create_by` = 'AGrand', `create_time` = 1693967758884, `update_by` = 'AGrand', `update_time` = 1697442483216, `remark` = '跟踪类型' WHERE `dict_id` = 100; + +INSERT INTO `omc_db`.`sys_job` (`job_id`, `job_name`, `job_group`, `invoke_target`, `target_params`, `cron_expression`, `misfire_policy`, `concurrent`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1, '触发执行', 'SYSTEM', 'simple', '{\"t\":10}', '0/10 * * * * ?', '3', '0', '0', 'AGrand', 1697091151523, '', 0, ''); + +INSERT INTO `omc_db`.`sys_job` (`job_id`, `job_name`, `job_group`, `invoke_target`, `target_params`, `cron_expression`, `misfire_policy`, `concurrent`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2, '缓慢执行', 'SYSTEM', 'foo', '{\"t\":15}', '0/15 * * * * ?', '3', '0', '0', 'AGrand', 1697091151526, '', 0, ''); + +INSERT INTO `omc_db`.`sys_job` (`job_id`, `job_name`, `job_group`, `invoke_target`, `target_params`, `cron_expression`, `misfire_policy`, `concurrent`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (3, '异常执行', 'SYSTEM', 'bar', '{\"t\":20}', '0/20 * * * * ?', '3', '0', '0', 'AGrand', 1697091151529, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1, '系统管理', 0, 1, 'system', '', '1', '1', 'D', '1', '1', '', 'icon-wenjian', 'AGrand', 1694599799316, '', 0, '系统管理目录'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2, '系统监控', 0, 2, 'monitor', '', '1', '1', 'D', '1', '1', '', 'icon-wenjian', 'AGrand', 1694599799345, '', 0, '系统监控目录'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (3, '系统工具', 0, 3, 'tool', '', '1', '1', 'D', '0', '0', '', 'icon-wenjian', 'AGrand', 1694599799372, 'AGrand', 1697428470614, '系统工具目录'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (4, '配置管理', 0, 4, 'configManage', '', '1', '0', 'D', '1', '1', '', 'icon-wenjian', 'AGrand', 1694599799396, 'AGrand', 1694600252468, '配置管理'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (100, '用户管理', 1, 1, 'user', 'system/user/index', '1', '1', 'M', '1', '1', 'system:user:list', '#', 'AGrand', 1697419844499, '', 0, '用户管理菜单'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (101, '角色管理', 1, 2, 'role', 'system/role/index', '1', '1', 'M', '1', '1', 'system:role:list', '#', 'AGrand', 1697419844502, '', 0, '角色管理菜单'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (102, '分配角色', 1, 3, 'role/inline/auth-user/:roleId', 'system/role/auth-user', '1', '1', 'M', '0', '1', 'system:role:auth', '#', 'AGrand', 1697419844503, '', 0, '分配角色内嵌隐藏菜单'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (103, '菜单管理', 1, 4, 'menu', 'system/menu/index', '1', '1', 'M', '1', '1', 'system:menu:list', '#', 'AGrand', 1697419844505, '', 0, '菜单管理菜单'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (104, '部门管理', 1, 5, 'dept', 'system/dept/index', '1', '1', 'M', '1', '1', 'system:dept:list', '#', 'AGrand', 1697419844506, '', 0, '部门管理菜单'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (105, '岗位管理', 1, 6, 'post', 'system/post/index', '1', '1', 'M', '1', '1', 'system:post:list', '#', 'AGrand', 1697419844507, '', 0, '岗位管理菜单'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (106, '字典管理', 1, 7, 'dict', 'system/dict/index', '1', '1', 'M', '1', '1', 'system:dict:list', '#', 'AGrand', 1697419844508, '', 0, '字典管理菜单'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (107, '字典数据', 1, 8, 'dict/inline/data/:dictId', 'system/dict/data', '1', '1', 'M', '0', '1', 'system:dict:data', '#', 'AGrand', 1697419844510, '', 0, '字典数据内嵌隐藏菜单'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (108, '参数设置', 1, 9, 'config', 'system/config/index', '1', '1', 'M', '1', '1', 'system:config:list', '#', 'AGrand', 1697419844516, '', 0, '参数设置菜单'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (109, '通知公告', 1, 10, 'notice', 'system/notice/index', '1', '1', 'M', '0', '0', 'system:notice:list', '#', 'AGrand', 1697419844517, '', 0, '通知公告菜单'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (111, '系统日志', 1, 11, 'log', '', '1', '1', 'D', '1', '1', '', '#', 'AGrand', 1697419844518, '', 0, '日志管理菜单'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (112, '系统信息', 2, 1, 'system-info', 'monitor/system/info', '1', '1', 'M', '1', '1', 'monitor:system:info', '#', 'AGrand', 1697419844520, '', 0, '系统信息菜单'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (113, '缓存信息', 2, 2, 'cache-info', 'monitor/cache/info', '1', '1', 'M', '1', '1', 'monitor:cache:info', '#', 'AGrand', 1697419844521, '', 0, '缓存信息菜单'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (114, '缓存管理', 2, 3, 'cache', 'monitor/cache/index', '1', '1', 'M', '1', '1', 'monitor:cache:list', '#', 'AGrand', 1697419844522, '', 0, '缓存列表菜单'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (115, '在线用户', 2, 4, 'online', 'monitor/online/index', '1', '1', 'M', '1', '1', 'monitor:online:list', '#', 'AGrand', 1697419844524, '', 0, '在线用户菜单'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (116, '调度任务', 2, 5, 'job', 'monitor/job/index', '1', '1', 'M', '1', '1', 'monitor:job:list', '#', 'AGrand', 1697419844525, '', 0, '调度任务菜单'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (117, '调度日志', 2, 6, 'job/inline/log/:jobId', 'monitor/job/log', '1', '1', 'M', '0', '1', 'monitor:job:log', '#', 'AGrand', 1697419844527, '', 0, '调度日志内嵌隐藏菜单'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (118, '帮助文档', 3, 1, 'help', 'tool/help/index', '1', '1', 'M', '0', '0', 'monitor:help:list', '#', 'AGrand', 1697419844528, '', 0, '系统接口菜单'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (500, '操作日志', 111, 1, 'operate', 'system/log/operate/index', '1', '1', 'M', '1', '1', 'system:log:operate:list', '#', 'AGrand', 1697419793720, '', 0, '操作日志菜单'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (501, '登录日志', 111, 2, 'login', 'system/log/login/index', '1', '1', 'M', '1', '1', 'system:log:login:list', '#', 'AGrand', 1697419793722, '', 0, '登录日志菜单'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1000, '用户查询', 100, 1, '', '', '1', '1', 'B', '1', '1', 'system:user:query', '#', 'AGrand', 1694599800500, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1001, '用户新增', 100, 2, '', '', '1', '1', 'B', '1', '1', 'system:user:add', '#', 'AGrand', 1694599800538, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1002, '用户修改', 100, 3, '', '', '1', '1', 'B', '1', '1', 'system:user:edit', '#', 'AGrand', 1694599800587, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1003, '用户删除', 100, 4, '', '', '1', '1', 'B', '1', '1', 'system:user:remove', '#', 'AGrand', 1694599800616, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1004, '用户导出', 100, 5, '', '', '1', '1', 'B', '1', '1', 'system:user:export', '#', 'AGrand', 1694599800642, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1005, '用户导入', 100, 6, '', '', '1', '1', 'B', '1', '1', 'system:user:import', '#', 'AGrand', 1694599800677, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1006, '重置密码', 100, 7, '', '', '1', '1', 'B', '1', '1', 'system:user:resetPwd', '#', 'AGrand', 1694599800766, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1007, '角色查询', 101, 1, '', '', '1', '1', 'B', '1', '1', 'system:role:query', '#', 'AGrand', 1694599800806, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1008, '角色新增', 101, 2, '', '', '1', '1', 'B', '1', '1', 'system:role:add', '#', 'AGrand', 1694599800845, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1009, '角色修改', 101, 3, '', '', '1', '1', 'B', '1', '1', 'system:role:edit', '#', 'AGrand', 1694599800880, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1010, '角色删除', 101, 4, '', '', '1', '1', 'B', '1', '1', 'system:role:remove', '#', 'AGrand', 1694599800976, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1011, '角色导出', 101, 5, '', '', '1', '1', 'B', '1', '1', 'system:role:export', '#', 'AGrand', 1694599801089, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1012, '菜单查询', 103, 1, '', '', '1', '1', 'B', '1', '1', 'system:menu:query', '#', 'AGrand', 1694599801135, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1013, '菜单新增', 103, 2, '', '', '1', '1', 'B', '1', '1', 'system:menu:add', '#', 'AGrand', 1694599801165, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1014, '菜单修改', 103, 3, '', '', '1', '1', 'B', '1', '1', 'system:menu:edit', '#', 'AGrand', 1694599801192, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1015, '菜单删除', 103, 4, '', '', '1', '1', 'B', '1', '1', 'system:menu:remove', '#', 'AGrand', 1694599801246, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1016, '部门查询', 104, 1, '', '', '1', '1', 'B', '1', '1', 'system:dept:query', '#', 'AGrand', 1694599801305, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1017, '部门新增', 104, 2, '', '', '1', '1', 'B', '1', '1', 'system:dept:add', '#', 'AGrand', 1694599801342, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1018, '部门修改', 104, 3, '', '', '1', '1', 'B', '1', '1', 'system:dept:edit', '#', 'AGrand', 1694599801432, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1019, '部门删除', 104, 4, '', '', '1', '1', 'B', '1', '1', 'system:dept:remove', '#', 'AGrand', 1694599801463, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1020, '岗位查询', 105, 1, '', '', '1', '1', 'B', '1', '1', 'system:post:query', '#', 'AGrand', 1694599801496, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1021, '岗位新增', 105, 2, '', '', '1', '1', 'B', '1', '1', 'system:post:add', '#', 'AGrand', 1694599801532, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1022, '岗位修改', 105, 3, '', '', '1', '1', 'B', '1', '1', 'system:post:edit', '#', 'AGrand', 1694599801581, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1023, '岗位删除', 105, 4, '', '', '1', '1', 'B', '1', '1', 'system:post:remove', '#', 'AGrand', 1694599801619, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1024, '岗位导出', 105, 5, '', '', '1', '1', 'B', '1', '1', 'system:post:export', '#', 'AGrand', 1694599801648, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1025, '字典查询', 106, 1, '#', '', '1', '1', 'B', '1', '1', 'system:dict:query', '#', 'AGrand', 1694599801673, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1026, '字典新增', 106, 2, '#', '', '1', '1', 'B', '1', '1', 'system:dict:add', '#', 'AGrand', 1694599801711, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1027, '字典修改', 106, 3, '#', '', '1', '1', 'B', '1', '1', 'system:dict:edit', '#', 'AGrand', 1694599801741, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1028, '字典删除', 106, 4, '#', '', '1', '1', 'B', '1', '1', 'system:dict:remove', '#', 'AGrand', 1694599801772, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1029, '字典导出', 106, 5, '#', '', '1', '1', 'B', '1', '1', 'system:dict:export', '#', 'AGrand', 1694599801801, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1030, '参数查询', 108, 1, '#', '', '1', '1', 'B', '1', '1', 'system:config:query', '#', 'AGrand', 1694599801828, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1031, '参数新增', 108, 2, '#', '', '1', '1', 'B', '1', '1', 'system:config:add', '#', 'AGrand', 1694599801855, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1032, '参数修改', 108, 3, '#', '', '1', '1', 'B', '1', '1', 'system:config:edit', '#', 'AGrand', 1694599801888, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1033, '参数删除', 108, 4, '#', '', '1', '1', 'B', '1', '1', 'system:config:remove', '#', 'AGrand', 1694599801920, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1034, '参数导出', 108, 5, '#', '', '1', '1', 'B', '1', '1', 'system:config:export', '#', 'AGrand', 1694599801963, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1035, '公告查询', 109, 1, '#', '', '1', '1', 'B', '1', '1', 'system:notice:query', '#', 'AGrand', 1694599801991, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1036, '公告新增', 109, 2, '#', '', '1', '1', 'B', '1', '1', 'system:notice:add', '#', 'AGrand', 1694599802024, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1037, '公告修改', 109, 3, '#', '', '1', '1', 'B', '1', '1', 'system:notice:edit', '#', 'AGrand', 1694599802055, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1038, '公告删除', 109, 4, '#', '', '1', '1', 'B', '1', '1', 'system:notice:remove', '#', 'AGrand', 1694599802079, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1039, '操作查询', 500, 1, '#', '', '1', '1', 'B', '1', '1', 'system:log:operate:query', '#', 'AGrand', 1697419763809, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1040, '操作删除', 500, 2, '#', '', '1', '1', 'B', '1', '1', 'system:log:operate:remove', '#', 'AGrand', 1697419763812, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1041, '日志导出', 500, 3, '#', '', '1', '1', 'B', '1', '1', 'system:log:operate:export', '#', 'AGrand', 1697419763813, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1042, '登录查询', 501, 1, '#', '', '1', '1', 'B', '1', '1', 'system:log:login:query', '#', 'AGrand', 1697419763815, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1043, '登录删除', 501, 2, '#', '', '1', '1', 'B', '1', '1', 'system:log:login:remove', '#', 'AGrand', 1697419763816, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1044, '日志导出', 501, 3, '#', '', '1', '1', 'B', '1', '1', 'system:log:login:export', '#', 'AGrand', 1697419763817, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1045, '账户解锁', 501, 4, '#', '', '1', '1', 'B', '1', '1', 'system:log:login:unlock', '#', 'AGrand', 1697419763818, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1046, '缓存查询', 114, 1, '#', '', '1', '1', 'B', '1', '1', 'monitor:cache:query', '#', 'AGrand', 1694599802363, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1047, '缓存删除', 114, 2, '#', '', '1', '1', 'B', '1', '1', 'monitor:cache:remove', '#', 'AGrand', 1694599802393, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1048, '在线查询', 115, 1, '#', '', '1', '1', 'B', '1', '1', 'monitor:online:query', '#', 'AGrand', 1694599802422, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1049, '批量强退', 115, 2, '#', '', '1', '1', 'B', '1', '1', 'monitor:online:batchLogout', '#', 'AGrand', 1694599802453, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1050, '单条强退', 115, 3, '#', '', '1', '1', 'B', '1', '1', 'monitor:online:forceLogout', '#', 'AGrand', 1694599802483, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1051, '任务查询', 116, 1, '#', '', '1', '1', 'B', '1', '1', 'monitor:job:query', '#', 'AGrand', 1694599802551, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1052, '任务新增', 116, 2, '#', '', '1', '1', 'B', '1', '1', 'monitor:job:add', '#', 'AGrand', 1694599802581, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1053, '任务修改', 116, 3, '#', '', '1', '1', 'B', '1', '1', 'monitor:job:edit', '#', 'AGrand', 1694599802605, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1054, '任务删除', 116, 4, '#', '', '1', '1', 'B', '1', '1', 'monitor:job:remove', '#', 'AGrand', 1694599802641, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1055, '状态修改', 116, 5, '#', '', '1', '1', 'B', '1', '1', 'monitor:job:changeStatus', '#', 'AGrand', 1694599802675, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1056, '任务导出', 116, 6, '#', '', '1', '1', 'B', '1', '1', 'monitor:job:export', '#', 'AGrand', 1694599802706, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2076, '参数配置', 4, 2, 'configParam', 'configManage/configParam/index', '1', '0', 'M', '1', '1', 'configManage:configParam:index', 'icon-piliang', 'AGrand', 1694656727697, 'AGrand', 1694664268131, '参数配置'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2077, '信令抓包', 10, 3, '', 'page/trace/pcap.html', '1', '0', 'M', '1', '1', 'trace:pcap', '#', 'AGrand', 1694834069721, '', 0, 'tcpdump抓包pcap文件'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2078, '备份管理', 4, 3, 'backupManage', 'configManage/backupManage/index', '1', '0', 'M', '1', '1', 'configManage:backupManage:index', 'icon-soutubiao', 'AGrand', 1695017673281, '', 0, '备份管理'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2079, '软件管理', 4, 4, 'softwareManage', 'configManage/softwareManage/index', '1', '0', 'M', '1', '1', 'configManage:softwareManage:index', 'icon-huidingbu', 'AGrand', 1695024225185, '', 0, '软件管理'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2080, 'IMS在线用户', 5, 4, 'ims', 'neUser/ims/index', '1', '0', 'M', '1', '1', 'neUser:ims:index', 'icon-xiangmuchengyuan', 'AGrand', 1695090024184, '', 0, 'IMS在线用户'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2081, 'UE在线信息', 5, 6, 'ue', 'neUser/ue/index', '1', '0', 'M', '1', '1', 'neUser:ue:index', 'icon-xiangmuchengyuan', 'AGrand', 1695090083747, '', 0, 'UE在线信息'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2082, '5G基站信息', 5, 7, 'base5G', 'neUser/base5G/index', '1', '0', 'M', '1', '1', 'neUser:base5G:index', 'icon-paixu', 'AGrand', 1695090192856, 'AGrand', 1695349622025, '5G基站信息'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2083, '跟踪管理', 0, 6, 'traceManage', '', '1', '0', 'D', '1', '1', '', 'icon-wenjian', 'AGrand', 1695379608530, 'AGrand', 1695696620937, '跟踪管理'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2084, '跟踪任务', 2083, 1, 'task', 'traceManage/task/index', '1', '0', 'M', '1', '1', 'traceManage:task:index', 'icon-chexiao', 'AGrand', 1695379860503, 'AGrand', 1695696631196, '跟踪任务'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2085, '信令分析', 2083, 1, 'analysis', 'traceManage/analysis/index', '1', '0', 'M', '1', '1', 'traceManage:analysis:index', 'icon-gongnengjieshao', 'AGrand', 1695379915764, 'AGrand', 1695696640147, '信令分析'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2086, '信令抓包', 2083, 3, 'pcap', 'traceManage/pcap/index', '1', '0', 'M', '1', '1', 'traceManage:pcap:index', 'icon-soutubiao', 'AGrand', 1695380002462, 'AGrand', 1695696648928, '信令抓包'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2087, '故障管理', 0, 7, 'faultManage', '', '1', '0', 'D', '1', '1', '', 'icon-wenjian', 'AGrand', 1695638038379, '', 0, '故障管理'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2088, '活动告警', 2087, 1, 'active-alarm', 'faultManage/active-alarm/index', '1', '0', 'M', '1', '1', 'faultManage:active-alarm:index', 'icon-wenjian', 'AGrand', 1695639086834, 'AGrand', 1695639108038, '活动告警'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2089, '日志管理', 0, 8, 'logManage', '', '1', '0', 'D', '1', '1', '', 'icon-wenjian', 'AGrand', 1695697146107, '', 0, '日志管理'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2090, '操作日志', 2089, 1, 'operation', 'logManage/operation/index', '1', '0', 'M', '1', '1', 'logManage:operation:index', 'icon-fuzhidaima', 'AGrand', 1695697237355, '', 0, '操作日志'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2091, '操作MML日志', 2089, 2, 'mml', 'logManage/mml/index', '1', '0', 'M', '1', '1', 'logManage:mml:index', 'icon-wocanyu', 'AGrand', 1695697317483, '', 0, '操作MML日志'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2092, '告警日志', 2089, 3, 'alarm', 'logManage/alarm/index', '1', '0', 'M', '1', '1', 'logManage:alarm:index', 'icon-fuzhidaima', 'AGrand', 1695697384204, '', 0, '告警日志'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2093, '安全日志', 2089, 6, 'security', 'logManage/security/index', '1', '0', 'M', '1', '1', 'logManage/security/index', 'icon-gongnengjieshao', 'AGrand', 1695697433544, '', 0, '安全日志'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2094, '告警前转日志', 2089, 7, 'forwarding', 'logManage/forwarding/index', '1', '0', 'M', '1', '1', 'logManage:forwarding:index', 'icon-huizhiguize', 'AGrand', 1695697548801, '', 0, '告警前转日志'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2095, '日志设置', 2089, 15, 'logSet', 'logManage/logSet/index', '1', '0', 'M', '1', '1', 'logManage:logSet:index', 'icon-you', 'AGrand', 1695697636698, '', 0, '日志设置'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2096, '用户会话', 2, 10, 'session', 'monitor/session/index', '1', '0', 'M', '1', '1', 'monitor:session:index', 'icon-gerenzhanghu', 'AGrand', 1695698108049, '', 0, '在线用户-旧用户在线'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2097, '历史告警', 2087, 0, 'history-alarm', 'faultManage/history-alarm/index', '1', '0', 'M', '1', '1', 'faultManage/history-alarm/index', 'icon-huizhiguize', 'AGrand', 1696665696950, 'AGrand', 1696665716447, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2098, '故障通用设置', 2087, 0, 'fault-setting', 'faultManage/fault-setting/index', '1', '0', 'M', '1', '1', 'faultManage/fault-setting/index', 'icon-gonggaodayi', 'AGrand', 1696668601941, '', 0, ''); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2099, '性能管理', 0, 9, 'perfManage', '', '1', '0', 'D', '1', '1', '', 'icon-soutubiao', 'AGrand', 1696923032637, 'AGrand', 1696923040267, '性能管理'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2100, '任务管理', 2099, 1, 'taskManage', 'perfManage/taskManage/index', '1', '0', 'M', '1', '1', 'perfManage:taskManage:index', 'icon-wofaqi', 'AGrand', 1696923215654, '', 0, '任务管理'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2101, '性能数据', 2099, 2, 'perfData', 'perfManage/perfData/index', '1', '0', 'M', '1', '1', 'perfManage:perfData:index', 'icon-soutubiao', 'AGrand', 1696923311093, '', 0, '性能数据'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2102, '性能报表', 2099, 3, 'perfReport', 'perfManage/perfReport/index', '1', '0', 'M', '1', '1', 'perfManage:perfReport:index', 'icon-gonggaodayi', 'AGrand', 1696923390542, '', 0, '性能报表'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2103, '性能门限', 2099, 4, 'perfThreshold', 'perfManage/perfThreshold/index', '1', '0', 'M', '1', '1', 'perfManage:perfThreshold:index', 'icon-zhuanrang', 'AGrand', 1696923461901, '', 0, '性能门限'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2104, '黄金指标', 2099, 5, 'goldTarget', 'perfManage/goldTarget/index', '1', '0', 'M', '1', '1', 'perfManage:goldTarget:index', 'icon-soutubiao', 'AGrand', 1696923551120, '', 0, '黄金指标'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2105, '自定义指标', 2099, 6, 'customTarget', 'perfManage/customTarget/index', '1', '0', 'M', '0', '0', 'perfManage:customTarget:index', 'icon-fanhui1', 'AGrand', 1696923639489, 'AGrand', 1697077437451, '自定义指标'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2106, '性能通用设置', 2099, 7, 'perfSet', 'perfManage/perfSet/index', '1', '0', 'M', '1', '1', 'perfManage:perfSet:index', 'icon-gonggao', 'AGrand', 1696923787076, '', 0, '性能通用设置'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2107, 'MML管理', 0, 10, 'mmlManage', '', '1', '0', 'D', '1', '1', '', 'icon-wenjian', 'AGrand', 1696924634152, 'AGrand', 1697428173406, 'MML管理'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2108, '网元操作', 2107, 1, 'neOperate', 'mmlManage/neOperate/index', '1', '0', 'M', '1', '1', 'mmlManage:neOperate:index', 'icon-huizhiguize', 'AGrand', 1696924784723, '', 0, '网元操作'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2109, 'UDM操作', 2107, 2, 'udmOperate', 'mmlManage/udmOperate/index', '1', '0', 'M', '1', '1', 'mmlManage:udmOperate:index', 'icon-gonggaodayi', 'AGrand', 1696924889906, 'AGrand', 1697005003783, '用户数据指定网元UDM'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2110, 'MML设置', 2107, 4, 'mmlSet', 'mmlManage/mmlSet/index', '1', '0', 'M', '1', '1', 'mmlManage:mmlSet:index', 'icon-wofaqi', 'AGrand', 1696924994794, '', 0, 'MML设置'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2111, 'OMC操作', 2107, 3, 'omcOperate', 'mmlManage/omcOperate/index', '1', '0', 'M', '1', '1', 'mmlManage:omcOperate:index', 'icon-huizhiguize', 'AGrand', 1696925543196, '', 0, 'OMC操作'); + +INSERT INTO `omc_db`.`sys_menu` (`menu_id`, `menu_name`, `parent_id`, `menu_sort`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2112, '许可证管理', 4, 5, 'license', 'configManage/license/index', '1', '0', 'M', '1', '1', 'configManage/license/index', 'icon-shang', 'AGrand', 1696989886481, 'AGrand', 1697013380449, 'License管理'); + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '用户信息', `parent_id` = 0, `menu_sort` = 5, `path` = 'neUser', `component` = '', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'D', `visible` = '1', `status` = '1', `perms` = '', `icon` = 'icon-wenjian', `create_by` = 'AGrand', `create_time` = 1693031847898, `update_by` = 'AGrand', `update_time` = 1695089607243, `remark` = '网元用户信息' WHERE `menu_id` = 5; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '故障管理', `parent_id` = 0, `menu_sort` = 50, `path` = 'page', `component` = '', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'D', `visible` = '0', `status` = '1', `perms` = '', `icon` = 'fa fa-home', `create_by` = 'AGrand', `create_time` = 1693031847898, `update_by` = 'AGrand', `update_time` = 1694679965315, `remark` = '故障管理' WHERE `menu_id` = 6; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '配置管理', `parent_id` = 0, `menu_sort` = 550, `path` = 'page', `component` = '', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'D', `visible` = '0', `status` = '1', `perms` = '', `icon` = 'fa fa-home', `create_by` = 'AGrand', `create_time` = 1693031847898, `update_by` = '', `update_time` = 0, `remark` = '配置管理' WHERE `menu_id` = 7; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '性能管理', `parent_id` = 0, `menu_sort` = 551, `path` = 'page', `component` = '', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'D', `visible` = '0', `status` = '1', `perms` = '', `icon` = 'fa fa-home', `create_by` = 'AGrand', `create_time` = 1693031847898, `update_by` = '', `update_time` = 0, `remark` = '性能管理' WHERE `menu_id` = 8; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '操作维护', `parent_id` = 0, `menu_sort` = 552, `path` = 'page', `component` = '', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'D', `visible` = '0', `status` = '1', `perms` = '', `icon` = 'fa fa-home', `create_by` = 'AGrand', `create_time` = 1693031847898, `update_by` = '', `update_time` = 0, `remark` = '操作维护' WHERE `menu_id` = 9; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '跟踪管理', `parent_id` = 0, `menu_sort` = 553, `path` = 'page', `component` = '', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'D', `visible` = '0', `status` = '1', `perms` = '', `icon` = 'fa fa-home', `create_by` = 'AGrand', `create_time` = 1693031847898, `update_by` = 'AGrand', `update_time` = 1694079481395, `remark` = '跟踪管理' WHERE `menu_id` = 10; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '日志管理', `parent_id` = 0, `menu_sort` = 554, `path` = 'page', `component` = '', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'D', `visible` = '0', `status` = '1', `perms` = '', `icon` = 'fa fa-home', `create_by` = 'AGrand', `create_time` = 1693031847898, `update_by` = '', `update_time` = 0, `remark` = '日志管理' WHERE `menu_id` = 11; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '安全管理', `parent_id` = 0, `menu_sort` = 555, `path` = 'page', `component` = '', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'D', `visible` = '0', `status` = '1', `perms` = '', `icon` = 'fa fa-home', `create_by` = 'AGrand', `create_time` = 1693031847898, `update_by` = '', `update_time` = 0, `remark` = '安全管理' WHERE `menu_id` = 12; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '北向管理', `parent_id` = 0, `menu_sort` = 556, `path` = 'page', `component` = '', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'D', `visible` = '0', `status` = '0', `perms` = '', `icon` = 'fa fa-home', `create_by` = 'AGrand', `create_time` = 1693031847898, `update_by` = 'AGrand', `update_time` = 1693538966603, `remark` = '北向管理' WHERE `menu_id` = 13; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '系统管理', `parent_id` = 0, `menu_sort` = 557, `path` = 'page', `component` = '', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'D', `visible` = '0', `status` = '1', `perms` = '', `icon` = 'fa fa-home', `create_by` = 'AGrand', `create_time` = 1693031847898, `update_by` = '', `update_time` = 0, `remark` = '系统管理' WHERE `menu_id` = 14; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '拓扑管理', `parent_id` = 0, `menu_sort` = 558, `path` = 'page', `component` = '', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'D', `visible` = '0', `status` = '0', `perms` = '', `icon` = 'fa fa-home', `create_by` = 'AGrand', `create_time` = 1693031847898, `update_by` = '', `update_time` = 0, `remark` = '拓扑管理' WHERE `menu_id` = 15; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = 'UDM鉴权用户', `parent_id` = 5, `menu_sort` = 1, `path` = 'auth', `component` = 'neUser/auth/index', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = 'neUser:auth:index', `icon` = 'icon-xiangmuchengyuan', `create_by` = 'AGrand', `create_time` = 1693452185987, `update_by` = 'AGrand', `update_time` = 1695089843923, `remark` = 'UDM鉴权用户' WHERE `menu_id` = 2009; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = 'UDM签约用户', `parent_id` = 5, `menu_sort` = 2, `path` = 'sub', `component` = 'neUser/sub/index', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = 'neUser:sub:index', `icon` = 'icon-xiangmuchengyuan', `create_by` = 'AGrand', `create_time` = 1693452241733, `update_by` = 'AGrand', `update_time` = 1695365399746, `remark` = 'UDM签约用户' WHERE `menu_id` = 2010; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '活动告警', `parent_id` = 6, `menu_sort` = 1, `path` = '', `component` = 'page/alarm/alarmListDown.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '活动告警', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693452882261, `update_by` = 'AGrand', `update_time` = 1694679879968, `remark` = '活动告警' WHERE `menu_id` = 2011; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '历史告警', `parent_id` = 6, `menu_sort` = 2, `path` = '', `component` = 'page/alarm/AlarmListHistory.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '历史告警', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693452917746, `update_by` = '', `update_time` = 0, `remark` = '历史告警' WHERE `menu_id` = 2012; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '故障通用设置', `parent_id` = 6, `menu_sort` = 3, `path` = '', `component` = 'page/alarm/alarmInfoConfig.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '故障通用设置', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693452963347, `update_by` = '', `update_time` = 0, `remark` = '故障通用设置' WHERE `menu_id` = 2013; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '定时同步设置', `parent_id` = 6, `menu_sort` = 4, `path` = '', `component` = 'page/alarm/synchronous.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '0', `perms` = '定时同步设置', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693452983027, `update_by` = 'AGrand', `update_time` = 1693544689303, `remark` = '定时同步设置' WHERE `menu_id` = 2014; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '健康状态检查', `parent_id` = 6, `menu_sort` = 5, `path` = '', `component` = 'page/alarm/healthCheck.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '0', `perms` = '健康状态检查', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453161355, `update_by` = 'AGrand', `update_time` = 1693544697864, `remark` = '健康状态检查' WHERE `menu_id` = 2015; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '告警前转', `parent_id` = 6, `menu_sort` = 6, `path` = '', `component` = 'page/alarm/alarmForwarding.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '告警前转', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453182346, `update_by` = '', `update_time` = 0, `remark` = '告警前转' WHERE `menu_id` = 2016; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '网元管理', `parent_id` = 7, `menu_sort` = 1, `path` = '', `component` = 'page/nfManage/list.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '网元管理', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453229420, `update_by` = '', `update_time` = 0, `remark` = '网元管理' WHERE `menu_id` = 2017; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '参数配置', `parent_id` = 7, `menu_sort` = 2, `path` = '', `component` = 'page/configParam/list.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '参数配置', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453250426, `update_by` = '', `update_time` = 0, `remark` = '参数配置' WHERE `menu_id` = 2018; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '软件管理', `parent_id` = 7, `menu_sort` = 3, `path` = '', `component` = 'page/softwareManage/softwareManage.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '软件管理', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453272021, `update_by` = '', `update_time` = 0, `remark` = '软件管理' WHERE `menu_id` = 2019; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '备份管理', `parent_id` = 7, `menu_sort` = 5, `path` = '', `component` = 'page/softwareManage/backupManage.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '备份管理', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453295606, `update_by` = '', `update_time` = 0, `remark` = '备份管理' WHERE `menu_id` = 2020; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '配置参数设置', `parent_id` = 7, `menu_sort` = 6, `path` = '', `component` = 'page/configParam/configParamSet.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '配置参数设置', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453313510, `update_by` = '', `update_time` = 0, `remark` = '配置参数设置' WHERE `menu_id` = 2021; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '任务管理', `parent_id` = 8, `menu_sort` = 1, `path` = '', `component` = 'page/task/list.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '任务管理', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453371550, `update_by` = '', `update_time` = 0, `remark` = '任务管理' WHERE `menu_id` = 2022; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '性能数据', `parent_id` = 8, `menu_sort` = 2, `path` = '', `component` = 'page/repair/list.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '性能数据', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453391983, `update_by` = '', `update_time` = 0, `remark` = '性能数据' WHERE `menu_id` = 2023; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '性能报表', `parent_id` = 8, `menu_sort` = 3, `path` = '', `component` = 'page/task/perfReport.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '性能报表', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453414547, `update_by` = '', `update_time` = 0, `remark` = '性能报表' WHERE `menu_id` = 2024; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '性能门限', `parent_id` = 8, `menu_sort` = 4, `path` = '', `component` = 'page/task/threshold.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '性能门限', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453436131, `update_by` = '', `update_time` = 0, `remark` = '性能门限' WHERE `menu_id` = 2025; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '黄金指标', `parent_id` = 8, `menu_sort` = 5, `path` = '', `component` = 'page/gold/list.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '黄金指标', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453458286, `update_by` = '', `update_time` = 0, `remark` = '黄金指标' WHERE `menu_id` = 2026; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '性能通用设置', `parent_id` = 8, `menu_sort` = 7, `path` = '', `component` = 'page/task/perfReportSet.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '性能通用设置', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453477609, `update_by` = '', `update_time` = 0, `remark` = '性能通用设置' WHERE `menu_id` = 2027; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '自定义指标', `parent_id` = 8, `menu_sort` = 8, `path` = '', `component` = 'page/indicators/list.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '自定义指标', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453497532, `update_by` = '', `update_time` = 0, `remark` = '自定义指标' WHERE `menu_id` = 2028; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '对象模板', `parent_id` = 8, `menu_sort` = 8, `path` = '', `component` = 'page/objectTemplate/list.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '0', `perms` = '对象模板', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453519007, `update_by` = 'AGrand', `update_time` = 1693544534649, `remark` = '对象模板' WHERE `menu_id` = 2029; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '自定义测量数据', `parent_id` = 8, `menu_sort` = 9, `path` = '', `component` = 'page/indicators/measuringData.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '0', `perms` = '自定义测量数据', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453547977, `update_by` = 'AGrand', `update_time` = 1693544544479, `remark` = '自定义测量数据' WHERE `menu_id` = 2030; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '拓扑视图', `parent_id` = 15, `menu_sort` = 1, `path` = '', `component` = 'page/topology/topologyList.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '0', `perms` = '拓扑视图', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453593545, `update_by` = '', `update_time` = 0, `remark` = '拓扑视图' WHERE `menu_id` = 2031; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '系统维护', `parent_id` = 14, `menu_sort` = 1, `path` = '', `component` = 'page/systemManage/systemOperation.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '系统维护', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453620554, `update_by` = '', `update_time` = 0, `remark` = '系统维护' WHERE `menu_id` = 2032; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '稳定性事件列表', `parent_id` = 14, `menu_sort` = 2, `path` = '', `component` = 'page/systemManage/stabilityEvents.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '0', `perms` = '稳定性事件列表', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453643025, `update_by` = 'AGrand', `update_time` = 1693544384309, `remark` = '稳定性事件列表' WHERE `menu_id` = 2033; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '稳定性统计报告', `parent_id` = 14, `menu_sort` = 3, `path` = '', `component` = 'page/systemManage/stabilityReports.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '0', `perms` = '稳定性统计报告', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453662988, `update_by` = 'AGrand', `update_time` = 1693544429662, `remark` = '稳定性统计报告' WHERE `menu_id` = 2034; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '系统备份', `parent_id` = 14, `menu_sort` = 4, `path` = '', `component` = 'page/systemManage/systemBackup.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '0', `perms` = '系统备份', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453682872, `update_by` = 'AGrand', `update_time` = 1693544442726, `remark` = '系统备份' WHERE `menu_id` = 2035; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '系统可扩展', `parent_id` = 14, `menu_sort` = 5, `path` = '', `component` = 'page/systemManage/systemExtended.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '0', `perms` = '系统可扩展', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453705157, `update_by` = 'AGrand', `update_time` = 1693544457342, `remark` = '\"系统可扩展' WHERE `menu_id` = 2036; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '证书管理', `parent_id` = 14, `menu_sort` = 6, `path` = '', `component` = 'page/systemManage/certificateManage.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '0', `perms` = '证书管理', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453726181, `update_by` = 'AGrand', `update_time` = 1693544468109, `remark` = '证书管理' WHERE `menu_id` = 2037; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '北向操作日志', `parent_id` = 13, `menu_sort` = 0, `path` = '', `component` = 'page/log/nbiOperLogList.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '0', `status` = '0', `perms` = '北向操作日志', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453756619, `update_by` = '', `update_time` = 0, `remark` = '北向操作日志' WHERE `menu_id` = 2038; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '北向告警日志', `parent_id` = 13, `menu_sort` = 2, `path` = '', `component` = 'page/log/nbiAlarmLog.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '0', `status` = '0', `perms` = '北向告警日志', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453776700, `update_by` = 'AGrand', `update_time` = 1693539031280, `remark` = '北向告警日志' WHERE `menu_id` = 2039; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '北向通用设置', `parent_id` = 13, `menu_sort` = 3, `path` = '', `component` = 'page/log/nbiSet.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '0', `status` = '0', `perms` = '北向通用设置', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453799234, `update_by` = '', `update_time` = 0, `remark` = '北向通用设置' WHERE `menu_id` = 2040; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '用户管理', `parent_id` = 12, `menu_sort` = 1, `path` = '', `component` = 'page/user/list.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '用户管理', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453827502, `update_by` = '', `update_time` = 0, `remark` = '用户管理' WHERE `menu_id` = 2041; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '在线状态', `parent_id` = 12, `menu_sort` = 2, `path` = '', `component` = 'page/user/online.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '在线状态', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453847338, `update_by` = '', `update_time` = 0, `remark` = '在线状态' WHERE `menu_id` = 2042; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '用户组管理', `parent_id` = 12, `menu_sort` = 2, `path` = '', `component` = 'page/group/list.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '0', `perms` = '用户组管理', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453875705, `update_by` = 'AGrand', `update_time` = 1693544575599, `remark` = '用户组管理' WHERE `menu_id` = 2043; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '安全策略', `parent_id` = 12, `menu_sort` = 3, `path` = '', `component` = 'page/user/securityPolicy.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '0', `perms` = '安全策略', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453892806, `update_by` = 'AGrand', `update_time` = 1693544590261, `remark` = '安全策略' WHERE `menu_id` = 2044; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '操作日志', `parent_id` = 11, `menu_sort` = 1, `path` = '', `component` = 'page/log/operLogList.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '操作日志', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453925729, `update_by` = '', `update_time` = 0, `remark` = '操作日志' WHERE `menu_id` = 2045; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = 'mml操作日志', `parent_id` = 11, `menu_sort` = 2, `path` = '', `component` = 'page/log/mmlOperLogList.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = 'mml操作日志', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453944710, `update_by` = '', `update_time` = 0, `remark` = 'mml操作日志' WHERE `menu_id` = 2046; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '告警日志', `parent_id` = 11, `menu_sort` = 3, `path` = '', `component` = 'page/log/alarmLogList.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '告警日志', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453970200, `update_by` = '', `update_time` = 0, `remark` = '告警日志' WHERE `menu_id` = 2047; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '安全日志', `parent_id` = 11, `menu_sort` = 4, `path` = '', `component` = 'page/log/securityLogList.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '安全日志', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693453993525, `update_by` = '', `update_time` = 0, `remark` = '安全日志' WHERE `menu_id` = 2048; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '告警前转日志', `parent_id` = 11, `menu_sort` = 7, `path` = '', `component` = 'page/log/forwardingLog.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '告警前转日志', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693454015722, `update_by` = '', `update_time` = 0, `remark` = '告警前转日志' WHERE `menu_id` = 2049; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '日志通用管理', `parent_id` = 11, `menu_sort` = 8, `path` = '', `component` = 'page/log/logSet.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '日志通用管理', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693454038045, `update_by` = '', `update_time` = 0, `remark` = '日志通用管理' WHERE `menu_id` = 2050; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '系统日志', `parent_id` = 11, `menu_sort` = 9, `path` = '', `component` = 'page/log/systemLog.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '系统日志', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693454059224, `update_by` = '', `update_time` = 0, `remark` = '系统日志' WHERE `menu_id` = 2051; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '跟踪任务', `parent_id` = 10, `menu_sort` = 1, `path` = '', `component` = 'page/trace/taskList.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '0', `status` = '1', `perms` = '跟踪任务', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693454085034, `update_by` = 'AGrand', `update_time` = 1694079489394, `remark` = '跟踪任务' WHERE `menu_id` = 2052; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '信令分析', `parent_id` = 10, `menu_sort` = 2, `path` = '', `component` = 'page/trace/traceShow.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '0', `status` = '1', `perms` = '信令分析', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693454104730, `update_by` = 'AGrand', `update_time` = 1694080368852, `remark` = '信令分析' WHERE `menu_id` = 2053; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '核心网池', `parent_id` = 9, `menu_sort` = 0, `path` = '', `component` = 'page/mml/poolList.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '0', `perms` = '核心网池', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693454139150, `update_by` = 'AGrand', `update_time` = 1693544615419, `remark` = '核心网池' WHERE `menu_id` = 2054; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '操作维护MML', `parent_id` = 9, `menu_sort` = 1, `path` = '', `component` = 'page/mml/omcList.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '操作维护MML', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693454188732, `update_by` = '', `update_time` = 0, `remark` = '操作维护MML' WHERE `menu_id` = 2055; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '网元操作MML', `parent_id` = 9, `menu_sort` = 3, `path` = '', `component` = 'page/mml/list.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '网元操作MML', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693454410796, `update_by` = '', `update_time` = 0, `remark` = '网元操作MML' WHERE `menu_id` = 2056; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '用户数据MML', `parent_id` = 9, `menu_sort` = 4, `path` = '', `component` = 'page/mml/udmList.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '用户数据MML', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693454436837, `update_by` = '', `update_time` = 0, `remark` = '用户数据MML' WHERE `menu_id` = 2057; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '操作维护设置', `parent_id` = 9, `menu_sort` = 5, `path` = '', `component` = 'page/mml/mmlSet.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '操作维护设置', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693454454951, `update_by` = '', `update_time` = 0, `remark` = '操作维护设置' WHERE `menu_id` = 2058; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '角色管理', `parent_id` = 12, `menu_sort` = 6, `path` = '', `component` = 'page/role/list.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '角色管理', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693454568739, `update_by` = '', `update_time` = 0, `remark` = '角色管理' WHERE `menu_id` = 2059; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '菜单管理', `parent_id` = 12, `menu_sort` = 7, `path` = '', `component` = 'page/menu/list.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = '菜单管理', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693454603776, `update_by` = '', `update_time` = 0, `remark` = '菜单管理' WHERE `menu_id` = 2060; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '数据库URL查询', `parent_id` = 0, `menu_sort` = 10011, `path` = '', `component` = '', `is_frame` = '1', `is_cache` = '1', `menu_type` = 'B', `visible` = '1', `status` = '1', `perms` = 'db:select', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693809550257, `update_by` = '', `update_time` = 0, `remark` = '数据库查询操作' WHERE `menu_id` = 2065; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '数据库URL新增', `parent_id` = 0, `menu_sort` = 10012, `path` = '', `component` = '', `is_frame` = '1', `is_cache` = '1', `menu_type` = 'B', `visible` = '1', `status` = '1', `perms` = 'db:insert', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693809619984, `update_by` = '', `update_time` = 0, `remark` = '数据库URL新增' WHERE `menu_id` = 2066; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '数据库URL更新', `parent_id` = 0, `menu_sort` = 10013, `path` = '', `component` = '', `is_frame` = '1', `is_cache` = '1', `menu_type` = 'B', `visible` = '1', `status` = '1', `perms` = 'db:update', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693809717502, `update_by` = '', `update_time` = 0, `remark` = '数据库URL更新' WHERE `menu_id` = 2067; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '数据库URL删除', `parent_id` = 0, `menu_sort` = 10014, `path` = '', `component` = '', `is_frame` = '1', `is_cache` = '1', `menu_type` = 'B', `visible` = '1', `status` = '1', `perms` = 'db:delete', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1693809752149, `update_by` = '', `update_time` = 0, `remark` = '数据库URL删除' WHERE `menu_id` = 2068; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '用户信息', `parent_id` = 0, `menu_sort` = 559, `path` = 'page', `component` = '', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'D', `visible` = '0', `status` = '1', `perms` = '', `icon` = 'fa fa-home', `create_by` = 'AGrand', `create_time` = 1694086316660, `update_by` = 'AGrand', `update_time` = 1694172973622, `remark` = '' WHERE `menu_id` = 2069; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = 'UDM鉴权用户', `parent_id` = 2069, `menu_sort` = 1, `path` = '', `component` = 'page/nfUserInfo/authList.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = 'root', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1694086376417, `update_by` = 'AGrand', `update_time` = 1694501279203, `remark` = '' WHERE `menu_id` = 2070; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '5G基站信息', `parent_id` = 2069, `menu_sort` = 4, `path` = '', `component` = 'page/nfUserInfo/5gBase.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = 'root', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1694141926419, `update_by` = 'AGrand', `update_time` = 1694501340788, `remark` = '' WHERE `menu_id` = 2071; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = 'IMS在线用户', `parent_id` = 2069, `menu_sort` = 3, `path` = '', `component` = 'page/baseInfo/imsOnline.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = 'root', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1694425197596, `update_by` = 'AGrand', `update_time` = 1694575034285, `remark` = '' WHERE `menu_id` = 2072; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = 'UDM签约用户', `parent_id` = 2069, `menu_sort` = 2, `path` = '', `component` = 'page/nfUserInfo/subsList.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = 'root', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1694501260782, `update_by` = 'AGrand', `update_time` = 1694501323769, `remark` = '' WHERE `menu_id` = 2073; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = 'UE在线信息', `parent_id` = 2069, `menu_sort` = 4, `path` = '', `component` = 'page/baseInfo/ueInfoList.html', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = 'root', `icon` = '#', `create_by` = 'AGrand', `create_time` = 1694595147722, `update_by` = 'AGrand', `update_time` = 1694595166749, `remark` = '' WHERE `menu_id` = 2074; + +UPDATE `omc_db`.`sys_menu` SET `menu_name` = '网元管理', `parent_id` = 4, `menu_sort` = 1, `path` = 'neManage', `component` = 'configManage/neManage/index', `is_frame` = '1', `is_cache` = '0', `menu_type` = 'M', `visible` = '1', `status` = '1', `perms` = 'configManage:neManage:index', `icon` = 'icon-biaoqing', `create_by` = 'AGrand', `create_time` = 1694600408970, `update_by` = '', `update_time` = 0, `remark` = '网元管理' WHERE `menu_id` = 2075; + +INSERT INTO `omc_db`.`sys_post` (`post_id`, `post_code`, `post_name`, `post_sort`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1, 'ceo', '董事长', 1, '1', 'AGrand', 1697110106499, '', 0, ''); + +INSERT INTO `omc_db`.`sys_post` (`post_id`, `post_code`, `post_name`, `post_sort`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2, 'se', '项目经理', 2, '1', 'AGrand', 1697110106502, '', 0, ''); + +INSERT INTO `omc_db`.`sys_post` (`post_id`, `post_code`, `post_name`, `post_sort`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (3, 'hr', '人力资源', 3, '1', 'AGrand', 1697110106504, '', 0, ''); + +INSERT INTO `omc_db`.`sys_post` (`post_id`, `post_code`, `post_name`, `post_sort`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (4, 'user', '普通员工', 4, '1', 'AGrand', 1697110106507, '', 0, ''); + +DELETE FROM `omc_db`.`sys_role` WHERE `role_id` = 3; + +UPDATE `omc_db`.`sys_role` SET `role_name` = '管理员', `role_key` = 'admin', `role_sort` = 1, `data_scope` = '1', `menu_check_strictly` = '1', `dept_check_strictly` = '1', `status` = '1', `del_flag` = '0', `create_by` = 'AGrand', `create_time` = 1697091437683, `update_by` = '', `update_time` = 0, `remark` = '管理员' WHERE `role_id` = 1; + +UPDATE `omc_db`.`sys_role` SET `role_name` = '普通角色', `role_key` = 'common', `role_sort` = 2, `data_scope` = '2', `menu_check_strictly` = '1', `dept_check_strictly` = '1', `status` = '1', `del_flag` = '0', `create_by` = 'AGrand', `create_time` = 1697091437686, `update_by` = '', `update_time` = 0, `remark` = '普通角色' WHERE `role_id` = 2; + +INSERT INTO `omc_db`.`sys_user` (`user_id`, `dept_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `del_flag`, `login_ip`, `login_date`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1, 103, 'AGrand', '管理员', 'sys', 'AGrand@agt.com', '', '0', '', '$2a$10$njuHCwb/YqIr/zt.aXb3F.UIAT0aymrbaxd.SwP1Ibo320w2CDzYO', '1', '0', '127.0.0.1', 1697091656500, 'AGrand', 1697091656500, 'AGrand', 1697161493601, '管理员'); + +INSERT INTO `omc_db`.`sys_user` (`user_id`, `dept_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `del_flag`, `login_ip`, `login_date`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2, 103, 'admin', '管理员', 'sys', 'admin@agt.com', '', '0', '', '$2a$10$QgIcp6yuOEGrEU0TNU12K.uQRLbcufesEU7hiRYlRSSdUO7OAkoTq', '1', '0', '127.0.0.1', 1697091656503, 'AGrand', 1697091656503, 'AGrand', 1697161493601, '普通人员'); + +INSERT INTO `omc_db`.`sys_user_post` (`user_id`, `post_id`) VALUES (1, 1); + +INSERT INTO `omc_db`.`sys_user_post` (`user_id`, `post_id`) VALUES (2, 2); + +INSERT INTO `omc_db`.`sys_user_role` (`user_id`, `role_id`) VALUES (2, 1); + +SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/features/cm/param.go b/features/cm/param.go index d273661b..74089cd6 100644 --- a/features/cm/param.go +++ b/features/cm/param.go @@ -12,6 +12,7 @@ import ( "io" "net/http" + tokenConst "ems.agt/src/framework/constants/token" "github.com/go-resty/resty/v2" "github.com/gorilla/mux" ) @@ -43,7 +44,7 @@ func GetParamConfigFromNF(w http.ResponseWriter, r *http.Request) { } restHostPort := fmt.Sprintf("http://127.0.0.1:%d", config.GetYamlConfig().Rest[0].Port) - getNeInfoPattern := fmt.Sprintf(config.UriPrefix+"/databaseManagement/v1/%s/ne_info", config.GetYamlConfig().Database.Name) + getNeInfoPattern := fmt.Sprintf(config.DefaultUriPrefix+"/databaseManagement/v1/%s/ne_info", config.GetYamlConfig().Database.Name) getNeInfoURI := restHostPort + getNeInfoPattern neId := services.GetUriParamString(r, "ne_id", ",", true, false) if neId == "" { @@ -56,6 +57,7 @@ func GetParamConfigFromNF(w http.ResponseWriter, r *http.Request) { client := resty.New() resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"accessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). diff --git a/features/mml/mml.go b/features/mml/mml.go index 045d0556..229aee0a 100644 --- a/features/mml/mml.go +++ b/features/mml/mml.go @@ -16,9 +16,8 @@ import ( "ems.agt/lib/services" "ems.agt/restagent/config" + tokenConst "ems.agt/src/framework/constants/token" "github.com/gorilla/mux" - - _ "github.com/go-sql-driver/mysql" ) const ( @@ -153,7 +152,7 @@ func PostMMLToNF(w http.ResponseWriter, r *http.Request) { for _, mml := range mmlRequest.MML { mmlCommand := fmt.Sprintf("%s\n", mml) log.Debug("mml command:", mmlCommand) - n, err = conn.Write([]byte(mmlCommand)) + _, err = conn.Write([]byte(mmlCommand)) if err != nil { log.Errorf("Error: %s", err.Error()) return @@ -211,14 +210,15 @@ func PostMMLToOMC(w http.ResponseWriter, r *http.Request) { hostUri := fmt.Sprintf("http://%s:%s", neInfo.Ip, neInfo.Port) omcMmlVar := &mmlp.MmlVar{ - Version: "16.1.1", - Output: mmlp.DefaultFormatType, - MmlHome: config.GetYamlConfig().MML.MmlHome, - Limit: 50, - User: "", - SessionToken: token, - HttpUri: hostUri, - UserAgent: config.GetDefaultUserAgent(), + Version: "16.1.1", + Output: mmlp.DefaultFormatType, + MmlHome: config.GetYamlConfig().MML.MmlHome, + Limit: 50, + User: "", + SessionToken: token, // 旧token + Authorization: r.Header.Get(tokenConst.HEADER_KEY), // 请求Token + HttpUri: hostUri, + UserAgent: config.GetDefaultUserAgent(), } mmlRequest := new(MMLRequest) _ = json.Unmarshal(body, mmlRequest) diff --git a/features/security/account.go b/features/security/account.go index 2be05f52..0e593d8b 100644 --- a/features/security/account.go +++ b/features/security/account.go @@ -14,7 +14,6 @@ import ( sysConfigService "ems.agt/features/sys_config/service" "ems.agt/lib/core/account" "ems.agt/lib/core/cache" - "ems.agt/lib/core/conf" "ems.agt/lib/core/constants/cachekey" "ems.agt/lib/core/utils/ctx" "ems.agt/lib/core/vo/result" @@ -24,7 +23,8 @@ import ( "ems.agt/lib/oauth" "ems.agt/lib/services" "ems.agt/restagent/config" - "github.com/go-admin-team/go-admin-core/logger" + srcConfig "ems.agt/src/framework/config" + "ems.agt/src/framework/redis" "github.com/mojocn/base64Captcha" ) @@ -136,9 +136,10 @@ func LoginFromOMC(w http.ResponseWriter, r *http.Request) { if user != nil { // 缓存用户信息 account.CacheLoginUser(user) + redis.SetByExpire("", "session_token", token, time.Second*1800) // 角色权限集合,管理员拥有所有权限 userId := fmt.Sprint(user.Id) - isAdmin := conf.IsAdmin(userId) + isAdmin := srcConfig.IsAdmin(userId) roles, perms := service.NewServiceAccount.RoleAndMenuPerms(userId, isAdmin) services.ResponseStatusOK200LoginWhitRP(w, token, user, roles, perms) return @@ -301,6 +302,7 @@ func LoginOMC(w http.ResponseWriter, r *http.Request) { if user != nil { // 缓存用户信息 account.CacheLoginUser(user) + redis.SetByExpire("", "session_token", token, time.Second*1800) ctx.JSON(w, 200, result.OkData(map[string]any{ "accessToken": token, })) @@ -363,7 +365,7 @@ func CaptchaImage(w http.ResponseWriter, r *http.Request) { // 验证码表达式解析输出 item, err := driverCaptcha.DrawCaptcha(question) if err != nil { - logger.Infof("Generate Id Question Answer %s : %v", question, err) + log.Infof("Generate Id Question Answer %s : %v", question, err) } else { data["uuid"] = id data["img"] = item.EncodeB64string() @@ -388,7 +390,7 @@ func UserInfo(w http.ResponseWriter, r *http.Request) { } // 角色权限集合,管理员拥有所有权限 userId := fmt.Sprint(loginUser.UserID) - isAdmin := conf.IsAdmin(userId) + isAdmin := srcConfig.IsAdmin(userId) roles, perms := service.NewServiceAccount.RoleAndMenuPerms(userId, isAdmin) ctx.JSON(w, 200, result.OkData(map[string]any{ @@ -403,7 +405,7 @@ func Routers(w http.ResponseWriter, r *http.Request) { userID := ctx.LoginUserToUserID(r) // 前端路由,管理员拥有所有 - isAdmin := conf.IsAdmin(userID) + isAdmin := srcConfig.IsAdmin(userID) buildMenus := service.NewServiceAccount.RouteMenus(userID, isAdmin) ctx.JSON(w, 200, result.OkData(buildMenus)) } diff --git a/features/state/getstate.go b/features/state/getstate.go index bcffc139..1b1b2a3e 100644 --- a/features/state/getstate.go +++ b/features/state/getstate.go @@ -17,6 +17,7 @@ import ( "ems.agt/lib/log" "ems.agt/lib/services" "ems.agt/restagent/config" + tokenConst "ems.agt/src/framework/constants/token" ) type CpuUsage struct { @@ -238,6 +239,7 @@ func GetOneLicenseInfoFromNF(w http.ResponseWriter, r *http.Request) { resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"accessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -342,6 +344,7 @@ func GetAllLicenseInfoFromNF(w http.ResponseWriter, r *http.Request) { resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"accessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -461,6 +464,7 @@ func GetOneSysinfoFromNF(w http.ResponseWriter, r *http.Request) { resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"accessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -594,6 +598,7 @@ func GetAllSysinfoFromNF(w http.ResponseWriter, r *http.Request) { resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"accessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -704,13 +709,14 @@ func GetStateFromNF(w http.ResponseWriter, r *http.Request) { // query all NFs // create rest client restHostPort := fmt.Sprintf("http://127.0.0.1:%d", config.GetYamlConfig().Rest[0].Port) - getNeInfoPattern := fmt.Sprintf(config.UriPrefix+"/databaseManagement/v1/elementType/%s/objectType/ne_info", + getNeInfoPattern := fmt.Sprintf(config.DefaultUriPrefix+"/databaseManagement/v1/elementType/%s/objectType/ne_info", config.GetYamlConfig().Database.Name) getNeInfoURI := restHostPort + getNeInfoPattern + "?WHERE=status='0'" log.Debug("getNeInfoPattern:", getNeInfoPattern) resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"AccessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -724,7 +730,7 @@ func GetStateFromNF(w http.ResponseWriter, r *http.Request) { neList, _ = dborm.XormParseResult(resp.Body()) default: restHostPort := fmt.Sprintf("http://127.0.0.1:%d", config.GetYamlConfig().Rest[0].Port) - getNeInfoPattern := fmt.Sprintf(config.UriPrefix+"/databaseManagement/v1/elementType/%s/objectType/ne_info", + getNeInfoPattern := fmt.Sprintf(config.DefaultUriPrefix+"/databaseManagement/v1/elementType/%s/objectType/ne_info", config.GetYamlConfig().Database.Name) getNeInfoURI := restHostPort + getNeInfoPattern neId := services.GetUriParamString(r, "ne_id", ",", true, false) @@ -737,6 +743,7 @@ func GetStateFromNF(w http.ResponseWriter, r *http.Request) { resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"accessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -767,6 +774,7 @@ func GetStateFromNF(w http.ResponseWriter, r *http.Request) { result["ipAddress"] = ne.Ip resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"accessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). diff --git a/features/sys_menu/api_sys_menu.go b/features/sys_menu/api_sys_menu.go index 8e8657a7..e7f644be 100644 --- a/features/sys_menu/api_sys_menu.go +++ b/features/sys_menu/api_sys_menu.go @@ -7,13 +7,13 @@ import ( "ems.agt/features/sys_menu/consts" "ems.agt/features/sys_menu/model" "ems.agt/features/sys_menu/service" - "ems.agt/lib/core/conf" "ems.agt/lib/core/utils/ctx" "ems.agt/lib/core/utils/regular" "ems.agt/lib/core/vo/result" "ems.agt/lib/midware" "ems.agt/lib/services" "ems.agt/restagent/config" + srcConfig "ems.agt/src/framework/config" ) // 菜单接口添加到路由 @@ -109,7 +109,7 @@ func (s *SysMenuApi) List(w http.ResponseWriter, r *http.Request) { } userId := ctx.LoginUserToUserID(r) - if conf.IsAdmin(userId) { + if srcConfig.IsAdmin(userId) { userId = "*" } data := s.sysMenuService.SelectMenuList(query, userId) @@ -315,7 +315,7 @@ func (s *SysMenuApi) TreeSelect(w http.ResponseWriter, r *http.Request) { } userId := ctx.LoginUserToUserID(r) - if conf.IsAdmin(userId) { + if srcConfig.IsAdmin(userId) { userId = "*" } data := s.sysMenuService.SelectMenuTreeSelectByUserId(query, userId) @@ -342,7 +342,7 @@ func (s *SysMenuApi) RoleMenuTreeSelect(w http.ResponseWriter, r *http.Request) } userId := ctx.LoginUserToUserID(r) - if conf.IsAdmin(userId) { + if srcConfig.IsAdmin(userId) { userId = "*" } menuTreeSelect := s.sysMenuService.SelectMenuTreeSelectByUserId(query, userId) diff --git a/features/sys_user/api_sys_user.go b/features/sys_user/api_sys_user.go index af7ff84f..93018c4f 100644 --- a/features/sys_user/api_sys_user.go +++ b/features/sys_user/api_sys_user.go @@ -9,13 +9,13 @@ import ( sysRoleService "ems.agt/features/sys_role/service" sysUserModel "ems.agt/features/sys_user/model" "ems.agt/features/sys_user/service" - "ems.agt/lib/core/conf" "ems.agt/lib/core/utils/ctx" "ems.agt/lib/core/utils/parse" "ems.agt/lib/core/vo/result" "ems.agt/lib/midware" "ems.agt/lib/services" "ems.agt/restagent/config" + srcConfig "ems.agt/src/framework/config" ) // 用户接口添加到路由 @@ -114,7 +114,7 @@ func (s *SysUserApi) Info(w http.ResponseWriter, r *http.Request) { roles := s.sysRoleService.SelectRoleList(sysRoleModel.SysRole{}) // 不是系统指定管理员需要排除其角色 - if !conf.IsAdmin(userId) { + if !srcConfig.IsAdmin(userId) { rolesFilter := make([]sysRoleModel.SysRole, 0) for _, r := range roles { if r.RoleID != "1" { @@ -193,7 +193,7 @@ func (s *SysUserApi) Edit(w http.ResponseWriter, r *http.Request) { } // 检查是否管理员用户 - // if conf.IsAdmin(body.Id) { + // if srcConfig.IsAdmin(body.Id) { // ctx.JSON(w, 200, result.ErrMsg("不允许操作管理员用户")) // return // } @@ -261,7 +261,7 @@ func (s *SysUserApi) ResetPwd(w http.ResponseWriter, r *http.Request) { } // 检查是否管理员用户 - if conf.IsAdmin(body.UserID) { + if srcConfig.IsAdmin(body.UserID) { ctx.JSON(w, 200, result.ErrMsg("No permission to access user data!")) return } diff --git a/features/sys_user/service/repo_sys_user.go b/features/sys_user/service/repo_sys_user.go index 622b7e68..93c7781c 100644 --- a/features/sys_user/service/repo_sys_user.go +++ b/features/sys_user/service/repo_sys_user.go @@ -8,10 +8,10 @@ import ( sysRoleModel "ems.agt/features/sys_role/model" sysUserModel "ems.agt/features/sys_user/model" "ems.agt/lib/core/datasource" - "ems.agt/lib/core/utils/crypto" "ems.agt/lib/core/utils/date" "ems.agt/lib/core/utils/parse" "ems.agt/lib/log" + "ems.agt/src/framework/utils/crypto" ) // 实例化数据层 RepoSysUser 结构体 diff --git a/features/trace/tcpdump.go b/features/trace/tcpdump.go index ccab0b5b..3c1ffb0f 100644 --- a/features/trace/tcpdump.go +++ b/features/trace/tcpdump.go @@ -130,15 +130,15 @@ func TcpdumpNeUPFTask(w http.ResponseWriter, r *http.Request) { return } - // 开始 - if body.RunType == "start" { + // 开始telnet + if body.RunType == "start_telnet" { // 创建TCP连接 conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", neInfo.Ip, 5002)) if err != nil { - conn.Close() ctx.JSON(w, 200, result.ErrMsg(err.Error())) return } + defer conn.Close() filePcapName := fmt.Sprintf("tmp_%s_%s.pcap", body.NeType, body.NeId) cmdStr := fmt.Sprintf("pcap dispatch trace on max 100000 file %s", filePcapName) @@ -169,15 +169,15 @@ func TcpdumpNeUPFTask(w http.ResponseWriter, r *http.Request) { conn.Close() return } - // 停止 - if body.RunType == "stop" { + // 停止telnet + if body.RunType == "stop_telnet" { // 创建TCP连接 conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", neInfo.Ip, 5002)) if err != nil { - conn.Close() ctx.JSON(w, 200, result.ErrMsg(err.Error())) return } + defer conn.Close() filePcapName := fmt.Sprintf("tmp_%s_%s.pcap", body.NeType, body.NeId) cmdStr := "pcap dispatch trace off" @@ -268,5 +268,72 @@ func TcpdumpNeUPFTask(w http.ResponseWriter, r *http.Request) { return } + // 开始-脚本字符串 + if body.RunType == "start_str" { + fileLogName := fmt.Sprintf("tmp_%s_%s.log", body.NeType, body.NeId) + filePcapName := fmt.Sprintf("tmp_%s_%s.pcap", body.NeType, body.NeId) + scriptStr := "#!/bin/expect\nset capcmd [lindex $argv 0]\nspawn telnet localhost 5002\nexpect \"upfd1# \"\nsend \"$capcmd\\n\"\nexpect \"upfd1# \"\nsend \"quit\\n\"\nexpect \"eof\"" + writeLog := fmt.Sprintf(" > %s 2>&1 \ncat %s", fileLogName, fileLogName) // 执行信息写入日志文件输出,避免弹出code 127 + + capCmdStr := fmt.Sprintf("%s file %s", body.Cmd, filePcapName) + + cmdStr := fmt.Sprintf("cd /tmp\n\necho '%s' > cap.sh\n\nchmod +x cap.sh\n\n./cap.sh '%s'%s", scriptStr, capCmdStr, writeLog) + usernameNe := conf.Get("ne.user").(string) // 网元统一用户 + sshHost := fmt.Sprintf("%s@%s", usernameNe, neInfo.Ip) + msg, err := cmd.ExecWithCheck("ssh", sshHost, cmdStr) + if err != nil { + ctx.JSON(w, 200, result.ErrMsg(err.Error())) + } else { + s := strings.Index(msg, "pcap dispatch trace:") + if s != -1 { + e := strings.Index(msg, "\r\nupfd1#") + msg = msg[s:e] + } else { + msg = "Executed, please stop before proceeding" + } + ctx.JSON(w, 200, result.OkData(map[string]any{ + "cmd": capCmdStr, + "msg": msg, + "fileName": filePcapName, + })) + } + return + } + // 停止-脚本字符串 + if body.RunType == "stop_str" { + fileLogName := fmt.Sprintf("tmp_%s_%s.log", body.NeType, body.NeId) + filePcapName := fmt.Sprintf("tmp_%s_%s.pcap", body.NeType, body.NeId) + scriptStr := "#!/bin/expect\nset capcmd [lindex $argv 0]\nspawn telnet localhost 5002\nexpect \"upfd1# \"\nsend \"$capcmd\\n\"\nexpect \"upfd1# \"\nsend \"quit\\n\"\nexpect \"eof\"" + writeLog := fmt.Sprintf(" > %s 2>&1 \ncat %s", fileLogName, fileLogName) // 执行信息写入日志文件输出,避免弹出code 127 + + capCmdStr := body.Cmd + + cmdStr := fmt.Sprintf("cd /tmp\n\necho '%s' > cap.sh\n\nchmod +x cap.sh\n\n./cap.sh '%s'%s", scriptStr, capCmdStr, writeLog) + + usernameNe := conf.Get("ne.user").(string) // 网元统一用户 + sshHost := fmt.Sprintf("%s@%s", usernameNe, neInfo.Ip) + msg, err := cmd.ExecWithCheck("ssh", sshHost, cmdStr) + if err != nil { + ctx.JSON(w, 200, result.ErrMsg(err.Error())) + } else { + s := strings.Index(msg, "pcap dispatch trace:") + if s == -1 { + s = strings.Index(msg, "Write ") + } + if s != -1 { + e := strings.Index(msg, "\r\nupfd1#") + msg = msg[s:e] + } else { + msg = "No stoppable found" + } + ctx.JSON(w, 200, result.OkData(map[string]any{ + "cmd": capCmdStr, + "msg": msg, + "fileName": filePcapName, + })) + } + return + } + ctx.JSON(w, 200, result.ErrMsg("runType is start or stop")) } diff --git a/features/udm_user/service/service_redis_data.go b/features/udm_user/service/service_redis_data.go index 1f208d2f..f1c691ea 100644 --- a/features/udm_user/service/service_redis_data.go +++ b/features/udm_user/service/service_redis_data.go @@ -4,7 +4,7 @@ import ( "strings" "ems.agt/features/udm_user/model" - "ems.agt/lib/core/redis" + "ems.agt/src/framework/redis" ) // phoneImsiList 获取所有imsi diff --git a/features/ue/ue.go b/features/ue/ue.go index 15013f85..7f77458d 100644 --- a/features/ue/ue.go +++ b/features/ue/ue.go @@ -11,6 +11,7 @@ import ( "ems.agt/lib/log" "ems.agt/lib/services" "ems.agt/restagent/config" + tokenConst "ems.agt/src/framework/constants/token" "github.com/go-resty/resty/v2" "github.com/gorilla/mux" ) @@ -129,6 +130,7 @@ func GetUEInfoFromNF(w http.ResponseWriter, r *http.Request) { resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"accessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -192,6 +194,7 @@ func GetUENumFromNF(w http.ResponseWriter, r *http.Request) { resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"accessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -255,6 +258,7 @@ func GetNBInfoFromNF(w http.ResponseWriter, r *http.Request) { resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"accessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). diff --git a/go.mod b/go.mod index e05e85d4..629929ad 100644 --- a/go.mod +++ b/go.mod @@ -4,18 +4,23 @@ go 1.20 require ( github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/dlclark/regexp2 v1.10.0 github.com/gin-gonic/gin v1.9.1 - github.com/go-admin-team/go-admin-core v1.3.12-0.20221121065133-27b7dbe27a8f github.com/go-admin-team/go-admin-core/sdk v1.5.1 github.com/go-resty/resty/v2 v2.7.0 github.com/go-sql-driver/mysql v1.7.1 + github.com/golang-jwt/jwt/v5 v5.0.0 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/gosnmp/gosnmp v1.35.0 github.com/jasonlvhit/gocron v0.0.1 github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f + github.com/matoous/go-nanoid/v2 v2.0.0 github.com/metaleap/go-xsd v0.0.0-20180330193350-61f7638f502f + github.com/mojocn/base64Captcha v1.3.5 + github.com/mssola/user_agent v0.6.0 github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/redis/go-redis/v9 v9.1.0 github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e github.com/robfig/cron/v3 v3.0.1 github.com/shirou/gopsutil v3.21.11+incompatible @@ -29,10 +34,13 @@ require ( golang.org/x/term v0.11.0 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/yaml.v3 v3.0.1 + gorm.io/driver/mysql v1.5.1 + gorm.io/gorm v1.25.2 xorm.io/xorm v1.3.2 - github.com/redis/go-redis/v9 v9.1.0 ) +require github.com/go-admin-team/go-admin-core v1.3.12-0.20221121065133-27b7dbe27a8f // indirect + require ( github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect github.com/bsm/redislock v0.8.2 // indirect @@ -88,7 +96,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect - github.com/mojocn/base64Captcha v1.3.5 github.com/nsqio/go-nsq v1.0.8 // indirect github.com/nyaruka/phonenumbers v1.0.55 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect @@ -102,7 +109,7 @@ require ( github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.5 github.com/subosito/gotenv v1.4.2 // indirect github.com/syndtr/goleveldb v1.0.0 // indirect github.com/tebeka/strftime v0.1.5 // indirect @@ -128,6 +135,5 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/kyokomi/emoji.v1 v1.5.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gorm.io/gorm v1.24.2 // indirect xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 // indirect ) diff --git a/go.sum b/go.sum index 5a079b72..a34f0ccd 100644 --- a/go.sum +++ b/go.sum @@ -121,6 +121,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= +github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= @@ -190,6 +192,7 @@ github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPr github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -204,6 +207,8 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= @@ -387,7 +392,6 @@ github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uc github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -442,6 +446,9 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2 github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U= +github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0= +github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -492,6 +499,8 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4PfoagaA0= github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY= +github.com/mssola/user_agent v0.6.0 h1:uwPR4rtWlCHRFyyP9u2KOV0u8iQXmS7Z7feTrstQwk4= +github.com/mssola/user_agent v0.6.0/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= @@ -647,6 +656,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -1153,8 +1163,11 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/gorm v1.24.2 h1:9wR6CFD+G8nOusLdvkZelOEhpJVwwHzpQOUM+REd6U0= -gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw= +gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o= +gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho= +gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/lib/core/account/account.go b/lib/core/account/account.go index 22791419..53f12c88 100644 --- a/lib/core/account/account.go +++ b/lib/core/account/account.go @@ -8,9 +8,9 @@ import ( sysMenuService "ems.agt/features/sys_menu/service" sysRoleService "ems.agt/features/sys_role/service" "ems.agt/lib/core/cache" - "ems.agt/lib/core/conf" "ems.agt/lib/core/vo" "ems.agt/lib/dborm" + srcConfig "ems.agt/src/framework/config" ) // 登录缓存用户信息 @@ -35,7 +35,7 @@ func CacheLoginUser(user *dborm.User) { } // 是否管理员 - if conf.IsAdmin(loginUser.UserID) { + if srcConfig.IsAdmin(loginUser.UserID) { loginUser.Permissions = []string{"*:*:*"} } else { // 获取权限标识 diff --git a/lib/core/conf/conf.go b/lib/core/conf/conf.go index 506c66a1..c5e25863 100644 --- a/lib/core/conf/conf.go +++ b/lib/core/conf/conf.go @@ -2,51 +2,35 @@ package conf import ( "fmt" - "time" "github.com/spf13/viper" ) +var v *viper.Viper + // 配置文件读取 func InitConfig(configFile string) { + v = viper.New() + // 设置配置文件路径 - viper.SetConfigFile(configFile) + v.SetConfigFile(configFile) // 读取配置文件 - err := viper.ReadInConfig() + err := v.ReadInConfig() if err != nil { fmt.Printf("读取配置文件失败: %v \n", err) return } - - // 记录程序开始运行的时间点 - viper.Set("runTime", time.Now()) -} - -// RunTime 程序开始运行的时间 -func RunTime() time.Time { - return viper.GetTime("runTime") } // Get 获取配置信息 // // Get("framework.name") func Get(key string) any { - return viper.Get(key) + return v.Get(key) } -// IsAdmin 用户是否为管理员 -func IsAdmin(userID string) bool { - if userID == "" { - return false - } - // 从本地配置获取user信息 - // admins := Get("user.adminList").([]any) - admins := []string{"1", "2", "3"} - for _, s := range admins { - if s == userID { - return true - } - } - return false +// AllSettings 全部配置信息 +func AllSettings() map[string]interface{} { + return v.AllSettings() } diff --git a/lib/core/utils/ctx/ctx.go b/lib/core/utils/ctx/ctx.go index f800d7a4..dac758b1 100644 --- a/lib/core/utils/ctx/ctx.go +++ b/lib/core/utils/ctx/ctx.go @@ -8,8 +8,11 @@ import ( "net/url" "os" "path/filepath" + "strings" "ems.agt/lib/core/vo" + commonConstants "ems.agt/src/framework/constants/common" + tokenConst "ems.agt/src/framework/constants/token" "github.com/gorilla/mux" ) @@ -101,13 +104,27 @@ func SaveUploadedFile(r *http.Request, dst string) error { /// ==== 登录用户信息, 通过中间件后预置入 +// Authorization 解析请求头 +func Authorization(r *http.Request) string { + authHeader := r.Header.Get(tokenConst.HEADER_KEY) + if authHeader == "" { + return "" + } + // 拆分 Authorization 请求头,提取 JWT 令牌部分 + arr := strings.Split(authHeader, tokenConst.HEADER_PREFIX) + if len(arr) == 2 && arr[1] == "" { + return "" + } + return arr[1] +} + // 定义自定义类型作为键 type ContextKey string // LoginUser 登录用户信息需要Authorize中间件 func LoginUser(r *http.Request) (vo.LoginUser, error) { // 上下文 - v := r.Context().Value(ContextKey("LoginUser")) + v := r.Context().Value(ContextKey(commonConstants.CTX_LOGIN_USER)) if v != nil { return v.(vo.LoginUser), nil } diff --git a/lib/global/global.go b/lib/global/global.go index 29270bc0..228fdf20 100644 --- a/lib/global/global.go +++ b/lib/global/global.go @@ -26,9 +26,9 @@ const ( ) var ( - Version string - BuildTime string - GoVer string + Version string = "-" + BuildTime string = "-" + GoVer string = "-" ) var ( diff --git a/lib/midware/authorize.go b/lib/midware/authorize.go index 09734487..f8f4f180 100644 --- a/lib/midware/authorize.go +++ b/lib/midware/authorize.go @@ -10,6 +10,8 @@ import ( "ems.agt/lib/core/vo" "ems.agt/lib/core/vo/result" "ems.agt/lib/dborm" + commonConstants "ems.agt/src/framework/constants/common" + tokenUtils "ems.agt/src/framework/utils/token" ) // Authorize 用户身份授权认证校验 @@ -25,30 +27,74 @@ func Authorize(options map[string][]string) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 获取请求头标识信息 + tokenStr := ctx.Authorization(r) + // 获取请求头标识信息-旧头 accessToken := r.Header.Get("AccessToken") - if accessToken == "" { + if tokenStr == "" && accessToken != "" { + // 验证令牌 == 这里直接查数据库session + if !dborm.XormExistValidToken(accessToken, 0) { + ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization valid error")) + return + } + se, err := dborm.XormUpdateSessionShakeTime(accessToken) + if err != nil { + ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization shake error")) + return + } + + // 获取缓存的用户信息 + data, ok := cache.GetLocalTTL(se.AccountId) + if data == nil || !ok { + ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization info error")) + return + } + loginUser := data.(vo.LoginUser) + + // 登录用户角色权限校验 + if options != nil { + var roles []string + for _, item := range loginUser.User.Roles { + roles = append(roles, item.RoleKey) + } + perms := loginUser.Permissions + verifyOk := verifyRolePermission(roles, perms, options) + if !verifyOk { + msg := fmt.Sprintf("Unauthorized access %s %s", r.Method, r.RequestURI) + ctx.JSON(w, 403, result.CodeMsg(403, msg)) + return + } + } + + // 在请求的 Context 中存储数据 + rContext := r.Context() + rContext = context.WithValue(rContext, ctx.ContextKey(commonConstants.CTX_LOGIN_USER), loginUser) + // 继续处理请求 + next.ServeHTTP(w, r.WithContext(rContext)) + return + } + + // 获取请求头标识信息 + if tokenStr == "" { ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization token error")) return } - // 验证令牌 == 这里直接查数据库session - if !dborm.XormExistValidToken(accessToken, 0) { - ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization valid error")) - return - } - se, err := dborm.XormUpdateSessionShakeTime(accessToken) + // 验证令牌 + claims, err := tokenUtils.Verify(tokenStr) if err != nil { - ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization shake error")) + ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization valid error")) return } // 获取缓存的用户信息 - data, ok := cache.GetLocalTTL(se.AccountId) - if data == nil || !ok { - ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization info error")) + loginUser := tokenUtils.LoginUser(claims) + if loginUser.UserID == "" { + ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization shake error")) return } - loginUser := data.(vo.LoginUser) + + // 检查刷新有效期后存入上下文 + tokenUtils.RefreshIn(&loginUser) // 登录用户角色权限校验 if options != nil { @@ -67,7 +113,7 @@ func Authorize(options map[string][]string) func(http.Handler) http.Handler { // 在请求的 Context 中存储数据 rContext := r.Context() - rContext = context.WithValue(rContext, ctx.ContextKey("LoginUser"), loginUser) + rContext = context.WithValue(rContext, ctx.ContextKey(commonConstants.CTX_LOGIN_USER), loginUser) // 继续处理请求 next.ServeHTTP(w, r.WithContext(rContext)) }) diff --git a/lib/midware/midhandle.go b/lib/midware/midhandle.go index 14cb958a..506ee70f 100644 --- a/lib/midware/midhandle.go +++ b/lib/midware/midhandle.go @@ -6,6 +6,7 @@ import ( "ems.agt/lib/log" "ems.agt/lib/services" + tokenConst "ems.agt/src/framework/constants/token" "github.com/gorilla/mux" ) @@ -22,6 +23,7 @@ func LoggerTrace(next http.Handler) http.Handler { log.Trace(" User-Agent:", r.Header.Get("User-Agent")) log.Trace(" Content-Type:", r.Header.Get("Content-Type")) log.Trace(" AccessToken:", r.Header.Get("AccessToken")) + log.Trace(" Authorization:", r.Header.Get(tokenConst.HEADER_KEY)) log.Trace("Trace End=====") //body, _ := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen)) // nop-close to ready r.Body !!! diff --git a/lib/mmlp/parse.go b/lib/mmlp/parse.go index d9a173ac..75d4bb26 100644 --- a/lib/mmlp/parse.go +++ b/lib/mmlp/parse.go @@ -15,6 +15,7 @@ import ( "ems.agt/lib/global" "ems.agt/lib/log" "ems.agt/lib/run" + tokenConst "ems.agt/src/framework/constants/token" "github.com/go-resty/resty/v2" ) @@ -36,14 +37,15 @@ type MmlCommand struct { } type MmlVar struct { - Version string `json:"version"` - Output string `json:"output"` - MmlHome string `json:"mmlHome"` - Limit int `json:"limit"` - User string `json:"user"` - SessionToken string `josn:"sessionToken"` - HttpUri string `json:"httpUri"` - UserAgent string `json:"userAgent"` + Version string `json:"version"` + Output string `json:"output"` + MmlHome string `json:"mmlHome"` + Limit int `json:"limit"` + User string `json:"user"` + SessionToken string `josn:"sessionToken"` + Authorization string `josn:"authorization"` + HttpUri string `json:"httpUri"` + UserAgent string `json:"userAgent"` } // func init() { @@ -504,6 +506,7 @@ func TransMml2HttpReq(omcMmlVar *MmlVar, mml *MmlCommand) (*[]byte, error) { log.Debugf("method: Get requestURI: %s", requestURI) response, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: omcMmlVar.Authorization}). SetHeaders(map[string]string{"accessToken": omcMmlVar.SessionToken}). SetHeaders(map[string]string{"User-Agent": omcMmlVar.UserAgent}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -520,6 +523,7 @@ func TransMml2HttpReq(omcMmlVar *MmlVar, mml *MmlCommand) (*[]byte, error) { log.Debugf("method: Post requestURI: %s", requestURI) response, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: omcMmlVar.Authorization}). SetHeaders(map[string]string{"accessToken": omcMmlVar.SessionToken}). SetHeaders(map[string]string{"User-Agent": omcMmlVar.UserAgent}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -553,6 +557,7 @@ func TransMml2HttpReq(omcMmlVar *MmlVar, mml *MmlCommand) (*[]byte, error) { body := ParseInputBody(inputJson, mml) response, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: omcMmlVar.Authorization}). SetHeaders(map[string]string{"accessToken": omcMmlVar.SessionToken}). SetHeaders(map[string]string{"User-Agent": omcMmlVar.UserAgent}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -569,6 +574,7 @@ func TransMml2HttpReq(omcMmlVar *MmlVar, mml *MmlCommand) (*[]byte, error) { log.Debugf("method: Delete requestURI: %s", requestURI) response, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: omcMmlVar.Authorization}). SetHeaders(map[string]string{"accessToken": omcMmlVar.SessionToken}). SetHeaders(map[string]string{"User-Agent": omcMmlVar.UserAgent}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -584,6 +590,7 @@ func TransMml2HttpReq(omcMmlVar *MmlVar, mml *MmlCommand) (*[]byte, error) { log.Debugf("method: patch requestURI: %s", requestURI) response, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: omcMmlVar.Authorization}). SetHeaders(map[string]string{"accessToken": omcMmlVar.SessionToken}). SetHeaders(map[string]string{"User-Agent": omcMmlVar.UserAgent}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -764,12 +771,19 @@ func ParseOutputResponse(omcMmlVar *MmlVar, outputJson *dborm.MmlOutput, respons output = *ParseErrorOutput(string(response.Body())) } else { log.Trace("mapResults:", mapResults) - errResult := mapResults["error"] - log.Trace("errResult:", errResult) - if len(errResult.(map[string]interface{})) > 0 { - errCode, _ := strconv.Atoi(fmt.Sprintf("%v", errResult.(map[string]interface{})["errorCode"])) - errorInfo := errResult.(map[string]interface{})["errorInfo"] + if v, ok := mapResults["error"]; ok { + vMap := v.(map[string]interface{}) + if len(vMap) > 0 { + errCode, _ := strconv.Atoi(fmt.Sprintf("%v", vMap["errorCode"])) + errorInfo := vMap["errorInfo"] + output = []byte(fmt.Sprintf(outputJson.ErrMsg, errCode, errorInfo)) + } + } else if v, ok := mapResults["code"]; ok { + errCode, _ := strconv.Atoi(fmt.Sprintf("%v", v)) + errorInfo := mapResults["msg"] output = []byte(fmt.Sprintf(outputJson.ErrMsg, errCode, errorInfo)) + } else { + output = []byte(fmt.Sprintf("%v", mapResults)) } } } diff --git a/lib/routes/routes.go b/lib/routes/routes.go index 9ab9299b..0a971566 100644 --- a/lib/routes/routes.go +++ b/lib/routes/routes.go @@ -71,22 +71,10 @@ func init() { Register("GET", sm.CustomUriOMCLocalTime, sm.GetOMCLocalTime, nil) // 数据库直连操作权限 - selectPermission := midware.Authorize(map[string][]string{ - "hasRoles": {"dba"}, - "hasPerms": {"db:select"}, - }) - updatePermission := midware.Authorize(map[string][]string{ - "hasRoles": {"dba"}, - "hasPerms": {"db:update"}, - }) - insertPermission := midware.Authorize(map[string][]string{ - "hasRoles": {"dba"}, - "hasPerms": {"db:insert"}, - }) - deletePermission := midware.Authorize(map[string][]string{ - "hasRoles": {"dba"}, - "hasPerms": {"db:delete"}, - }) + selectPermission := midware.Authorize(map[string][]string{}) + updatePermission := midware.Authorize(map[string][]string{}) + insertPermission := midware.Authorize(map[string][]string{}) + deletePermission := midware.Authorize(map[string][]string{}) // database management Register("GET", dbrest.XormGetDataUri, dbrest.DatabaseGetData, selectPermission) @@ -368,12 +356,12 @@ func NewRouter() *mux.Router { r := mux.NewRouter() // set custom handle for status 404/405 - r.NotFoundHandler = services.CustomResponseNotFound404Handler() - r.MethodNotAllowedHandler = services.CustomResponseMethodNotAllowed405Handler() + // r.NotFoundHandler = services.CustomResponseNotFound404Handler() + // r.MethodNotAllowedHandler = services.CustomResponseMethodNotAllowed405Handler() r.Use(midware.LoggerTrace) - r.Use(midware.Cors) - //r.Use(midware.OptionProcess) + // r.Use(midware.Cors) + // r.Use(midware.OptionProcess) // r.Use(midware.ArrowIPAddr) for _, router := range routers { diff --git a/restagent/config/config.go b/restagent/config/config.go index 92ad9b2b..29aba635 100644 --- a/restagent/config/config.go +++ b/restagent/config/config.go @@ -1,12 +1,10 @@ package config import ( - "flag" "fmt" "os" "strings" - "ems.agt/lib/core/conf" "ems.agt/lib/global" "ems.agt/lib/log" @@ -288,7 +286,7 @@ func GetLogLevel() log.LogLevel { var ( DefaultUriPrefix string = "/api/rest" - UriPrefix string = "/api/rest" + UriPrefix string = "/omc/rest" //TestDataUDM []map[string]interface{} TDatas map[string]NeTestData ) @@ -312,33 +310,33 @@ func GetDefaultUserAgent() string { return "OMC-restagent/" + global.Version } -const defaultConfigFile = "./etc/restconf.yaml" +// const defaultConfigFile = "./etc/restconf.yaml" -func init() { - cfile := flag.String("c", defaultConfigFile, "config file") - pv := flag.Bool("version", false, "print version") - ph := flag.Bool("help", false, "print help") +// func init() { +// cfile := flag.String("c", defaultConfigFile, "config file") +// pv := flag.Bool("version", false, "print version") +// ph := flag.Bool("help", false, "print help") - //global.BuildTime = "Wed May 31 18:24:04 CST 2023" - //global.GoVer = "go version go1.15.7 linux/arm64" - flag.Parse() - if *pv { - fmt.Printf("OMC restagent version: %s\n%s\n%s\n\n", global.Version, global.BuildTime, global.GoVer) - os.Exit(0) - } - if *ph { - flag.Usage() - os.Exit(0) - } +// //global.BuildTime = "Wed May 31 18:24:04 CST 2023" +// //global.GoVer = "go version go1.15.7 linux/arm64" +// flag.Parse() +// if *pv { +// fmt.Printf("OMC restagent version: %s\n%s\n%s\n\n", global.Version, global.BuildTime, global.GoVer) +// os.Exit(0) +// } +// if *ph { +// flag.Usage() +// os.Exit(0) +// } - // 使用viper读取配置 - conf.InitConfig(*cfile) +// // 使用viper读取配置 +// conf.InitConfig(*cfile) - ReadConfig(*cfile) - if GetYamlConfig().OMC.UriPrefix != "" { - UriPrefix = GetYamlConfig().OMC.UriPrefix - } - if GetYamlConfig().TestConfig.Enabled { - ReadTestConfigYaml(GetYamlConfig().TestConfig.File) - } -} +// ReadConfig(*cfile) +// if GetYamlConfig().OMC.UriPrefix != "" { +// UriPrefix = GetYamlConfig().OMC.UriPrefix +// } +// if GetYamlConfig().TestConfig.Enabled { +// ReadTestConfigYaml(GetYamlConfig().TestConfig.File) +// } +// } diff --git a/restagent/etc/restconf-t.yaml b/restagent/etc/restconf-t.yaml index bafe50f4..2a8acc46 100644 --- a/restagent/etc/restconf-t.yaml +++ b/restagent/etc/restconf-t.yaml @@ -62,7 +62,7 @@ ne: # chk2ne: true/false, if put OmcNeConfig parameters to NE omc: - uriPrefix: /api/rest/oam + uriPrefix: "/omc/rest" neType: OMC neId: 001 rmUID: 4400HX101 diff --git a/restagent/etc/restconf.yaml b/restagent/etc/restconf.yaml index 879dc248..7340d86a 100644 --- a/restagent/etc/restconf.yaml +++ b/restagent/etc/restconf.yaml @@ -79,7 +79,7 @@ ne: # chk2ne: true/false, if put OmcNeConfig parameters to NE omc: - uriPrefix: /api/rest/oam + uriPrefix: "/omc/rest" neType: OMC neId: 001 rmUID: 4400HX101 diff --git a/restagent/restagent.go b/restagent/restagent.go index 336e172c..25d55030 100644 --- a/restagent/restagent.go +++ b/restagent/restagent.go @@ -4,24 +4,23 @@ import ( "crypto/tls" "crypto/x509" "fmt" - "net" "net/http" "os" "strconv" "strings" - "ems.agt/lib/core/redis" + "ems.agt/features/dbrest" + "ems.agt/features/fm" + "ems.agt/features/lm" + "ems.agt/features/pm" "ems.agt/lib/dborm" "ems.agt/lib/global" "ems.agt/lib/log" "ems.agt/lib/routes" - - "ems.agt/features/dbrest" - "ems.agt/features/fm" - "ems.agt/features/lm" - "ems.agt/features/monitor/monitor" - "ems.agt/features/pm" "ems.agt/restagent/config" + "ems.agt/src" + libSession "ems.agt/src/lib_features/session" + "github.com/gin-gonic/gin" ) // const defaultConfigFile = "./etc/restconf.yaml" @@ -46,25 +45,25 @@ import ( // //fmt.Println(config.UriPrefix) // } -func listenIPv6(ipv6 string, port int) { - // - addr := &net.TCPAddr{ - IP: net.ParseIP(ipv6), - Port: port, - } +// func listenIPv6(ipv6 string, port int) { +// // +// addr := &net.TCPAddr{ +// IP: net.ParseIP(ipv6), +// Port: port, +// } - listener, err := net.ListenTCP("tcp6", addr) - if err != nil { - fmt.Println("Failed to listen:", err) - return - } +// listener, err := net.ListenTCP("tcp6", addr) +// if err != nil { +// fmt.Println("Failed to listen:", err) +// return +// } - server := &http.Server{} - err = server.Serve(listener) - if err != nil { - fmt.Println("Failed to serve:", err) - } -} +// server := &http.Server{} +// err = server.Serve(listener) +// if err != nil { +// fmt.Println("Failed to serve:", err) +// } +// } func HttpListen(addr string, router http.Handler) { err := http.ListenAndServe(addr, router) @@ -128,6 +127,10 @@ func HttpListenWebServer(addr string) { } func main() { + // src 配置中心初始加载 + src.ConfigurationInit() + app := src.AppEngine() + conf := config.GetYamlConfig() log.InitLogger(conf.Logger.File, conf.Logger.Duration, conf.Logger.Count, "omc:restagent", config.GetLogLevel()) @@ -166,22 +169,28 @@ func main() { os.Exit(4) } - // 连接redis - redis.Connect() + // 将 mux.Router 注册到 gin.Engine - router := routes.NewRouter() + // 默认路由组 + defaultUriGroup := app.Group(config.DefaultUriPrefix) + defaultUriGroup.Use(libSession.SessionHeader()) + defaultUriGroup.Any("/*any", gin.WrapH(routes.NewRouter())) + // 可配置前缀路由组 + uriGroup := app.Group(config.UriPrefix) + uriGroup.Use(libSession.SessionHeader()) + uriGroup.Any("/*any", gin.WrapH(routes.NewRouter())) // 开启监控采集 - monitor.StartMonitor(false, "") + // monitor.StartMonitor(false, "") for _, rest := range conf.Rest { // ipv4 goroutines if rest.IPv4 != "" { listen := rest.IPv4 + ":" + strconv.Itoa(int(rest.Port)) if strings.ToLower(rest.Scheme) == "https" { - go HttpListenTLS(listen, rest.CertFile, rest.KeyFile, router) + go HttpListenTLS(listen, rest.CertFile, rest.KeyFile, app) } else { - go HttpListen(listen, router) + go HttpListen(listen, app) } } @@ -189,9 +198,9 @@ func main() { if rest.IPv6 != "" { listenv6 := "[" + rest.IPv6 + "]" + ":" + strconv.Itoa(int(rest.Port)) if strings.ToLower(rest.Scheme) == "https" { - go HttpListenTLS(listenv6, rest.CertFile, rest.KeyFile, router) + go HttpListenTLS(listenv6, rest.CertFile, rest.KeyFile, app) } else { - go HttpListen(listenv6, router) + go HttpListen(listenv6, app) } } } diff --git a/src/app.go b/src/app.go new file mode 100644 index 00000000..c9ef2096 --- /dev/null +++ b/src/app.go @@ -0,0 +1,119 @@ +package src + +import ( + "fmt" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/errorcatch" + "ems.agt/src/framework/middleware" + "ems.agt/src/framework/middleware/security" + "ems.agt/src/modules/common" + "ems.agt/src/modules/monitor" + "ems.agt/src/modules/system" + + "github.com/gin-gonic/gin" +) + +// 路由函数句柄,交给由 http.ListenAndServe(addr, router) +func AppEngine() *gin.Engine { + app := initAppEngine() + + // 初始全局默认 + initDefeat(app) + + // 初始模块路由 + initModulesRoute(app) + + // 读取服务配置 + app.ForwardedByClientIP = config.Get("server.proxy").(bool) + addr := fmt.Sprintf(":%d", config.Get("server.port").(int)) + + // 启动服务 + fmt.Printf("\nopen http://localhost%s \n\n", addr) + return app +} + +// 运行服务程序 main.go +// +// func main() { +// src.ConfigurationInit() +// if err := src.RunServer(); err != nil { +// src.ConfigurationClose() +// } +// } +func RunServer() error { + app := initAppEngine() + + // 初始全局默认 + initDefeat(app) + + // 初始模块路由 + initModulesRoute(app) + + // 读取服务配置 + app.ForwardedByClientIP = config.Get("server.proxy").(bool) + addr := fmt.Sprintf(":%d", config.Get("server.port").(int)) + + // 启动服务 + fmt.Printf("\nopen http://localhost%s \n\n", addr) + return app.Run(addr) +} + +// 初始应用引擎 +func initAppEngine() *gin.Engine { + var app *gin.Engine + + // 禁止控制台日志输出的颜色 + gin.DisableConsoleColor() + + // 根据运行环境注册引擎 + if config.Env() == "prod" { + gin.SetMode(gin.ReleaseMode) + app = gin.New() + app.Use(gin.Recovery()) + } else { + app = gin.Default() + } + + return app +} + +// 初始全局默认 +func initDefeat(app *gin.Engine) { + // 全局中间件 + app.Use(errorcatch.ErrorCatch(), middleware.Report(), middleware.Cors(), security.Security()) + + // 静态目录-静态资源 + if v := config.Get("staticFile.default"); v != nil { + fsMap := v.(map[string]any) + prefix, dir := fsMap["prefix"], fsMap["dir"] + if prefix != nil && dir != nil { + app.StaticFS(prefix.(string), gin.Dir(dir.(string), true)) + } + } + + // 静态目录-上传资源 + if v := config.Get("staticFile.upload"); v != nil { + fsMap := v.(map[string]any) + prefix, dir := fsMap["prefix"], fsMap["dir"] + if prefix != nil && dir != nil { + app.StaticFS(prefix.(string), gin.Dir(dir.(string), true)) + } + } + + // 路由未找到时 + app.NoRoute(func(c *gin.Context) { + c.JSON(404, gin.H{ + "code": 404, + "msg": fmt.Sprintf("%s Not Found", c.Request.RequestURI), + }) + }) +} + +// 初始模块路由 +func initModulesRoute(app *gin.Engine) { + + common.Setup(app) + monitor.Setup(app) + system.Setup(app) +} diff --git a/src/configuration.go b/src/configuration.go new file mode 100644 index 00000000..fdf27120 --- /dev/null +++ b/src/configuration.go @@ -0,0 +1,35 @@ +package src + +import ( + "ems.agt/src/framework/config" + "ems.agt/src/framework/cron" + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/redis" +) + +// 配置中心初始加载 +func ConfigurationInit() { + // 初始配置参数 + config.InitConfig() + // 初始程序日志 + logger.InitLogger() + // 连接数据库实例 + datasource.Connect() + // 连接Redis实例 + redis.Connect() + // 启动调度任务实例 + cron.StartCron() +} + +// 配置中心相关配置关闭连接 +func ConfigurationClose() { + // 停止调度任务实例 + cron.StopCron() + // 关闭Redis实例 + redis.Close() + // 关闭数据库实例 + datasource.Close() + // 关闭程序日志 + logger.Close() +} diff --git a/src/framework/config/config.go b/src/framework/config/config.go new file mode 100644 index 00000000..dfe60ebc --- /dev/null +++ b/src/framework/config/config.go @@ -0,0 +1,163 @@ +package config + +import ( + "bytes" + "embed" + "fmt" + "log" + "os" + "time" + + libConfig "ems.agt/src/lib_features/config" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +//go:embed config/*.yaml +var configFiles embed.FS + +// 初始化程序配置 +func InitConfig() { + initFlag() + initViper() +} + +// 指定参数绑定 +func initFlag() { + // --env prod + pflag.String("env", "prod", "Specify Run Environment Configuration local or prod") + // --c /etc/restconf.yaml + // -c /etc/restconf.yaml + pConfig := pflag.StringP("config", "c", "./etc/restconf.yaml", "Specify Configuration File") + // --version + // -V + pVersion := pflag.BoolP("version", "V", false, "Output program version") + // --help + pHelp := pflag.Bool("help", false, "Viewing Help Commands") + + pflag.Parse() + + // 参数固定输出 + if *pVersion { + buildInfo := libConfig.BuildInfo() + fmt.Println(buildInfo) + os.Exit(1) + } + if *pHelp { + pflag.Usage() + os.Exit(1) + } + + // 外层lib和features使用的配置 + libConfig.ConfigRead(*pConfig) + + viper.BindPFlags(pflag.CommandLine) +} + +// 配置文件读取 +func initViper() { + // 在当前工作目录中寻找配置 + // viper.AddConfigPath("config") + // viper.AddConfigPath("src/config") + // 如果配置文件名中没有扩展名,则需要设置Type + viper.SetConfigType("yaml") + + // 从 embed.FS 中读取默认配置文件内容 + configDefault, err := configFiles.ReadFile("config/config.default.yaml") + if err != nil { + log.Fatalf("ReadFile config default file: %s", err) + return + } + // 设置默认配置文件内容到 viper + err = viper.ReadConfig(bytes.NewReader(configDefault)) + if err != nil { + log.Fatalf("NewReader config default file: %s", err) + return + } + + // // 配置文件的名称(无扩展名) + // viper.SetConfigName("config.default") + // // 读取默认配置文件 + // if err := viper.ReadInConfig(); err != nil { + // log.Fatalf("fatal error config default file: %s", err) + // } + + env := viper.GetString("env") + if env != "local" && env != "prod" { + log.Fatalf("fatal error config env for local or prod : %s", env) + } + log.Printf("Current service environment operation configuration => %s \n", env) + + // 加载运行配置文件合并相同配置 + if env == "prod" { + // viper.SetConfigName("config.prod") + // 从 embed.FS 中读取默认配置文件内容 + configProd, err := configFiles.ReadFile("config/config.prod.yaml") + if err != nil { + log.Fatalf("ReadFile config prod file: %s", err) + return + } + // 设置默认配置文件内容到 viper + err = viper.MergeConfig(bytes.NewReader(configProd)) + if err != nil { + log.Fatalf("NewReader config prod file: %s", err) + return + } + } else { + // viper.SetConfigName("config.local") + // 从 embed.FS 中读取默认配置文件内容 + configLocal, err := configFiles.ReadFile("config/config.local.yaml") + if err != nil { + log.Fatalf("ReadFile config local file: %s", err) + return + } + // 设置默认配置文件内容到 viper + err = viper.MergeConfig(bytes.NewReader(configLocal)) + if err != nil { + log.Fatalf("NewReader config local file: %s", err) + return + } + } + // if err := viper.MergeInConfig(); err != nil { + // log.Fatalf("fatal error config MergeInConfig: %s", err) + // } + + // 合并外层lib和features使用配置 + libConfig.ConfigInMerge() + + // 记录程序开始运行的时间点 + viper.Set("runTime", time.Now()) +} + +// Env 获取运行服务环境 +// local prod +func Env() string { + return viper.GetString("env") +} + +// RunTime 程序开始运行的时间 +func RunTime() time.Time { + return viper.GetTime("runTime") +} + +// Get 获取配置信息 +// +// Get("framework.name") +func Get(key string) any { + return viper.Get(key) +} + +// IsAdmin 用户是否为管理员 +func IsAdmin(userID string) bool { + if userID == "" { + return false + } + // 从本地配置获取user信息 + admins := Get("user.adminList").([]any) + for _, s := range admins { + if s.(string) == userID { + return true + } + } + return false +} diff --git a/src/framework/config/config/config.default.yaml b/src/framework/config/config/config.default.yaml new file mode 100644 index 00000000..2a5daa64 --- /dev/null +++ b/src/framework/config/config/config.default.yaml @@ -0,0 +1,208 @@ +# 项目信息 +framework: + name: "ems_agt" + version: "0.0.1" + +# 应用服务配置 +server: + # 服务端口 + port: 3040 + # 是否开启代理 + proxy: false + +# 日志 +logger: + fileDir: "/usr/local/omc/log" + fileName: "ems_agt.log" + level: 2 # 日志记录的等级 0:silent<1:info<2:warn<3:error + maxDay: 30 # 日志会保留 30 天 + maxSize: 10 # 调整按 10MB 大小的切割 + +# 静态文件配置, 相对项目根路径或填绝对路径 +staticFile: + # 默认资源,dir目录需要预先创建 + default: + prefix: "/static" + dir: "/usr/local/omc/static" + # 文件上传资源目录映射,与项目目录同级 + upload: + prefix: "/upload" + dir: "/usr/local/omc/upload" + +# 文件上传 +upload: + # 最大上传文件大小,默认为 10mb + fileSize: 10 + # 文件扩展名白名单 + whitelist: + # 图片 + - ".bmp" + - ".webp" + - ".gif" + - ".jpg" + - ".jpeg" + - ".png" + # word excel powerpoint + - ".doc" + - ".docx" + - ".xls" + - ".xlsx" + - ".ppt" + - ".pptx" + # 文本文件 + - ".html" + - ".htm" + - ".txt" + # pdf + - ".pdf" + # 压缩文件 + - ".zip" + - ".gz" + - ".tgz" + - ".gzip" + # 音视频格式 + - ".mp3" + - ".mp4" + - ".avi" + - ".rmvb" + +# cors 跨域 +cors: + # 设置 Access-Control-Allow-Origin 的值,【默认值】会获取请求头上的 origin + # 例如:http://mask-api.org + # 如果请求设置了 credentials,则 origin 不能设置为 * + origin: "*" + # 设置 Access-Control-Allow-Credentials,【默认值】false + credentials: true + # 设置 Access-Control-Max-Age + maxAge: 31536000 + # 允许跨域的方法,【默认值】为 GET,HEAD,PUT,POST,DELETE,PATCH + allowMethods: + - "OPTIONS" + - "HEAD" + - "GET" + - "POST" + - "PUT" + - "DELETE" + - "PATCH" + # 设置 Access-Control-Allow-Headers 的值,【默认值】会获取请求头上的 Access-Control-Request-Headers + allowHeaders: + - "X-App-Code" + - "X-App-Version" + - "Authorization" + - "Origin" + - "X-Requested-With" + - "Content-Type" + - "Content-Language" + - "Accept" + - "Range" + - "Accesstoken" + - "Operationtype" + # 设置 Access-Control-Expose-Headers 的值 + exposeHeaders: + - "X-RepeatSubmit-Rest" + +# security 安全 +security: + csrf: + enable: false + type: "referer" + # 允许调用的域名地址的,例如:http:///mask-api + refererWhiteList: + - "127.0.0.1:3030" + xframe: + enable: true + value: "SAMEORIGIN" + csp: + enable: true + hsts: + enable: false + maxAge: 31536000 + includeSubdomains: false + noopen: + enable: false + nosniff: + enable: false + xssProtection: + enable: true + value: "1; mode=block" + +# JWT 令牌配置 +jwt: + # 令牌算法 HS256 HS384 HS512 + algorithm: "HS512" + # 令牌密钥 + secret: "217a0481c7f9cfe1cb547d32ee012b0f" + # 令牌有效期(默认120分钟) + expiresIn: 120 + # 验证令牌有效期,相差不足xx分钟,自动刷新缓存 + refreshIn: 20 + +# GORM 数据源 +gorm: + dataSource: + # 默认数据库实例 + default: + type: "mysql" + host: "127.0.0.1" + port: 3306 + username: "<用户名>" + password: "<密码>" + database: "<数据库>" + logging: false + # 多个数据源时可以用这个指定默认的数据源 + defaultDataSourceName: "default" + +# Redis 缓存数据 +redis: + dataSource: + default: + port: 6379 # Redis port + host: "127.0.0.1" # Redis host + password: "<密码>" + db: 0 # Redis db_num + # 多个数据源时可以用这个指定默认的数据源 + defaultDataSourceName: "default" + +# 用户配置 +user: + # 密码 + password: + # 密码最大错误次数 + maxRetryCount: 5 + # 密码锁定时间,单位分钟(默认10分钟) + lockTime: 10 + # 管理员列表 + adminList: + - "1" + - "2" + +# char 字符验证码配置 +charCaptcha: + # 宽度 + width: 120 + # 高度 + height: 40 + # 干扰线条的数量 + noise: 4 + # 验证码的字符是否有颜色,默认没有,如果设定了背景,则默认有 + color: true + # 验证码图片背景颜色 + background: "#fafafa" + # 验证码长度 + size: 4 + # 验证码字符 + chars: "023456789abcdefghjkmnprstuvwxyz" + +# math 数值计算码配置 +mathCaptcha: + # 宽度 + width: 120 + # 高度 + height: 40 + # 干扰线条的数量 + noise: 4 + # 验证码的字符是否有颜色,默认没有,如果设定了背景,则默认有 + color: true + # 验证码图片背景颜色 + background: "#fafafa" diff --git a/src/framework/config/config/config.local.yaml b/src/framework/config/config/config.local.yaml new file mode 100644 index 00000000..a0b210ed --- /dev/null +++ b/src/framework/config/config/config.local.yaml @@ -0,0 +1,53 @@ +# 应用服务配置 +server: + port: 3040 + +# 日志 +logger: + fileDir: "C:/usr/local/omc/log" + level: 0 # 输出最低等级 + +# 静态文件配置, 相对项目根路径或填绝对路径 +staticFile: + default: + dir: "C:/usr/local/omc/static" + # 文件上传资源目录映射,与项目目录同级 + upload: + dir: "C:/usr/local/omc/upload" + +# security 安全 +security: + csrf: + refererWhiteList: + - "localhost:3131" + - "127.0.0.1:3131" + +# GORM 数据源 +gorm: + dataSource: + default: + type: "mysql" + host: "192.168.0.229" + port: 33066 + username: "root" + password: "1000omc@kp!" + database: "omc_db_dev" + logging: true + +# Redis 缓存数据,数据源声明全小写 +redis: + dataSource: + # OMC系统使用库 + default: + port: 6379 # Redis port + host: "192.168.0.229" # Redis host + password: "" + db: 10 # Redis db_num + # UDM网元用户库 + udmuser: + port: 6379 # Redis port + host: "192.168.0.229" + password: "" + db: 0 # Redis db_num + # 多个数据源时可以用这个指定默认的数据源 + defaultDataSourceName: "default" diff --git a/src/framework/config/config/config.prod.yaml b/src/framework/config/config/config.prod.yaml new file mode 100644 index 00000000..36cccb2f --- /dev/null +++ b/src/framework/config/config/config.prod.yaml @@ -0,0 +1,32 @@ +# 应用服务配置 +server: + port: 3030 + proxy: true + +# security 安全 +security: + csrf: + # 允许调用的域名地址的,例如:http:/// + refererWhiteList: + - "127.0.0.1" + - "" + +# GORM 数据源 +gorm: + dataSource: + default: + type: "mysql" + host: "" + port: 3306 + username: "<用户名>" + password: "<密码>" + database: "<数据库>" + +# Redis 缓存数据 +redis: + dataSource: + default: + port: 6379 # Redis port + host: "" + password: "<密码>" + db: 0 # Redis db_num diff --git a/src/framework/constants/admin/admin.go b/src/framework/constants/admin/admin.go new file mode 100644 index 00000000..7ac32c50 --- /dev/null +++ b/src/framework/constants/admin/admin.go @@ -0,0 +1,12 @@ +package admin + +// 管理员常量信息 + +// 管理员-系统指定角色ID +const ROLE_ID = "1" + +// 管理员-系统指定角色KEY +const ROLE_KEY = "admin" + +// 管理员-系统指定权限 +const PERMISSION = "*:*:*" diff --git a/src/framework/constants/cachekey/cachekey.go b/src/framework/constants/cachekey/cachekey.go new file mode 100644 index 00000000..0e69445f --- /dev/null +++ b/src/framework/constants/cachekey/cachekey.go @@ -0,0 +1,24 @@ +package cachekey + +// 缓存的key常量 + +// 登录用户 +const LOGIN_TOKEN_KEY = "login_tokens:" + +// 验证码 +const CAPTCHA_CODE_KEY = "captcha_codes:" + +// 参数管理 +const SYS_CONFIG_KEY = "sys_config:" + +// 字典管理 +const SYS_DICT_KEY = "sys_dict:" + +// 防重提交 +const REPEAT_SUBMIT_KEY = "repeat_submit:" + +// 限流 +const RATE_LIMIT_KEY = "rate_limit:" + +// 登录账户密码错误次数 +const PWD_ERR_CNT_KEY = "pwd_err_cnt:" diff --git a/src/framework/constants/captcha/captcha.go b/src/framework/constants/captcha/captcha.go new file mode 100644 index 00000000..816a65e4 --- /dev/null +++ b/src/framework/constants/captcha/captcha.go @@ -0,0 +1,12 @@ +package captcha + +// 验证码常量信息 + +// 验证码有效期,单位秒 +const EXPIRATION = 2 * 60 + +// 验证码类型-数值计算 +const TYPE_CHAR = "char" + +// 验证码类型-字符验证 +const TYPE_MATH = "math" diff --git a/src/framework/constants/common/common.go b/src/framework/constants/common/common.go new file mode 100644 index 00000000..37861049 --- /dev/null +++ b/src/framework/constants/common/common.go @@ -0,0 +1,21 @@ +package common + +// 通用常量信息 + +// www主域 +const WWW = "www." + +// http请求 +const HTTP = "http://" + +// https请求 +const HTTPS = "https://" + +// 通用状态标识-正常/成功/是 +const STATUS_YES = "1" + +// 通用状态标识-停用/失败/否 +const STATUS_NO = "0" + +// 上下文信息-登录用户 +const CTX_LOGIN_USER = "loginuser" diff --git a/src/framework/constants/menu/menu.go b/src/framework/constants/menu/menu.go new file mode 100644 index 00000000..94913cbd --- /dev/null +++ b/src/framework/constants/menu/menu.go @@ -0,0 +1,24 @@ +package menu + +// 系统菜单常量信息 + +const ( + // 组件布局类型-基础布局组件标识 + COMPONENT_LAYOUT_BASIC = "BasicLayout" + // 组件布局类型-空白布局组件标识 + COMPONENT_LAYOUT_BLANK = "BlankLayout" + // 组件布局类型-内链接布局组件标识 + COMPONENT_LAYOUT_LINK = "LinkLayout" +) + +const ( + // 菜单类型-目录 + TYPE_DIR = "D" + // 菜单类型-菜单 + TYPE_MENU = "M" + // 菜单类型-按钮 + TYPE_BUTTON = "B" +) + +// 菜单内嵌地址标识-带/前缀 +const PATH_INLINE = "/inline" diff --git a/src/framework/constants/result/result.go b/src/framework/constants/result/result.go new file mode 100644 index 00000000..dbd86302 --- /dev/null +++ b/src/framework/constants/result/result.go @@ -0,0 +1,15 @@ +package result + +// 响应结果常量信息 + +const ( + // 响应-code错误失败 + CODE_ERROR = 0 + // 响应-msg错误失败 + MSG_ERROR = "error" + + // 响应-msg正常成功 + CODE_SUCCESS = 1 + // 响应-code正常成功 + MSG_SUCCESS = "success" +) diff --git a/src/framework/constants/roledatascope/roledatascope.go b/src/framework/constants/roledatascope/roledatascope.go new file mode 100644 index 00000000..52c6350e --- /dev/null +++ b/src/framework/constants/roledatascope/roledatascope.go @@ -0,0 +1,29 @@ +package roledatascope + +// 系统角色数据范围常量 + +const ( + // 全部数据权限 + ALL = "1" + + // 自定数据权限 + CUSTOM = "2" + + // 部门数据权限 + DEPT = "3" + + // 部门及以下数据权限 + DEPT_AND_CHILD = "4" + + // 仅本人数据权限 + SELF = "5" +) + +// 系统角色数据范围映射 +var RoleDataScope = map[string]string{ + ALL: "全部数据权限", + CUSTOM: "自定数据权限", + DEPT: "部门数据权限", + DEPT_AND_CHILD: "部门及以下数据权限", + SELF: "仅本人数据权限", +} diff --git a/src/framework/constants/token/token.go b/src/framework/constants/token/token.go new file mode 100644 index 00000000..5c71b086 --- /dev/null +++ b/src/framework/constants/token/token.go @@ -0,0 +1,21 @@ +package token + +// 令牌常量信息 + +// 令牌-数据响应字段 +const RESPONSE_FIELD = "access_token" + +// 令牌-请求头标识前缀 +const HEADER_PREFIX = "Bearer " + +// 令牌-请求头标识 +const HEADER_KEY = "Authorization" + +// 令牌-JWT唯一标识字段 +const JWT_UUID = "login_key" + +// 令牌-JWT标识用户主键字段 +const JWT_KEY = "user_id" + +// 令牌-JWT标识用户登录账号字段 +const JWT_NAME = "user_name" diff --git a/src/framework/constants/uploadsubpath/uploadsubpath.go b/src/framework/constants/uploadsubpath/uploadsubpath.go new file mode 100644 index 00000000..697e3718 --- /dev/null +++ b/src/framework/constants/uploadsubpath/uploadsubpath.go @@ -0,0 +1,37 @@ +package uploadsubpath + +// 文件上传-子路径类型常量 + +const ( + // 默认 + DEFAULT = "default" + + // 头像 + AVATART = "avatar" + + // 导入 + IMPORT = "import" + + // 导出 + EXPORT = "export" + + // 通用上传 + COMMON = "common" + + // 下载 + DOWNLOAD = "download" + + // 切片 + CHUNK = "chunk" +) + +// 子路径类型映射 +var UploadSubpath = map[string]string{ + DEFAULT: "默认", + AVATART: "头像", + IMPORT: "导入", + EXPORT: "导出", + COMMON: "通用上传", + DOWNLOAD: "下载", + CHUNK: "切片", +} diff --git a/src/framework/cron/cron.go b/src/framework/cron/cron.go new file mode 100644 index 00000000..ac14b05b --- /dev/null +++ b/src/framework/cron/cron.go @@ -0,0 +1,202 @@ +package cron + +import ( + "fmt" + "time" + + "github.com/robfig/cron/v3" +) + +// 定义内部调度任务实例 +var c *cron.Cron + +// 任务队列 +var queueMap map[string]Queue + +// StartCron 启动调度任务实例 +func StartCron() { + queueMap = make(map[string]Queue) + c = cron.New(cron.WithSeconds()) + c.Start() +} + +// StopCron 停止调度任务实例 +func StopCron() { + c.Stop() +} + +// CreateQueue 创建队列注册处理器 +func CreateQueue(name string, processor QueueProcessor) Queue { + queue := Queue{ + Name: name, + Processor: processor, + Job: &[]*QueueJob{}, + } + queueMap[name] = queue + return queue +} + +// GetQueue 通过名称获取队列实例 +func GetQueue(name string) Queue { + if v, ok := queueMap[name]; ok { + return v + } + return Queue{} +} + +// QueueNames 获取注册的队列名称 +func QueueNames() []string { + keys := make([]string, 0, len(queueMap)) + for k := range queueMap { + keys = append(keys, k) + } + return keys +} + +// Queue 任务队列 +type Queue struct { + Name string // 队列名 + Processor QueueProcessor + Job *[]*QueueJob +} + +// QueueProcessor 队列处理函数接口 +type QueueProcessor interface { + // Execute 实际执行函数 + Execute(data any) any +} + +// RunJob 运行任务,data是传入的数据 +func (q *Queue) RunJob(data any, opts JobOptions) int { + job := &QueueJob{ + Status: Waiting, + Data: data, + Opts: opts, + queueName: q.Name, + queueProcessor: &q.Processor, + } + + // 非重复任务立即执行 + if opts.Cron == "" { + // 获取执行的任务 + currentJob := job.GetJob(false) + if currentJob.Status == Active { + return Active + } + // 从切片 jobs 中删除指定索引位置的元素 + for i, v := range *q.Job { + if v.cid == 0 { + jobs := *q.Job + jobs = append(jobs[:i], jobs[i+1:]...) + *q.Job = jobs + break + } + } + go job.Run() + } else { + // 移除已存的任务ID + q.RemoveJob(opts.JobId) + // 添加新任务 + cid, err := c.AddJob(opts.Cron, job) + if err != nil { + newLog.Error(err, "err") + job.Status = Failed + } + job.cid = cid + } + + *q.Job = append(*q.Job, job) + newLog.Info("RunJob", job.cid, opts.JobId, job.Status) + return job.Status +} + +// RemoveJob 移除任务 +func (q *Queue) RemoveJob(jobId string) bool { + for i, v := range *q.Job { + if jobId == v.Opts.JobId { + newLog.Info("RemoveJob", v.cid, jobId, v.Status) + c.Remove(v.cid) + // 从切片 jobs 中删除指定索引位置的元素 + jobs := *q.Job + jobs = append(jobs[:i], jobs[i+1:]...) + *q.Job = jobs + return true + } + } + return false +} + +// Status 任务执行状态 +const ( + Waiting = iota + Active + Completed + Failed +) + +// JobOptions 任务参数信息 +type JobOptions struct { + JobId string // 执行任务编号 + Cron string // 重复任务cron表达式 +} + +// QueueJob 队列内部执行任务 +type QueueJob struct { + Status int // 任务执行状态 + Timestamp int64 // 执行时间 + Data any // 执行任务时传入的参数 + Opts JobOptions + + cid cron.EntryID // 执行ID + + queueName string //队列名 + queueProcessor *QueueProcessor +} + +// GetJob 获取当前执行任务 +func (job *QueueJob) GetJob(repeat bool) *QueueJob { + q := GetQueue(job.queueName) + for _, v := range *q.Job { + if repeat && v.Opts.JobId == job.Opts.JobId { + return v + } + if !repeat && v.cid == 0 { + return v + } + } + return job +} + +// Run 实现的接口函数 +func (s QueueJob) Run() { + // 检查当前任务 + job := s.GetJob(s.cid != 0) + + // Active 状态不执行 + if job.Status == Active { + return + } + + // panics 异常收集 + defer func() { + if r := recover(); r != nil { + err, ok := r.(error) + if !ok { + err = fmt.Errorf("%v", r) + } + job.Status = Failed + newLog.Error(err, "failed", job) + } + }() + + // 开始执行 + job.Status = Active + job.Timestamp = time.Now().UnixMilli() + newLog.Info("run", job.cid, job.Opts.JobId) + + // 获取队列处理器接口实现 + processor := *job.queueProcessor + result := processor.Execute(job.Data) + job.Status = Completed + newLog.Completed(result, "completed", job) +} diff --git a/src/framework/cron/cron_test.go b/src/framework/cron/cron_test.go new file mode 100644 index 00000000..cb8f7776 --- /dev/null +++ b/src/framework/cron/cron_test.go @@ -0,0 +1,179 @@ +package cron + +import ( + "testing" + "time" + + "ems.agt/src/framework/logger" +) + +// 参考文章: +// https://blog.csdn.net/zjbyough/article/details/113853582 +// https://mp.weixin.qq.com/s/Ak7RBv1NuS-VBeDNo8_fww +func init() { + StartCron() +} + +// 简单示例 队列任务处理 +var NewSimple = &Simple{} + +type Simple struct{} + +func (s *Simple) Execute(data any) any { + logger.Infof("执行=> %+v ", data) + // 实现任务处理逻辑 + return data +} + +func TestSimple(t *testing.T) { + + simple := CreateQueue("simple", NewSimple) + simple.RunJob(map[string]string{ + "ok": "ok", + "data": "data", + }, JobOptions{ + JobId: "101", + }) + + simpleC := CreateQueue("simple", NewSimple) + simpleC.RunJob(map[string]string{ + "corn": "*/5 * * * * *", + "id": "102", + }, JobOptions{ + JobId: "102", + Cron: "*/5 * * * * *", + }) + + // simpleC.RunJob(map[string]string{ + // "corn": "*/15 * * * * *", + // "id": "103", + // }, JobOptions{ + // JobId: "103", + // Cron: "*/15 * * * * *", + // }) + + // simpleC.RemoveJob("102") + + select {} +} + +// Foo 队列任务处理 +var NewFooProcessor = &FooProcessor{ + progress: 0, + count: 0, +} + +type FooProcessor struct { + progress int + count int +} + +func (s *FooProcessor) Execute(data any) any { + logger.Infof("执行 %d %d => %+v ", s.count, s.progress, data) + s.count++ + + // 实现任务处理逻辑 + i := 0 + s.progress = i + for i < 10 { + // 获取任务进度 + progress := s.progress + logger.Infof("data: %v => 任务进度:%d", data, progress) + // 延迟响应 + time.Sleep(time.Second * 2) + i++ + // 改变任务进度 + s.progress = i + } + return data +} + +func TestFoo(t *testing.T) { + + foo := CreateQueue("foo", NewFooProcessor) + foo.RunJob(map[string]string{ + "data": "2", + }, JobOptions{ + JobId: "2", + }) + + fooC := CreateQueue("foo", NewFooProcessor) + fooC.RunJob(map[string]string{ + "corn": "*/5 * * * * *", + }, JobOptions{ + JobId: "3", + Cron: "*/5 * * * * *", + }) + + select {} +} + +// Bar 队列任务处理 +var NewBarProcessor = &BarProcessor{ + progress: 0, + count: 0, +} + +type BarProcessor struct { + progress int + count int +} + +func (s *BarProcessor) Execute(data any) any { + logger.Infof("执行 %d %d => %+v ", s.count, s.progress, data) + s.count++ + + // 实现任务处理逻辑 + i := 0 + s.progress = i + for i < 5 { + // 获取任务进度 + progress := s.progress + logger.Infof("data: %v => 任务进度:%d", data, progress) + // 延迟响应 + time.Sleep(time.Second * 2) + // 程序中途执行错误 + if i == 3 { + // arr := [1]int{1} + // arr[i] = 3 + // fmt.Println(arr) + // return "i = 3" + panic("程序中途执行错误") + } + i++ + // 改变任务进度 + s.progress = i + } + + return data +} + +func TestBar(t *testing.T) { + + bar := CreateQueue("bar", NewBarProcessor) + bar.RunJob(map[string]string{ + "data": "wdf", + }, JobOptions{ + JobId: "81923", + }) + + barC := CreateQueue("bar", NewBarProcessor) + barC.RunJob(map[string]string{ + "corn": "*/5 * * * * *", + }, JobOptions{ + JobId: "789", + Cron: "*/5 * * * * *", + }) + + // barDB := CreateQueue("barDB", NewBarProcessor) + // barDB.RunJob(JobData{ + // SysJob: model.SysJob{ + // JobID: "9123", + // JobName: "测试任务", + // }, + // }, JobOptions{ + // JobId: "9123", + // }) + + select {} +} diff --git a/src/framework/cron/log.go b/src/framework/cron/log.go new file mode 100644 index 00000000..0657ab4d --- /dev/null +++ b/src/framework/cron/log.go @@ -0,0 +1,112 @@ +package cron + +import ( + "encoding/json" + "time" + + "ems.agt/src/framework/constants/common" + "ems.agt/src/modules/monitor/model" + "ems.agt/src/modules/monitor/repository" +) + +// 实例任务执行日志收集 +var newLog = cronlog{} + +// cronlog 任务执行日志收集 +type cronlog struct{} + +// Info 任务普通信息收集 +func (s cronlog) Info(msg string, keysAndValues ...any) { + // logger.Infof("Info msg: %v ====> kv: %v", msg, keysAndValues) + +} + +// Error 任务异常错误收集 +func (s cronlog) Error(err error, msg string, keysAndValues ...any) { + // logger.Errorf("Error: %v -> msg: %v ====> kv: %v", err, msg, keysAndValues) + // logger.Errorf("k0: %v", keysAndValues[0].(*QueueJob)) + + // 指定的错误收集 + if msg == "failed" { + // 任务对象 + job := keysAndValues[0].(*QueueJob) + + // 结果信息序列化字符串 + jsonByte, _ := json.Marshal(map[string]any{ + "name": "failed", + "message": err.Error(), + }) + jobMsg := string(jsonByte) + if len(jobMsg) > 500 { + jobMsg = jobMsg[:500] + } + + // 读取任务信息创建日志对象 + if data, ok := job.Data.(JobData); ok { + duration := time.Since(time.UnixMilli(job.Timestamp)) + sysJob := data.SysJob + if sysJob.JobID == job.Opts.JobId { + sysJobLog := model.SysJobLog{ + JobName: sysJob.JobName, + JobGroup: sysJob.JobGroup, + InvokeTarget: sysJob.InvokeTarget, + TargetParams: sysJob.TargetParams, + Status: common.STATUS_NO, + JobMsg: jobMsg, + CostTime: duration.Milliseconds(), + } + // 插入数据 + repository.NewSysJobLogImpl.InsertJobLog(sysJobLog) + } + } + } +} + +// Completed 任务完成return的结果收集 +func (s cronlog) Completed(result any, msg string, keysAndValues ...any) { + // logger.Infof("Completed: %v -> msg: %v ====> kv: %v", result, msg, keysAndValues) + // logger.Infof("k0: %v", keysAndValues[0].(*QueueJob)) + + // 指定的完成收集 + if msg == "completed" { + // 任务对象 + job := keysAndValues[0].(*QueueJob) + + // 结果信息序列化字符串 + jsonByte, _ := json.Marshal(map[string]any{ + "name": "completed", + "message": result, + }) + jobMsg := string(jsonByte) + if len(jobMsg) > 500 { + jobMsg = jobMsg[:500] + } + + // 读取任务信息创建日志对象 + if data, ok := job.Data.(JobData); ok { + duration := time.Since(time.UnixMilli(job.Timestamp)) + sysJob := data.SysJob + if sysJob.JobID == job.Opts.JobId { + sysJobLog := model.SysJobLog{ + JobName: sysJob.JobName, + JobGroup: sysJob.JobGroup, + InvokeTarget: sysJob.InvokeTarget, + TargetParams: sysJob.TargetParams, + Status: common.STATUS_YES, + JobMsg: jobMsg, + CostTime: duration.Milliseconds(), + } + // 插入数据 + repository.NewSysJobLogImpl.InsertJobLog(sysJobLog) + } + } + } +} + +// JobData 调度任务日志收集结构体,执行任务时传入的接收参数 +type JobData struct { + // 触发执行cron重复多次 + Repeat bool + // 定时任务调度表记录信息 + SysJob model.SysJob +} diff --git a/src/framework/datasource/datasource.go b/src/framework/datasource/datasource.go new file mode 100644 index 00000000..b4b63202 --- /dev/null +++ b/src/framework/datasource/datasource.go @@ -0,0 +1,161 @@ +package datasource + +import ( + "fmt" + "log" + "os" + "regexp" + "time" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/logger" + + "gorm.io/driver/mysql" + "gorm.io/gorm" + gormLog "gorm.io/gorm/logger" +) + +// 数据库连接实例 +var dbMap = make(map[string]*gorm.DB) + +type dialectInfo struct { + dialector gorm.Dialector + logging bool +} + +// 载入数据库连接 +func loadDialect() map[string]dialectInfo { + dialects := make(map[string]dialectInfo, 0) + + // 读取数据源配置 + datasource := config.Get("gorm.datasource").(map[string]any) + for key, value := range datasource { + item := value.(map[string]any) + // 数据库类型对应的数据库连接 + switch item["type"] { + case "mysql": + dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", + item["username"], + item["password"], + item["host"], + item["port"], + item["database"], + ) + dialects[key] = dialectInfo{ + dialector: mysql.Open(dsn), + logging: item["logging"].(bool), + } + default: + logger.Fatalf("%s: %v\n Not Load DB Config Type", key, item) + } + } + + return dialects +} + +// 载入连接日志配置 +func loadLogger() gormLog.Interface { + newLogger := gormLog.New( + log.New(os.Stdout, "[GORM] ", log.LstdFlags), // 将日志输出到控制台 + gormLog.Config{ + SlowThreshold: time.Second, // Slow SQL 阈值 + LogLevel: gormLog.Info, // 日志级别 Silent不输出任何日志 + ParameterizedQueries: false, // 参数化查询SQL 用实际值带入?的执行语句 + Colorful: false, // 彩色日志输出 + }, + ) + return newLogger +} + +// 连接数据库实例 +func Connect() { + // 遍历进行连接数据库实例 + for key, info := range loadDialect() { + opts := &gorm.Config{} + // 是否需要日志输出 + if info.logging { + opts.Logger = loadLogger() + } + // 创建连接 + db, err := gorm.Open(info.dialector, opts) + if err != nil { + logger.Fatalf("failed error db connect: %s", err) + } + // 获取底层 SQL 数据库连接 + sqlDB, err := db.DB() + if err != nil { + logger.Fatalf("failed error underlying SQL database: %v", err) + } + // 测试数据库连接 + err = sqlDB.Ping() + if err != nil { + logger.Fatalf("failed error ping database: %v", err) + } + logger.Infof("database %s connection is successful.", key) + dbMap[key] = db + } +} + +// 关闭数据库实例 +func Close() { + for _, db := range dbMap { + sqlDB, err := db.DB() + if err != nil { + continue + } + if err := sqlDB.Close(); err != nil { + logger.Errorf("fatal error db close: %s", err) + } + } +} + +// 获取默认数据源 +func DefaultDB() *gorm.DB { + source := config.Get("gorm.defaultDataSourceName").(string) + return dbMap[source] +} + +// 获取数据源 +func DB(source string) *gorm.DB { + return dbMap[source] +} + +// RawDB 原生查询语句 +func RawDB(source string, sql string, parameters []any) ([]map[string]any, error) { + // 数据源 + db := DefaultDB() + if source != "" { + db = DB(source) + } + + // 使用正则表达式替换连续的空白字符为单个空格 + fmtSql := regexp.MustCompile(`\s+`).ReplaceAllString(sql, " ") + + // logger.Infof("sql=> %v", fmtSql) + // logger.Infof("parameters=> %v", parameters) + + // 查询结果 + var rows []map[string]any + res := db.Raw(fmtSql, parameters...).Scan(&rows) + if res.Error != nil { + return nil, res.Error + } + return rows, nil +} + +// ExecDB 原生执行语句 +func ExecDB(source string, sql string, parameters []any) (int64, error) { + // 数据源 + db := DefaultDB() + if source != "" { + db = DB(source) + } + // 使用正则表达式替换连续的空白字符为单个空格 + fmtSql := regexp.MustCompile(`\s+`).ReplaceAllString(sql, " ") + // 执行结果 + res := db.Exec(fmtSql, parameters...) + if res.Error != nil { + return 0, res.Error + } + return res.RowsAffected, nil +} diff --git a/src/framework/errorcatch/errorcatch.go b/src/framework/errorcatch/errorcatch.go new file mode 100644 index 00000000..baa42585 --- /dev/null +++ b/src/framework/errorcatch/errorcatch.go @@ -0,0 +1,40 @@ +package errorcatch + +import ( + "fmt" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/vo/result" + + "github.com/gin-gonic/gin" +) + +// ErrorCatch 全局异常捕获 +func ErrorCatch() gin.HandlerFunc { + return func(c *gin.Context) { + defer func() { + // 在这里处理 Panic 异常,例如记录日志或返回错误信息给客户端 + if err := recover(); err != nil { + logger.Errorf("发生了 Panic 异常: %v", err) + + // 返回错误响应给客户端 + if config.Env() == "prod" { + c.JSON(500, result.ErrMsg("服务器内部错误")) + } else { + // 通过实现 error 接口的 Error() 方法自定义错误类型进行捕获 + switch v := err.(type) { + case error: + c.JSON(500, result.ErrMsg(v.Error())) + default: + c.JSON(500, result.ErrMsg(fmt.Sprint(err))) + } + } + + c.Abort() // 停止执行后续的处理函数 + } + }() + + c.Next() + } +} diff --git a/src/framework/logger/logger.go b/src/framework/logger/logger.go new file mode 100644 index 00000000..543252c7 --- /dev/null +++ b/src/framework/logger/logger.go @@ -0,0 +1,49 @@ +package logger + +import ( + "log" + + "github.com/spf13/viper" +) + +var logWriter *Logger + +// 初始程序日志 +func InitLogger() { + env := viper.GetString("env") + conf := viper.GetStringMap("logger") + fileDir := conf["filedir"].(string) + fileName := conf["filename"].(string) + level := conf["level"].(int) + maxDay := conf["maxday"].(int) + maxSize := conf["maxsize"].(int) + + newLog, err := NewLogger(env, fileDir, fileName, level, maxDay, maxSize) + if err != nil { + log.Fatalf("failed to initialize logger: %v", err) + } + + logWriter = newLog +} + +// 关闭程序日志写入 +func Close() { + logWriter.Close() +} + +func Infof(format string, v ...any) { + logWriter.Infof(format, v...) +} + +func Warnf(format string, v ...any) { + logWriter.Warnf(format, v...) +} + +func Errorf(format string, v ...any) { + logWriter.Errorf(format, v...) +} + +// Fatalf 抛出错误并退出程序 +func Fatalf(format string, v ...any) { + log.Fatalf(format, v...) +} diff --git a/src/framework/logger/writer.go b/src/framework/logger/writer.go new file mode 100644 index 00000000..70fca2c5 --- /dev/null +++ b/src/framework/logger/writer.go @@ -0,0 +1,190 @@ +package logger + +import ( + "fmt" + "io" + "log" + "os" + "path/filepath" + "strings" + "time" +) + +// 日志器对象 +type Logger struct { + env string // 运行环境 + filePath string // 文件路径 + fileName string // 文件名 + level int // 日志等级标识 + maxDay int // 保留最长天数 + maxSize int64 // 文件最大空间 + fileHandle *os.File // 文件实例 + logger *log.Logger // 日志实例 + logLevelMap map[int]string // 日志等级标识名 + logDay int // 日志当前日 +} + +const ( + LOG_LEVEL_SILENT = iota + LOG_LEVEL_INFO + LOG_LEVEL_WARN + LOG_LEVEL_ERROR +) + +// NewLogger 实例日志器对象 +func NewLogger(env, fileDir, fileName string, level, maxDay, maxSize int) (*Logger, error) { + logFilePath := filepath.Join(fileDir, fileName) + if err := os.MkdirAll(filepath.Dir(logFilePath), 0750); err != nil { + return nil, fmt.Errorf("failed to mkdir logger dir: %v", err) + } + fileHandle, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + return nil, fmt.Errorf("failed to open log file: %v", err) + } + + writer := io.Writer(fileHandle) + if env == "local" { + writer = io.MultiWriter(fileHandle, os.Stderr) + } + + logger := log.New(writer, "", log.LstdFlags|log.Lshortfile) + + logLevelMap := map[int]string{ + LOG_LEVEL_INFO: "INFO", + LOG_LEVEL_WARN: "WARN", + LOG_LEVEL_ERROR: "ERROR", + } + + stdLogger := &Logger{ + env: env, + filePath: fileDir, + fileName: fileName, + level: level, + maxDay: maxDay, + maxSize: int64(maxSize * 1024 * 1024), + fileHandle: fileHandle, + logger: logger, + logLevelMap: logLevelMap, + logDay: time.Now().Day(), + } + + go stdLogger.checkFile() + + return stdLogger, nil +} + +// checkFile 检查文件分割,自定时调用 +func (l *Logger) checkFile() { + fileInfo, err := l.fileHandle.Stat() + if err != nil { + l.logger.Printf("Failed to get log file info: %v\n", err) + return + } + + currTime := time.Now() + if l.logDay != currTime.Day() { + l.logDay = currTime.Day() + l.rotateFile(currTime.AddDate(0, 0, -1).Format("2006_01_02")) + // 移除超过保存最长天数的文件 + l.removeOldFile(currTime.AddDate(0, 0, -l.maxDay)) + } else if fileInfo.Size() >= l.maxSize { + l.rotateFile(currTime.Format("2006_01_02_150405")) + } else if time.Since(fileInfo.ModTime()).Hours() > 24 { + l.rotateFile(fileInfo.ModTime().Format("2006_01_02")) + } + + time.AfterFunc(1*time.Minute, l.checkFile) +} + +// rotateFile 检查文件大小进行分割 +func (l *Logger) rotateFile(timeFormat string) { + l.fileHandle.Close() + + newFileName := fmt.Sprintf("%s.%s", l.fileName, timeFormat) + newFilePath := filepath.Join(l.filePath, newFileName) + oldfilePath := filepath.Join(l.filePath, l.fileName) + + // 重命名 + os.Rename(oldfilePath, newFilePath) + + // 新文件句柄 + fileHandle, err := os.OpenFile(oldfilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + l.logger.Printf("Failed to open log file: %v\n", err) + return + } + + l.fileHandle = fileHandle + + // 重新设置 logger 的 writer + writer := io.Writer(l.fileHandle) + if l.env == "local" { + writer = io.MultiWriter(l.fileHandle, os.Stderr) + } + l.logger.SetOutput(writer) +} + +// RemoveOldFile 删除旧文件 +func (l *Logger) removeOldFile(oldFileDate time.Time) { + // 遍历目标文件夹中的文件 + files, err := os.ReadDir(l.filePath) + if err != nil { + l.Errorf("logger RemoveOldFile ReadDir err: %v", err.Error()) + return + } + + for _, file := range files { + idx := strings.LastIndex(file.Name(), ".log.") + if idx == -1 { + continue + } + dateStr := file.Name()[idx+5 : idx+15] + + // 解析日期字符串 + fileDate, err := time.Parse("2006_01_02", dateStr) + if err != nil { + l.Errorf("logger RemoveOldFile Parse err: %v", err.Error()) + continue + } + + // 判断文件日期是否在给定日期之前 + if fileDate.Before(oldFileDate) { + // 删除旧文件 + err := os.Remove(filepath.Join(l.filePath, file.Name())) + if err != nil { + l.Errorf("logger RemoveOldFile Remove err: %v", err.Error()) + continue + } + } + } +} + +// writeLog 写入chan +func (l *Logger) writeLog(level int, format string, args ...interface{}) { + if level < l.level { + return + } + + logMsg := fmt.Sprintf("[%s] %s\n", l.logLevelMap[level], fmt.Sprintf(format, args...)) + l.logger.Output(4, logMsg) +} + +func (l *Logger) Infof(format string, args ...interface{}) { + l.writeLog(LOG_LEVEL_INFO, format, args...) +} + +func (l *Logger) Warnf(format string, args ...interface{}) { + l.writeLog(LOG_LEVEL_WARN, format, args...) +} + +func (l *Logger) Errorf(format string, args ...interface{}) { + l.writeLog(LOG_LEVEL_ERROR, format, args...) +} + +// Close 日志关闭 +func (l *Logger) Close() { + err := l.fileHandle.Close() + if err != nil { + l.logger.Printf("Failed to close log file: %v\n", err) + } +} diff --git a/src/framework/middleware/collectlogs/operate_log.go b/src/framework/middleware/collectlogs/operate_log.go new file mode 100644 index 00000000..3c22e463 --- /dev/null +++ b/src/framework/middleware/collectlogs/operate_log.go @@ -0,0 +1,182 @@ +package collectlogs + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" +) + +const ( + // 业务操作类型-其它 + BUSINESS_TYPE_OTHER = "0" + + // 业务操作类型-新增 + BUSINESS_TYPE_INSERT = "1" + + // 业务操作类型-修改 + BUSINESS_TYPE_UPDATE = "2" + + // 业务操作类型-删除 + BUSINESS_TYPE_DELETE = "3" + + // 业务操作类型-授权 + BUSINESS_TYPE_GRANT = "4" + + // 业务操作类型-导出 + BUSINESS_TYPE_EXPORT = "5" + + // 业务操作类型-导入 + BUSINESS_TYPE_IMPORT = "6" + + // 业务操作类型-强退 + BUSINESS_TYPE_FORCE = "7" + + // 业务操作类型-清空数据 + BUSINESS_TYPE_CLEAN = "8" +) + +const ( + // 操作人类别-其它 + OPERATOR_TYPE_OTHER = "0" + + // 操作人类别-后台用户 + OPERATOR_TYPE_MANAGE = "1" + + // 操作人类别-手机端用户 + OPERATOR_TYPE_MOBILE = "2" +) + +// Option 操作日志参数 +type Options struct { + Title string `json:"title"` // 标题 + BusinessType string `json:"businessType"` // 类型,默认常量 BUSINESS_TYPE_OTHER + OperatorType string `json:"operatorType"` // 操作人类别,默认常量 OPERATOR_TYPE_OTHER + IsSaveRequestData bool `json:"isSaveRequestData"` // 是否保存请求的参数 + IsSaveResponseData bool `json:"isSaveResponseData"` // 是否保存响应的参数 +} + +// OptionNew 操作日志参数默认值 +// +// 标题 "title":"--" +// +// 类型 "businessType": BUSINESS_TYPE_OTHER +// +// 注意之后JSON反序列使用:c.ShouldBindBodyWith(¶ms, binding.JSON) +func OptionNew(title, businessType string) Options { + return Options{ + Title: title, + BusinessType: businessType, + OperatorType: OPERATOR_TYPE_OTHER, + IsSaveRequestData: true, + IsSaveResponseData: true, + } +} + +// 敏感属性字段进行掩码 +var maskProperties []string = []string{ + "password", + "oldPassword", + "newPassword", + "confirmPassword", +} + +// OperateLog 访问操作日志记录 +// +// 请在用户身份授权认证校验后使用以便获取登录用户信息 +func OperateLog(options Options) gin.HandlerFunc { + return func(c *gin.Context) { + c.Set("startTime", time.Now()) + + // 函数名 + funcName := c.HandlerName() + lastDotIndex := strings.LastIndex(funcName, "/") + funcName = funcName[lastDotIndex+1:] + + // 解析ip地址 + ipaddr, location := ctx.IPAddrLocation(c) + + // 获取登录用户信息 + loginUser, err := ctx.LoginUser(c) + if err != nil { + c.JSON(401, result.CodeMsg(401, "无效身份授权")) + c.Abort() // 停止执行后续的处理函数 + return + } + + // 操作日志记录 + operLog := model.SysLogOperate{ + Title: options.Title, + BusinessType: options.BusinessType, + OperatorType: options.OperatorType, + Method: funcName, + OperURL: c.Request.RequestURI, + RequestMethod: c.Request.Method, + OperIP: ipaddr, + OperLocation: location, + OperName: loginUser.User.UserName, + DeptName: loginUser.User.Dept.DeptName, + } + + if loginUser.User.UserType == "sys" { + operLog.OperatorType = OPERATOR_TYPE_MANAGE + } + + // 是否需要保存request,参数和值 + if options.IsSaveRequestData { + params := ctx.RequestParamsMap(c) + for k, v := range params { + // 敏感属性字段进行掩码 + for _, s := range maskProperties { + if s == k { + params[k] = parse.SafeContent(v.(string)) + break + } + } + } + jsonStr, _ := json.Marshal(params) + paramsStr := string(jsonStr) + if len(paramsStr) > 2000 { + paramsStr = paramsStr[:2000] + } + operLog.OperParam = paramsStr + } + + // 调用下一个处理程序 + c.Next() + + // 响应状态 + status := c.Writer.Status() + if status == 200 { + operLog.Status = common.STATUS_YES + } else { + operLog.Status = common.STATUS_NO + } + + // 是否需要保存response,参数和值 + if options.IsSaveResponseData { + contentDisposition := c.Writer.Header().Get("Content-Disposition") + contentType := c.Writer.Header().Get("Content-Type") + content := contentType + contentDisposition + msg := fmt.Sprintf(`{"status":"%d","size":"%d","content-type":"%s"}`, status, c.Writer.Size(), content) + operLog.OperMsg = msg + } + + // 日志记录时间 + duration := time.Since(c.GetTime("startTime")) + operLog.CostTime = duration.Milliseconds() + operLog.OperTime = time.Now().UnixMilli() + + // 保存操作记录到数据库 + service.NewSysLogOperateImpl.InsertSysLogOperate(operLog) + } +} diff --git a/src/framework/middleware/cors.go b/src/framework/middleware/cors.go new file mode 100644 index 00000000..12a15a2c --- /dev/null +++ b/src/framework/middleware/cors.go @@ -0,0 +1,83 @@ +package middleware + +import ( + "fmt" + "strings" + + "ems.agt/src/framework/config" + + "github.com/gin-gonic/gin" +) + +// Cors 跨域 +func Cors() gin.HandlerFunc { + return func(c *gin.Context) { + // 设置Vary头部 + c.Header("Vary", "Origin") + c.Header("Keep-Alive", "timeout=5") + + requestOrigin := c.GetHeader("Origin") + if requestOrigin == "" { + c.Next() + return + } + + origin := requestOrigin + if v := config.Get("cors.origin"); v != nil { + origin = v.(string) + } + c.Header("Access-Control-Allow-Origin", origin) + + if v := config.Get("cors.credentials"); v != nil && v.(bool) { + c.Header("Access-Control-Allow-Credentials", "true") + } + + // OPTIONS + if method := c.Request.Method; method == "OPTIONS" { + requestMethod := c.GetHeader("Access-Control-Request-Method") + if requestMethod == "" { + c.Next() + return + } + + // 响应最大时间值 + if v := config.Get("cors.maxAge"); v != nil && v.(int) > 10000 { + c.Header("Access-Control-Max-Age", fmt.Sprint(v)) + } + + // 允许方法 + if v := config.Get("cors.allowMethods"); v != nil { + var allowMethods = make([]string, 0) + for _, s := range v.([]any) { + allowMethods = append(allowMethods, s.(string)) + } + c.Header("Access-Control-Allow-Methods", strings.Join(allowMethods, ",")) + } else { + c.Header("Access-Control-Allow-Methods", "GET,HEAD,PUT,POST,DELETE,PATCH") + } + + // 允许请求头 + if v := config.Get("cors.allowHeaders"); v != nil { + var allowHeaders = make([]string, 0) + for _, s := range v.([]any) { + allowHeaders = append(allowHeaders, s.(string)) + } + c.Header("Access-Control-Allow-Headers", strings.Join(allowHeaders, ",")) + } + + c.AbortWithStatus(204) + return + } + + // 暴露请求头 + if v := config.Get("cors.exposeHeaders"); v != nil { + var exposeHeaders = make([]string, 0) + for _, s := range v.([]any) { + exposeHeaders = append(exposeHeaders, s.(string)) + } + c.Header("Access-Control-Expose-Headers", strings.Join(exposeHeaders, ",")) + } + + c.Next() + } +} diff --git a/src/framework/middleware/pre_authorize.go b/src/framework/middleware/pre_authorize.go new file mode 100644 index 00000000..3fd89883 --- /dev/null +++ b/src/framework/middleware/pre_authorize.go @@ -0,0 +1,180 @@ +package middleware + +import ( + "fmt" + + AdminConstants "ems.agt/src/framework/constants/admin" + commonConstants "ems.agt/src/framework/constants/common" + ctxUtils "ems.agt/src/framework/utils/ctx" + tokenUtils "ems.agt/src/framework/utils/token" + "ems.agt/src/framework/vo/result" + + "github.com/gin-gonic/gin" +) + +// PreAuthorize 用户身份授权认证校验 +// +// 只需含有其中角色 "hasRoles": {"xxx"}, +// +// 只需含有其中权限 "hasPerms": {"xxx"}, +// +// 同时匹配其中角色 "matchRoles": {"xxx"}, +// +// 同时匹配其中权限 "matchPerms": {"xxx"}, +func PreAuthorize(options map[string][]string) gin.HandlerFunc { + return func(c *gin.Context) { + // 获取请求头标识信息 + tokenStr := ctxUtils.Authorization(c) + if tokenStr == "" { + c.JSON(401, result.CodeMsg(401, "无效身份授权")) + c.Abort() // 停止执行后续的处理函数 + return + } + + // 验证令牌 + claims, err := tokenUtils.Verify(tokenStr) + if err != nil { + c.JSON(401, result.CodeMsg(401, err.Error())) + c.Abort() // 停止执行后续的处理函数 + return + } + + // 获取缓存的用户信息 + loginUser := tokenUtils.LoginUser(claims) + if loginUser.UserID == "" { + c.JSON(401, result.CodeMsg(401, "无效身份授权")) + c.Abort() // 停止执行后续的处理函数 + return + } + + // 检查刷新有效期后存入上下文 + tokenUtils.RefreshIn(&loginUser) + c.Set(commonConstants.CTX_LOGIN_USER, loginUser) + + // 登录用户角色权限校验 + if options != nil { + var roles []string + for _, item := range loginUser.User.Roles { + roles = append(roles, item.RoleKey) + } + perms := loginUser.Permissions + verifyOk := verifyRolePermission(roles, perms, options) + if !verifyOk { + msg := fmt.Sprintf("无权访问 %s %s", c.Request.Method, c.Request.RequestURI) + c.JSON(403, result.CodeMsg(403, msg)) + c.Abort() // 停止执行后续的处理函数 + return + } + } + + // 调用下一个处理程序 + c.Next() + } +} + +// verifyRolePermission 校验角色权限是否满足 +// +// roles 角色字符数组 +// +// perms 权限字符数组 +// +// options 参数 +func verifyRolePermission(roles, perms []string, options map[string][]string) bool { + // 直接放行 管理员角色或任意权限 + if contains(roles, AdminConstants.ROLE_KEY) || contains(perms, AdminConstants.PERMISSION) { + return true + } + opts := make([]bool, 4) + + // 只需含有其中角色 + hasRole := false + if arr, ok := options["hasRoles"]; ok && len(arr) > 0 { + hasRole = some(roles, arr) + opts[0] = true + } + + // 只需含有其中权限 + hasPerms := false + if arr, ok := options["hasPerms"]; ok && len(arr) > 0 { + hasPerms = some(perms, arr) + opts[1] = true + } + + // 同时匹配其中角色 + matchRoles := false + if arr, ok := options["matchRoles"]; ok && len(arr) > 0 { + matchRoles = every(roles, arr) + opts[2] = true + } + + // 同时匹配其中权限 + matchPerms := false + if arr, ok := options["matchPerms"]; ok && len(arr) > 0 { + matchPerms = every(perms, arr) + opts[3] = true + } + + // 同时判断 含有其中 + if opts[0] && opts[1] { + return hasRole || hasPerms + } + // 同时判断 匹配其中 + if opts[2] && opts[3] { + return matchRoles && matchPerms + } + // 同时判断 含有其中且匹配其中 + if opts[0] && opts[3] { + return hasRole && matchPerms + } + if opts[1] && opts[2] { + return hasPerms && matchRoles + } + + return hasRole || hasPerms || matchRoles || matchPerms +} + +// contains 检查字符串数组中是否包含指定的字符串 +func contains(arr []string, target string) bool { + for _, str := range arr { + if str == target { + return true + } + } + return false +} + +// some 检查字符串数组中含有其中一项 +func some(origin []string, target []string) bool { + has := false + for _, t := range target { + for _, o := range origin { + if t == o { + has = true + break + } + } + if has { + break + } + } + return has +} + +// every 检查字符串数组中同时包含所有项 +func every(origin []string, target []string) bool { + match := true + for _, t := range target { + found := false + for _, o := range origin { + if t == o { + found = true + break + } + } + if !found { + match = false + break + } + } + return match +} diff --git a/src/framework/middleware/rate_limit.go b/src/framework/middleware/rate_limit.go new file mode 100644 index 00000000..5dbf53d9 --- /dev/null +++ b/src/framework/middleware/rate_limit.go @@ -0,0 +1,101 @@ +package middleware + +import ( + "fmt" + "strings" + "time" + + "ems.agt/src/framework/constants/cachekey" + "ems.agt/src/framework/redis" + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/ip2region" + "ems.agt/src/framework/vo/result" + + "github.com/gin-gonic/gin" +) + +const ( + // 默认策略全局限流 + LIMIT_GLOBAL = 1 + + // 根据请求者IP进行限流 + LIMIT_IP = 2 + + // 根据用户ID进行限流 + LIMIT_USER = 3 +) + +// LimitOption 请求限流参数 +type LimitOption struct { + Time int64 `json:"time"` // 限流时间,单位秒 + Count int64 `json:"count"` // 限流次数 + Type int64 `json:"type"` // 限流条件类型,默认LIMIT_GLOBAL +} + +// RateLimit 请求限流 +// +// 示例参数:middleware.LimitOption{ Time: 5, Count: 10, Type: middleware.LIMIT_IP } +// +// 参数表示:5秒内,最多请求10次,限制类型为 IP +// +// 使用 USER 时,请在用户身份授权认证校验后使用 +// 以便获取登录用户信息,无用户信息时默认为 GLOBAL +func RateLimit(option LimitOption) gin.HandlerFunc { + return func(c *gin.Context) { + // 初始可选参数数据 + if option.Time < 5 { + option.Time = 5 + } + if option.Count < 10 { + option.Count = 10 + } + if option.Type == 0 { + option.Type = LIMIT_GLOBAL + } + + // 获取执行函数名称 + funcName := c.HandlerName() + lastDotIndex := strings.LastIndex(funcName, "/") + funcName = funcName[lastDotIndex+1:] + // 生成限流key + var limitKey string = cachekey.RATE_LIMIT_KEY + funcName + + // 用户 + if option.Type == LIMIT_USER { + loginUser, err := ctx.LoginUser(c) + if err != nil { + c.JSON(401, result.Err(map[string]any{ + "code": 401, + "msg": err.Error(), + })) + c.Abort() // 停止执行后续的处理函数 + return + } + limitKey = cachekey.RATE_LIMIT_KEY + loginUser.UserID + ":" + funcName + } + + // IP + if option.Type == LIMIT_IP { + clientIP := ip2region.ClientIP(c.ClientIP()) + limitKey = cachekey.RATE_LIMIT_KEY + clientIP + ":" + funcName + } + + // 在Redis查询并记录请求次数 + rateCount, _ := redis.RateLimit("", limitKey, option.Time, option.Count) + rateTime, _ := redis.GetExpire("", limitKey) + + // 设置响应头中的限流声明字段 + c.Header("X-RateLimit-Limit", fmt.Sprintf("%d", option.Count)) // 总请求数限制 + c.Header("X-RateLimit-Remaining", fmt.Sprintf("%d", option.Count-rateCount)) // 剩余可用请求数 + c.Header("X-RateLimit-Reset", fmt.Sprintf("%d", time.Now().Unix()+int64(rateTime))) // 重置时间戳 + + if rateCount >= option.Count { + c.JSON(200, result.ErrMsg("访问过于频繁,请稍候再试")) + c.Abort() // 停止执行后续的处理函数 + return + } + + // 调用下一个处理程序 + c.Next() + } +} diff --git a/src/framework/middleware/repeat/repeat.go b/src/framework/middleware/repeat/repeat.go new file mode 100644 index 00000000..0a844633 --- /dev/null +++ b/src/framework/middleware/repeat/repeat.go @@ -0,0 +1,84 @@ +package repeat + +import ( + "encoding/json" + "strconv" + "time" + + "ems.agt/src/framework/constants/cachekey" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/redis" + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/ip2region" + "ems.agt/src/framework/vo/result" + + "github.com/gin-gonic/gin" +) + +// repeatParam 重复提交参数的类型定义 +type repeatParam struct { + Time int64 `json:"time"` + Params string `json:"params"` +} + +// RepeatSubmit 防止表单重复提交,小于间隔时间视为重复提交 +// +// 间隔时间(单位秒) 默认:5 +// +// 注意之后JSON反序列使用:c.ShouldBindBodyWith(¶ms, binding.JSON) +func RepeatSubmit(interval int64) gin.HandlerFunc { + return func(c *gin.Context) { + if interval < 5 { + interval = 5 + } + + // 提交参数 + params := ctx.RequestParamsMap(c) + paramsJSONByte, err := json.Marshal(params) + if err != nil { + logger.Errorf("RepeatSubmit params json marshal err: %v", err) + } + paramsJSONStr := string(paramsJSONByte) + + // 唯一标识(指定key + 客户端IP + 请求地址) + clientIP := ip2region.ClientIP(c.ClientIP()) + repeatKey := cachekey.REPEAT_SUBMIT_KEY + clientIP + ":" + c.Request.RequestURI + + // 在Redis查询并记录请求次数 + repeatStr, _ := redis.Get("", repeatKey) + if repeatStr != "" { + var rp repeatParam + err := json.Unmarshal([]byte(repeatStr), &rp) + if err != nil { + logger.Errorf("RepeatSubmit repeatStr json unmarshal err: %v", err) + } + compareTime := time.Now().Unix() - rp.Time + compareParams := rp.Params == paramsJSONStr + + // 设置重复提交声明响应头(毫秒) + c.Header("X-RepeatSubmit-Rest", strconv.FormatInt(time.Now().Add(time.Duration(compareTime)*time.Second).UnixNano()/int64(time.Millisecond), 10)) + + // 小于间隔时间且参数内容一致 + if compareTime < interval && compareParams { + c.JSON(200, result.ErrMsg("不允许重复提交,请稍候再试")) + c.Abort() + return + } + } + + // 当前请求参数 + rp := repeatParam{ + Time: time.Now().Unix(), + Params: paramsJSONStr, + } + rpJSON, err := json.Marshal(rp) + if err != nil { + logger.Errorf("RepeatSubmit rp json marshal err: %v", err) + } + // 保存请求时间和参数 + redis.SetByExpire("", repeatKey, string(rpJSON), time.Duration(interval)*time.Second) + + // 调用下一个处理程序 + c.Next() + } +} diff --git a/src/framework/middleware/report.go b/src/framework/middleware/report.go new file mode 100644 index 00000000..74b3a5eb --- /dev/null +++ b/src/framework/middleware/report.go @@ -0,0 +1,23 @@ +package middleware + +import ( + "time" + + "ems.agt/src/framework/logger" + + "github.com/gin-gonic/gin" +) + +// Report 请求响应日志 +func Report() gin.HandlerFunc { + return func(c *gin.Context) { + start := time.Now() + + // 调用下一个处理程序 + c.Next() + + // 计算请求处理时间,并打印日志 + duration := time.Since(start) + logger.Infof("%s %s report end=> %v", c.Request.Method, c.Request.RequestURI, duration) + } +} diff --git a/src/framework/middleware/security/csp.go b/src/framework/middleware/security/csp.go new file mode 100644 index 00000000..3d76c81d --- /dev/null +++ b/src/framework/middleware/security/csp.go @@ -0,0 +1,22 @@ +package security + +import ( + "ems.agt/src/framework/config" + "ems.agt/src/framework/utils/generate" + + "github.com/gin-gonic/gin" +) + +// TODO +// csp 这将帮助防止跨站脚本攻击(XSS)。 +// HTTP 响应头 Content-Security-Policy 允许站点管理者控制指定的页面加载哪些资源。 +func csp(c *gin.Context) { + enable := false + if v := config.Get("security.csp.enable"); v != nil { + enable = v.(bool) + } + + if enable { + c.Header("x-csp-nonce", generate.Code(8)) + } +} diff --git a/src/framework/middleware/security/hsts.go b/src/framework/middleware/security/hsts.go new file mode 100644 index 00000000..4feb37c4 --- /dev/null +++ b/src/framework/middleware/security/hsts.go @@ -0,0 +1,37 @@ +package security + +import ( + "fmt" + + "ems.agt/src/framework/config" + + "github.com/gin-gonic/gin" +) + +// hsts 是一个安全功能 HTTP Strict Transport Security(通常简称为 HSTS ) +// 它告诉浏览器只能通过 HTTPS 访问当前资源,而不是 HTTP。 +func hsts(c *gin.Context) { + enable := false + if v := config.Get("security.hsts.enable"); v != nil { + enable = v.(bool) + } + + maxAge := 365 * 24 * 3600 + if v := config.Get("security.hsts.maxAge"); v != nil { + maxAge = v.(int) + } + + includeSubdomains := false + if v := config.Get("security.hsts.includeSubdomains"); v != nil { + includeSubdomains = v.(bool) + } + + str := fmt.Sprintf("max-age=%d", maxAge) + if includeSubdomains { + str += "; includeSubdomains" + } + + if enable { + c.Header("strict-transport-security", str) + } +} diff --git a/src/framework/middleware/security/noopen.go b/src/framework/middleware/security/noopen.go new file mode 100644 index 00000000..e0b5a768 --- /dev/null +++ b/src/framework/middleware/security/noopen.go @@ -0,0 +1,20 @@ +package security + +import ( + "ems.agt/src/framework/config" + + "github.com/gin-gonic/gin" +) + +// noopen 用于指定 IE 8 以上版本的用户不打开文件而直接保存文件。 +// 在下载对话框中不显式“打开”选项。 +func noopen(c *gin.Context) { + enable := false + if v := config.Get("security.noopen.enable"); v != nil { + enable = v.(bool) + } + + if enable { + c.Header("x-download-options", "noopen") + } +} diff --git a/src/framework/middleware/security/nosniff.go b/src/framework/middleware/security/nosniff.go new file mode 100644 index 00000000..f8ca6aaa --- /dev/null +++ b/src/framework/middleware/security/nosniff.go @@ -0,0 +1,26 @@ +package security + +import ( + "ems.agt/src/framework/config" + + "github.com/gin-gonic/gin" +) + +// nosniff 用于防止 XSS 等跨站脚本攻击 +// 如果从 script 或 stylesheet 读入的文件的 MIME 类型与指定 MIME 类型不匹配,不允许读取该文件。 +func nosniff(c *gin.Context) { + // 排除状态码范围 + status := c.Writer.Status() + if status >= 300 && status <= 308 { + return + } + + enable := false + if v := config.Get("security.nosniff.enable"); v != nil { + enable = v.(bool) + } + + if enable { + c.Header("x-content-type-options", "nosniff") + } +} diff --git a/src/framework/middleware/security/referer.go b/src/framework/middleware/security/referer.go new file mode 100644 index 00000000..f4e4ba2b --- /dev/null +++ b/src/framework/middleware/security/referer.go @@ -0,0 +1,76 @@ +package security + +import ( + "net/url" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/vo/result" + + "github.com/gin-gonic/gin" +) + +// referer 配置 referer 的 host 部分 +func referer(c *gin.Context) { + enable := false + if v := config.Get("security.csrf.enable"); v != nil { + enable = v.(bool) + } + if !enable { + return + } + + // csrf 校验类型 + okType := false + if v := config.Get("security.csrf.type"); v != nil { + vType := v.(string) + if vType == "all" || vType == "any" || vType == "referer" { + okType = true + } + } + if !okType { + return + } + + // 忽略请求方法 + method := c.Request.Method + ignoreMethods := []string{"GET", "HEAD", "OPTIONS", "TRACE"} + for _, ignore := range ignoreMethods { + if ignore == method { + return + } + } + + referer := c.GetHeader("Referer") + if referer == "" { + c.AbortWithStatusJSON(200, result.ErrMsg("无效 Referer 未知")) + return + } + + // 获取host + u, err := url.Parse(referer) + if err != nil { + c.AbortWithStatusJSON(200, result.ErrMsg("无效 Referer 未知")) + return + } + host := u.Host + + // 允许的来源白名单 + refererWhiteList := make([]string, 0) + if v := config.Get("security.csrf.refererWhiteList"); v != nil { + for _, s := range v.([]any) { + refererWhiteList = append(refererWhiteList, s.(string)) + } + } + + // 遍历检查 + ok := false + for _, domain := range refererWhiteList { + if domain == host { + ok = true + } + } + if !ok { + c.AbortWithStatusJSON(200, result.ErrMsg("无效 Referer "+host)) + return + } +} diff --git a/src/framework/middleware/security/security.go b/src/framework/middleware/security/security.go new file mode 100644 index 00000000..d922a092 --- /dev/null +++ b/src/framework/middleware/security/security.go @@ -0,0 +1,23 @@ +package security + +import ( + "github.com/gin-gonic/gin" +) + +// Security 安全 +func Security() gin.HandlerFunc { + return func(c *gin.Context) { + // 拦截,判断是否有效Referer + referer(c) + + // 无拦截,仅仅设置响应头 + xframe(c) + csp(c) + hsts(c) + noopen(c) + nosniff(c) + xssProtection(c) + + c.Next() + } +} diff --git a/src/framework/middleware/security/xframe.go b/src/framework/middleware/security/xframe.go new file mode 100644 index 00000000..9fae2007 --- /dev/null +++ b/src/framework/middleware/security/xframe.go @@ -0,0 +1,26 @@ +package security + +import ( + "ems.agt/src/framework/config" + + "github.com/gin-gonic/gin" +) + +// xframe 用来配置 X-Frame-Options 响应头 +// 用来给浏览器指示允许一个页面可否在 frame, iframe, embed 或者 object 中展现的标记。 +// 站点可以通过确保网站没有被嵌入到别人的站点里面,从而避免 clickjacking 攻击。 +func xframe(c *gin.Context) { + enable := false + if v := config.Get("security.xframe.enable"); v != nil { + enable = v.(bool) + } + + value := "sameorigin" + if v := config.Get("security.xframe.value"); v != nil { + value = v.(string) + } + + if enable { + c.Header("x-frame-options", value) + } +} diff --git a/src/framework/middleware/security/xss_protection.go b/src/framework/middleware/security/xss_protection.go new file mode 100644 index 00000000..88502ccb --- /dev/null +++ b/src/framework/middleware/security/xss_protection.go @@ -0,0 +1,24 @@ +package security + +import ( + "ems.agt/src/framework/config" + + "github.com/gin-gonic/gin" +) + +// xssProtection 用于启用浏览器的XSS过滤功能,以防止 XSS 跨站脚本攻击。 +func xssProtection(c *gin.Context) { + enable := false + if v := config.Get("security.xssProtection.enable"); v != nil { + enable = v.(bool) + } + + value := "1; mode=block" + if v := config.Get("security.xssProtection.value"); v != nil { + value = v.(string) + } + + if enable { + c.Header("x-xss-protection", value) + } +} diff --git a/lib/core/redis/redis.go b/src/framework/redis/redis.go similarity index 90% rename from lib/core/redis/redis.go rename to src/framework/redis/redis.go index d4597780..604d5388 100644 --- a/lib/core/redis/redis.go +++ b/src/framework/redis/redis.go @@ -6,8 +6,9 @@ import ( "strings" "time" - "ems.agt/lib/core/conf" - "ems.agt/lib/log" + "ems.agt/src/framework/config" + "ems.agt/src/framework/logger" + "github.com/redis/go-redis/v9" ) @@ -33,7 +34,7 @@ return tonumber(current);`) func Connect() { ctx := context.Background() // 读取数据源配置 - datasource := conf.Get("redis.dataSource").(map[string]any) + datasource := config.Get("redis.dataSource").(map[string]any) for k, v := range datasource { client := v.(map[string]any) // 创建连接 @@ -46,10 +47,9 @@ func Connect() { // 测试数据库连接 pong, err := rdb.Ping(ctx).Result() if err != nil { - log.Fatalf("failed error ping redis %s %d is %v", client["host"], client["db"], err) - continue + logger.Fatalf("Ping redis %s is %v", k, err) } - log.Infof("redis %s %d %s connection is successful.", client["host"], client["db"], pong) + logger.Infof("redis %s %s %d connection is successful.", k, pong, client["db"].(int)) rdbMap[k] = rdb } } @@ -58,14 +58,14 @@ func Connect() { func Close() { for _, rdb := range rdbMap { if err := rdb.Close(); err != nil { - log.Errorf("fatal error db close: %s", err) + logger.Errorf("fatal error db close: %s", err) } } } // 获取默认实例 func DefaultRDB() *redis.Client { - source := conf.Get("redis.defaultDataSourceName").(string) + source := config.Get("redis.defaultDataSourceName").(string) return rdbMap[source] } @@ -186,7 +186,7 @@ func GetKeys(source string, pattern string) ([]string, error) { // 使用 SCAN 命令获取匹配的键 batchKeys, nextCursor, err := rdb.Scan(ctx, cursor, pattern, 100).Result() if err != nil { - log.Errorf("Failed to scan keys: %v", err) + logger.Errorf("Failed to scan keys: %v", err) return keys, err } cursor = nextCursor @@ -214,7 +214,7 @@ func GetBatch(source string, keys []string) ([]any, error) { // 获取缓存数据 result, err := rdb.MGet(context.Background(), keys...).Result() if err != nil { - log.Errorf("Failed to get batch data: %v", err) + logger.Errorf("Failed to get batch data: %v", err) return []any{}, err } return result, nil @@ -279,7 +279,7 @@ func Set(source, key string, value any) (bool, error) { ctx := context.Background() err := rdb.Set(ctx, key, value, 0).Err() if err != nil { - log.Errorf("redis lua script err %v", err) + logger.Errorf("redis Set err %v", err) return false, err } return true, nil @@ -296,7 +296,7 @@ func SetByExpire(source, key string, value any, expiration time.Duration) (bool, ctx := context.Background() err := rdb.Set(ctx, key, value, expiration).Err() if err != nil { - log.Errorf("redis lua script err %v", err) + logger.Errorf("redis SetByExpire err %v", err) return false, err } return true, nil @@ -313,7 +313,7 @@ func Del(source string, key string) (bool, error) { ctx := context.Background() err := rdb.Del(ctx, key).Err() if err != nil { - log.Errorf("redis lua script err %v", err) + logger.Errorf("redis Del err %v", err) return false, err } return true, nil @@ -334,7 +334,7 @@ func DelKeys(source string, keys []string) (bool, error) { ctx := context.Background() err := rdb.Del(ctx, keys...).Err() if err != nil { - log.Errorf("redis lua script err %v", err) + logger.Errorf("redis DelKeys err %v", err) return false, err } return true, nil @@ -351,7 +351,7 @@ func RateLimit(source, limitKey string, time, count int64) (int64, error) { ctx := context.Background() result, err := rateLimitCommand.Run(ctx, rdb, []string{limitKey}, time, count).Result() if err != nil { - log.Errorf("redis lua script err %v", err) + logger.Errorf("redis RateLimit err %v", err) return 0, err } return result.(int64), err diff --git a/lib/core/utils/crypto/crypto.go b/src/framework/utils/crypto/crypto.go similarity index 100% rename from lib/core/utils/crypto/crypto.go rename to src/framework/utils/crypto/crypto.go diff --git a/src/framework/utils/ctx/ctx.go b/src/framework/utils/ctx/ctx.go new file mode 100644 index 00000000..7de6ace5 --- /dev/null +++ b/src/framework/utils/ctx/ctx.go @@ -0,0 +1,199 @@ +package ctx + +import ( + "fmt" + "strings" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/constants/roledatascope" + "ems.agt/src/framework/constants/token" + "ems.agt/src/framework/utils/ip2region" + "ems.agt/src/framework/utils/ua" + "ems.agt/src/framework/vo" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// QueryMap 查询参数转换Map +func QueryMap(c *gin.Context) map[string]any { + queryValues := c.Request.URL.Query() + queryParams := make(map[string]any) + for key, values := range queryValues { + queryParams[key] = values[0] + } + return queryParams +} + +// BodyJSONMap JSON参数转换Map +func BodyJSONMap(c *gin.Context) map[string]any { + params := make(map[string]any) + c.ShouldBindBodyWith(¶ms, binding.JSON) + return params +} + +// RequestParamsMap 请求参数转换Map +func RequestParamsMap(c *gin.Context) map[string]any { + params := make(map[string]any) + // json + if strings.HasPrefix(c.ContentType(), "application/json") { + c.ShouldBindBodyWith(¶ms, binding.JSON) + } + + // 表单 + bodyParams := c.Request.PostForm + for key, value := range bodyParams { + params[key] = value[0] + } + + // 查询 + queryParams := c.Request.URL.Query() + for key, value := range queryParams { + params[key] = value[0] + } + return params +} + +// IPAddrLocation 解析ip地址 +func IPAddrLocation(c *gin.Context) (string, string) { + ip := ip2region.ClientIP(c.ClientIP()) + location := ip2region.RealAddressByIp(ip) + return ip, location +} + +// Authorization 解析请求头 +func Authorization(c *gin.Context) string { + authHeader := c.GetHeader(token.HEADER_KEY) + if authHeader == "" { + return "" + } + // 拆分 Authorization 请求头,提取 JWT 令牌部分 + arr := strings.Split(authHeader, token.HEADER_PREFIX) + if len(arr) == 2 && arr[1] == "" { + return "" + } + return arr[1] +} + +// UaOsBrowser 解析请求用户代理信息 +func UaOsBrowser(c *gin.Context) (string, string) { + userAgent := c.GetHeader("user-agent") + uaInfo := ua.Info(userAgent) + + browser := "未知 未知" + bName, bVersion := uaInfo.Browser() + if bName != "" && bVersion != "" { + browser = bName + " " + bVersion + } + + os := "未知 未知" + bos := uaInfo.OS() + if bos != "" { + os = bos + } + return os, browser +} + +// LoginUser 登录用户信息 +func LoginUser(c *gin.Context) (vo.LoginUser, error) { + value, exists := c.Get(common.CTX_LOGIN_USER) + if exists { + return value.(vo.LoginUser), nil + } + return vo.LoginUser{}, fmt.Errorf("无效登录用户信息") +} + +// LoginUserToUserID 登录用户信息-用户ID +func LoginUserToUserID(c *gin.Context) string { + value, exists := c.Get(common.CTX_LOGIN_USER) + if exists { + loginUser := value.(vo.LoginUser) + return loginUser.UserID + } + return "" +} + +// LoginUserToUserName 登录用户信息-用户名称 +func LoginUserToUserName(c *gin.Context) string { + value, exists := c.Get(common.CTX_LOGIN_USER) + if exists { + loginUser := value.(vo.LoginUser) + return loginUser.User.UserName + } + return "" +} + +// LoginUserToDataScopeSQL 登录用户信息-角色数据范围过滤SQL字符串 +func LoginUserToDataScopeSQL(c *gin.Context, deptAlias string, userAlias string) string { + dataScopeSQL := "" + // 登录用户信息 + loginUser, err := LoginUser(c) + if err != nil { + return dataScopeSQL + } + userInfo := loginUser.User + + // 如果是管理员,则不过滤数据 + if config.IsAdmin(userInfo.UserID) { + return dataScopeSQL + } + // 无用户角色 + if len(userInfo.Roles) <= 0 { + return dataScopeSQL + } + + // 记录角色权限范围定义添加过, 非自定数据权限不需要重复拼接SQL + var scopeKeys []string + var conditions []string + for _, role := range userInfo.Roles { + dataScope := role.DataScope + + if roledatascope.ALL == dataScope { + break + } + + if roledatascope.CUSTOM != dataScope { + hasKey := false + for _, key := range scopeKeys { + if key == dataScope { + hasKey = true + break + } + } + if hasKey { + continue + } + } + + if roledatascope.CUSTOM == dataScope { + sql := fmt.Sprintf(`%s.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = '%s' )`, deptAlias, role.RoleID) + conditions = append(conditions, sql) + } + + if roledatascope.DEPT_AND_CHILD == dataScope { + sql := fmt.Sprintf(`%s.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = '%s' or find_in_set('%s' , ancestors ) )`, deptAlias, userInfo.DeptID, userInfo.DeptID) + conditions = append(conditions, sql) + } + + if roledatascope.SELF == dataScope { + // 数据权限为仅本人且没有userAlias别名不查询任何数据 + if userAlias == "" { + sql := fmt.Sprintf(`%s.dept_id = '0'`, deptAlias) + conditions = append(conditions, sql) + } else { + sql := fmt.Sprintf(`%s.user_id = '%s'`, userAlias, userInfo.UserID) + conditions = append(conditions, sql) + } + } + + // 记录角色范围 + scopeKeys = append(scopeKeys, dataScope) + } + + // 构建查询条件语句 + if len(conditions) > 0 { + dataScopeSQL = fmt.Sprintf(" AND ( %s ) ", strings.Join(conditions, " OR ")) + } + return dataScopeSQL +} diff --git a/src/framework/utils/date/date.go b/src/framework/utils/date/date.go new file mode 100644 index 00000000..c8cfa698 --- /dev/null +++ b/src/framework/utils/date/date.go @@ -0,0 +1,69 @@ +package date + +import ( + "time" + + "ems.agt/src/framework/logger" +) + +const ( + // 年 列如:2022 + YYYY = "2006" + // 年-月 列如:2022-12 + YYYY_MM = "2006-01" + // 年-月-日 列如:2022-12-30 + YYYY_MM_DD = "2006-01-02" + // 年月日时分秒 列如:20221230010159 + YYYYMMDDHHMMSS = "20060102150405" + // 年-月-日 时:分:秒 列如:2022-12-30 01:01:59 + YYYY_MM_DD_HH_MM_SS = "2006-01-02 15:04:05" +) + +// 格式时间字符串 +// +// dateStr 时间字符串 +// +// formatStr 时间格式 默认YYYY-MM-DD HH:mm:ss +func ParseStrToDate(dateStr, formatStr string) time.Time { + t, err := time.Parse(formatStr, dateStr) + if err != nil { + logger.Infof("utils ParseStrToDate err %v", err) + return time.Time{} + } + return t +} + +// 格式时间 +// +// date 可转的Date对象 +// +// formatStr 时间格式 默认YYYY-MM-DD HH:mm:ss +func ParseDateToStr(date any, formatStr string) string { + t, ok := date.(time.Time) + if !ok { + switch v := date.(type) { + case int64: + if v == 0 { + return "" + } + t = time.UnixMilli(v) + case string: + parsedTime, err := time.Parse(formatStr, v) + if err != nil { + logger.Infof("utils ParseDateToStr err %v", err) + return "" + } + t = parsedTime + default: + return "" + } + } + return t.Format(formatStr) +} + +// 格式时间成日期路径 +// +// 年/月 列如:2022/12 +func ParseDatePath(date time.Time) string { + return date.Format("2006/01") +} diff --git a/src/framework/utils/file/excel.go b/src/framework/utils/file/excel.go new file mode 100644 index 00000000..22a423d3 --- /dev/null +++ b/src/framework/utils/file/excel.go @@ -0,0 +1,154 @@ +package file + +import ( + "fmt" + "mime/multipart" + "os" + "path/filepath" + "time" + + "ems.agt/src/framework/constants/uploadsubpath" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/date" + + "github.com/xuri/excelize/v2" +) + +// TransferExeclUploadFile 表格文件上传保存 +// +// file 上传文件对象 +func TransferExeclUploadFile(file *multipart.FileHeader) (string, error) { + // 上传文件检查 + err := isAllowWrite(file.Filename, []string{".xls", ".xlsx"}, file.Size) + if err != nil { + return "", err + } + // 上传资源路径 + _, dir := resourceUpload() + // 新文件名称并组装文件地址 + filePath := filepath.Join(uploadsubpath.IMPORT, date.ParseDatePath(time.Now())) + fileName := generateFileName(file.Filename) + writePathFile := filepath.Join(dir, filePath, fileName) + // 存入新文件路径 + err = transferToNewFile(file, writePathFile) + if err != nil { + return "", err + } + return filepath.ToSlash(writePathFile), nil +} + +// 表格读取数据 +// +// filePath 文件路径地址 +// +// sheetName 工作簿名称, 空字符默认Sheet1 +func ReadSheet(filePath, sheetName string) ([]map[string]string, error) { + data := make([]map[string]string, 0) + // 打开 Excel 文件 + f, err := excelize.OpenFile(filePath) + if err != nil { + return data, err + } + defer func() { + if err := f.Close(); err != nil { + logger.Errorf("工作表文件关闭失败 : %v", err) + } + }() + + // 检查工作簿是否存在 + if sheetName == "" { + sheetName = "Sheet1" + } + if visible, _ := f.GetSheetVisible(sheetName); !visible { + return data, fmt.Errorf("读取工作簿 %s 失败", sheetName) + } + + // 获取工作簿记录 + rows, err := f.GetRows(sheetName) + if err != nil { + return data, err + } + + for i, row := range rows { + // 跳过第一行 + if i == 0 { + continue + } + // 遍历每个单元格 + rowData := map[string]string{} + for columnIndex, cellValue := range row { + columnName, _ := excelize.ColumnNumberToName(columnIndex + 1) + rowData[columnName] = cellValue + } + + data = append(data, rowData) + } + return data, nil +} + +// 表格写入数据 +// +// headerCells 第一行表头标题 "A1":"?" +// +// dataCells 从第二行开始的数据 "A2":"?" +// +// fileName 文件名称 +// +// sheetName 工作簿名称, 空字符默认Sheet1 +func WriteSheet(headerCells map[string]string, dataCells []map[string]any, fileName, sheetName string) (string, error) { + f := excelize.NewFile() + defer func() { + if err := f.Close(); err != nil { + logger.Errorf("工作表文件关闭失败 : %v", err) + } + }() + + // 创建一个工作表 + if sheetName == "" { + sheetName = "Sheet1" + } + index, err := f.NewSheet(sheetName) + if err != nil { + return "", fmt.Errorf("创建工作表失败 %v", err) + } + // 设置工作簿的默认工作表 + f.SetActiveSheet(index) + + // 首个和最后key名称 + firstKey := "A" + lastKey := "B" + + // 第一行表头标题 + for key, title := range headerCells { + f.SetCellValue(sheetName, key, title) + if key[:1] > lastKey { + lastKey = key[:1] + } + } + + // 设置工作表上宽度为 20 + f.SetColWidth(sheetName, firstKey, lastKey, 20) + + // 从第二行开始的数据 + for _, cell := range dataCells { + for key, value := range cell { + f.SetCellValue(sheetName, key, value) + } + } + + // 上传资源路径 + _, dir := resourceUpload() + filePath := filepath.Join(uploadsubpath.EXPORT, date.ParseDatePath(time.Now())) + saveFilePath := filepath.Join(dir, filePath, fileName) + + // 创建文件目录 + if err := os.MkdirAll(filepath.Dir(saveFilePath), 0750); err != nil { + return "", fmt.Errorf("创建保存文件失败 %v", err) + } + + // 根据指定路径保存文件 + if err := f.SaveAs(saveFilePath); err != nil { + return "", fmt.Errorf("保存工作表失败 %v", err) + } + return saveFilePath, nil +} diff --git a/src/framework/utils/file/file.go b/src/framework/utils/file/file.go new file mode 100644 index 00000000..9a95d9c1 --- /dev/null +++ b/src/framework/utils/file/file.go @@ -0,0 +1,297 @@ +package file + +import ( + "errors" + "fmt" + "mime/multipart" + "path" + "path/filepath" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/constants/uploadsubpath" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/generate" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/regular" +) + +/**最大文件名长度 */ +const DEFAULT_FILE_NAME_LENGTH = 100 + +// 文件上传路径 prefix, dir +func resourceUpload() (string, string) { + upload := config.Get("staticFile.upload").(map[string]any) + dir, err := filepath.Abs(upload["dir"].(string)) + if err != nil { + logger.Errorf("file resourceUpload err %v", err) + } + return upload["prefix"].(string), dir +} + +// 最大上传文件大小 +func uploadFileSize() int64 { + fileSize := 1 * 1024 * 1024 + size := config.Get("upload.fileSize").(int) + if size > 1 { + fileSize = size * 1024 * 1024 + } + return int64(fileSize) +} + +// 文件上传扩展名白名单 +func uploadWhiteList() []string { + arr := config.Get("upload.whitelist").([]any) + strings := make([]string, len(arr)) + for i, val := range arr { + if str, ok := val.(string); ok { + strings[i] = str + } + } + return strings +} + +// 生成文件名称 fileName_随机值.extName +// +// fileName 原始文件名称含后缀,如:logo.png +func generateFileName(fileName string) string { + fileExt := filepath.Ext(fileName) + // 替换掉后缀和特殊字符保留文件名 + newFileName := regular.Replace(fileName, fileExt, "") + newFileName = regular.Replace(newFileName, `[<>:"\\|?*]+`, "") + newFileName = strings.TrimSpace(newFileName) + return fmt.Sprintf("%s_%s%s", newFileName, generate.Code(6), fileExt) +} + +// 检查文件允许写入本地 +// +// fileName 原始文件名称含后缀,如:midway1_logo_iipc68.png +// +// allowExts 允许上传拓展类型,['.png'] +func isAllowWrite(fileName string, allowExts []string, fileSize int64) error { + // 判断上传文件名称长度 + if len(fileName) > DEFAULT_FILE_NAME_LENGTH { + return fmt.Errorf("上传文件名称长度限制最长为 %d", DEFAULT_FILE_NAME_LENGTH) + } + + // 最大上传文件大小 + maxFileSize := uploadFileSize() + if fileSize > maxFileSize { + return fmt.Errorf("最大上传文件大小 %s", parse.Bit(float64(maxFileSize))) + } + + // 判断文件拓展是否为允许的拓展类型 + fileExt := filepath.Ext(fileName) + hasExt := false + if len(allowExts) == 0 { + allowExts = uploadWhiteList() + } + for _, ext := range allowExts { + if ext == fileExt { + hasExt = true + break + } + } + if !hasExt { + return fmt.Errorf("上传文件类型不支持,仅支持以下类型:%s", strings.Join(allowExts, "、")) + } + + return nil +} + +// 检查文件允许本地读取 +// +// filePath 文件存放资源路径,URL相对地址 +func isAllowRead(filePath string) error { + // 禁止目录上跳级别 + if strings.Contains(filePath, "..") { + return fmt.Errorf("禁止目录上跳级别") + } + + // 检查允许下载的文件规则 + fileExt := filepath.Ext(filePath) + hasExt := false + for _, ext := range uploadWhiteList() { + if ext == fileExt { + hasExt = true + break + } + } + if !hasExt { + return fmt.Errorf("非法下载的文件规则:%s", fileExt) + } + + return nil +} + +// TransferUploadFile 上传资源文件转存 +// +// subPath 子路径,默认 UploadSubPath.DEFAULT +// +// allowExts 允许上传拓展类型(含“.”),如 ['.png','.jpg'] +func TransferUploadFile(file *multipart.FileHeader, subPath string, allowExts []string) (string, error) { + // 上传文件检查 + err := isAllowWrite(file.Filename, allowExts, file.Size) + if err != nil { + return "", err + } + // 上传资源路径 + prefix, dir := resourceUpload() + // 新文件名称并组装文件地址 + fileName := generateFileName(file.Filename) + filePath := filepath.Join(subPath, date.ParseDatePath(time.Now())) + writePathFile := filepath.Join(dir, filePath, fileName) + // 存入新文件路径 + err = transferToNewFile(file, writePathFile) + if err != nil { + return "", err + } + urlPath := filepath.Join(prefix, filePath, fileName) + return filepath.ToSlash(urlPath), nil +} + +// ReadUploadFileStream 上传资源文件读取 +// +// filePath 文件存放资源路径,URL相对地址 如:/upload/common/2023/06/xxx.png +// +// headerRange 断点续传范围区间,bytes=0-12131 +func ReadUploadFileStream(filePath, headerRange string) (map[string]any, error) { + // 读取文件检查 + err := isAllowRead(filePath) + if err != nil { + return map[string]any{}, err + } + // 上传资源路径 + prefix, dir := resourceUpload() + fileAsbPath := strings.Replace(filePath, prefix, dir, 1) + + // 响应结果 + result := map[string]any{ + "range": "", + "chunkSize": 0, + "fileSize": 0, + "data": nil, + } + + // 文件大小 + fileSize := getFileSize(fileAsbPath) + if fileSize <= 0 { + return result, nil + } + result["fileSize"] = fileSize + + if headerRange != "" { + partsStr := strings.Replace(headerRange, "bytes=", "", 1) + parts := strings.Split(partsStr, "-") + start, err := strconv.ParseInt(parts[0], 10, 64) + if err != nil || start > fileSize { + start = 0 + } + end, err := strconv.ParseInt(parts[1], 10, 64) + if err != nil || end > fileSize { + end = fileSize - 1 + } + if start > end { + start = end + } + + // 分片结果 + result["range"] = fmt.Sprintf("bytes %d-%d/%d", start, end, fileSize) + result["chunkSize"] = end - start + 1 + byteArr, err := getFileStream(fileAsbPath, start, end) + if err != nil { + return map[string]any{}, errors.New("读取文件失败") + } + result["data"] = byteArr + return result, nil + } + + byteArr, err := getFileStream(fileAsbPath, 0, fileSize) + if err != nil { + return map[string]any{}, errors.New("读取文件失败") + } + result["data"] = byteArr + return result, nil +} + +// TransferChunkUploadFile 上传资源切片文件转存 +// +// file 上传文件对象 +// +// index 切片文件序号 +// +// identifier 切片文件目录标识符 +func TransferChunkUploadFile(file *multipart.FileHeader, index, identifier string) (string, error) { + // 上传文件检查 + err := isAllowWrite(file.Filename, []string{}, file.Size) + if err != nil { + return "", err + } + // 上传资源路径 + prefix, dir := resourceUpload() + // 新文件名称并组装文件地址 + filePath := filepath.Join(uploadsubpath.CHUNK, date.ParseDatePath(time.Now()), identifier) + writePathFile := filepath.Join(dir, filePath, index) + // 存入新文件路径 + err = transferToNewFile(file, writePathFile) + if err != nil { + return "", err + } + urlPath := filepath.Join(prefix, filePath, index) + return filepath.ToSlash(urlPath), nil +} + +// 上传资源切片文件检查 +// +// identifier 切片文件目录标识符 +// +// originalFileName 原始文件名称,如logo.png +func ChunkCheckFile(identifier, originalFileName string) ([]string, error) { + // 读取文件检查 + err := isAllowWrite(originalFileName, []string{}, 0) + if err != nil { + return []string{}, err + } + // 上传资源路径 + _, dir := resourceUpload() + dirPath := path.Join(uploadsubpath.CHUNK, date.ParseDatePath(time.Now()), identifier) + readPath := path.Join(dir, dirPath) + fileList, err := getDirFileNameList(readPath) + if err != nil { + return []string{}, errors.New("读取文件失败") + } + return fileList, nil +} + +// 上传资源切片文件检查 +// +// identifier 切片文件目录标识符 +// +// originalFileName 原始文件名称,如logo.png +// +// subPath 子路径,默认 DEFAULT +func ChunkMergeFile(identifier, originalFileName, subPath string) (string, error) { + // 读取文件检查 + err := isAllowWrite(originalFileName, []string{}, 0) + if err != nil { + return "", err + } + // 上传资源路径 + prefix, dir := resourceUpload() + // 切片存放目录 + dirPath := path.Join(uploadsubpath.CHUNK, date.ParseDatePath(time.Now()), identifier) + readPath := path.Join(dir, dirPath) + // 组合存放文件路径 + fileName := generateFileName(originalFileName) + filePath := path.Join(subPath, date.ParseDatePath(time.Now())) + writePath := path.Join(dir, filePath) + err = mergeToNewFile(readPath, writePath, fileName) + if err != nil { + return "", err + } + urlPath := filepath.Join(prefix, filePath, fileName) + return filepath.ToSlash(urlPath), nil +} diff --git a/src/framework/utils/file/utils.go b/src/framework/utils/file/utils.go new file mode 100644 index 00000000..0a0f4b5c --- /dev/null +++ b/src/framework/utils/file/utils.go @@ -0,0 +1,185 @@ +package file + +import ( + "fmt" + "io" + "mime/multipart" + "os" + "path/filepath" + "sort" + "strconv" + + "ems.agt/src/framework/logger" +) + +// transferToNewFile 读取目标文件转移到新路径下 +// +// readFilePath 读取目标文件 +// +// writePath 写入路径 +// +// fileName 文件名称 +func transferToNewFile(file *multipart.FileHeader, dst string) error { + src, err := file.Open() + if err != nil { + return err + } + defer src.Close() + + if err = os.MkdirAll(filepath.Dir(dst), 0750); err != nil { + return err + } + + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, src) + return err +} + +// mergeToNewFile 将多个文件合并成一个文件并删除合并前的切片目录文件 +// +// dirPath 读取要合并文件的目录 +// +// writePath 写入路径 +// +// fileName 文件名称 +func mergeToNewFile(dirPath string, writePath string, fileName string) error { + // 读取目录下所有文件并排序,注意文件名称是否数值 + fileNameList, err := getDirFileNameList(dirPath) + if err != nil { + return fmt.Errorf("读取合并目标文件失败: %v", err) + } + if len(fileNameList) <= 0 { + return fmt.Errorf("读取合并目标文件失败") + } + + // 排序 + sort.Slice(fileNameList, func(i, j int) bool { + numI, _ := strconv.Atoi(fileNameList[i]) + numJ, _ := strconv.Atoi(fileNameList[j]) + return numI < numJ + }) + + // 写入到新路径文件 + newFilePath := filepath.Join(writePath, fileName) + if err = os.MkdirAll(filepath.Dir(newFilePath), 0750); err != nil { + return err + } + + // 转移完成后删除切片目录 + defer os.Remove(dirPath) + + // 打开新路径文件 + outputFile, err := os.Create(newFilePath) + if err != nil { + return fmt.Errorf("failed to create file: %v", err) + } + defer outputFile.Close() + + // 逐个读取文件后进行流拷贝数据块 + for _, fileName := range fileNameList { + chunkPath := filepath.Join(dirPath, fileName) + // 拷贝结束后删除切片 + defer os.Remove(chunkPath) + // 打开切片文件 + inputFile, err := os.Open(chunkPath) + if err != nil { + return fmt.Errorf("failed to open file: %v", err) + } + defer inputFile.Close() + // 拷贝文件流 + _, err = io.Copy(outputFile, inputFile) + if err != nil { + return fmt.Errorf("failed to copy file data: %w", err) + } + } + + return nil +} + +// getFileSize 读取文件大小 +func getFileSize(filePath string) int64 { + // 获取文件信息 + fileInfo, err := os.Stat(filePath) + if err != nil { + logger.Errorf("Failed stat %s: %v", filePath, err) + return 0 + } + // 获取文件大小(字节数) + return fileInfo.Size() +} + +// 读取文件流用于返回下载 +// +// filePath 文件路径 +// startOffset, endOffset 分片块读取区间,根据文件切片的块范围 +func getFileStream(filePath string, startOffset, endOffset int64) ([]byte, error) { + // 打开文件 + file, err := os.Open(filePath) + if err != nil { + return nil, err + } + defer file.Close() + + // 获取文件的大小 + fileInfo, err := file.Stat() + if err != nil { + return nil, err + } + fileSize := fileInfo.Size() + + // 确保起始和结束偏移量在文件范围内 + if startOffset > fileSize { + startOffset = 0 + } + if endOffset >= fileSize { + endOffset = fileSize - 1 + } + + // 计算切片的大小 + chunkSize := endOffset - startOffset + 1 + + // 创建 SectionReader + reader := io.NewSectionReader(file, startOffset, chunkSize) + + // 创建一个缓冲区来存储读取的数据 + buffer := make([]byte, chunkSize) + + // 读取数据到缓冲区 + _, err = reader.Read(buffer) + if err != nil && err != io.EOF { + return nil, err + } + + return buffer, nil +} + +// 获取文件目录下所有文件名称,不含目录名称 +// +// filePath 文件路径 +func getDirFileNameList(dirPath string) ([]string, error) { + fileNames := []string{} + + dir, err := os.Open(dirPath) + if err != nil { + return fileNames, nil + } + defer dir.Close() + + fileInfos, err := dir.Readdir(-1) + if err != nil { + return fileNames, err + } + + for _, fileInfo := range fileInfos { + if fileInfo.Mode().IsRegular() { + fileNames = append(fileNames, fileInfo.Name()) + } + } + + return fileNames, nil +} diff --git a/src/framework/utils/generate/generate.go b/src/framework/utils/generate/generate.go new file mode 100644 index 00000000..f7ca1571 --- /dev/null +++ b/src/framework/utils/generate/generate.go @@ -0,0 +1,43 @@ +package generate + +import ( + "math/rand" + "time" + + "ems.agt/src/framework/logger" + + gonanoid "github.com/matoous/go-nanoid/v2" +) + +// 生成随机Code +// 包含数字、小写字母 +// 不保证长度满足 +func Code(size int) string { + str, err := gonanoid.Generate("0123456789abcdefghijklmnopqrstuvwxyz", size) + if err != nil { + logger.Infof("%d : %v", size, err) + return "" + } + return str +} + +// 生成随机字符串 +// 包含数字、大小写字母、下划线、横杠 +// 不保证长度满足 +func String(size int) string { + str, err := gonanoid.New(size) + if err != nil { + logger.Infof("%d : %v", size, err) + return "" + } + return str +} + +// 随机数 纯数字0-9 +func Number(size int) int { + source := rand.NewSource(time.Now().UnixNano()) + random := rand.New(source) + min := int64(0) + max := int64(9 * int(size)) + return int(random.Int63n(max-min+1) + min) +} diff --git a/src/framework/utils/ip2region/binding.go b/src/framework/utils/ip2region/binding.go new file mode 100644 index 00000000..4782f67e --- /dev/null +++ b/src/framework/utils/ip2region/binding.go @@ -0,0 +1,238 @@ +package ip2region + +import ( + "encoding/binary" + "fmt" + "os" +) + +const ( + HeaderInfoLength = 256 + VectorIndexRows = 256 + VectorIndexCols = 256 + VectorIndexSize = 8 + SegmentIndexBlockSize = 14 +) + +// --- Index policy define + +type IndexPolicy int + +const ( + VectorIndexPolicy IndexPolicy = 1 + BTreeIndexPolicy IndexPolicy = 2 +) + +func (i IndexPolicy) String() string { + switch i { + case VectorIndexPolicy: + return "VectorIndex" + case BTreeIndexPolicy: + return "BtreeIndex" + default: + return "unknown" + } +} + +// --- Header define + +type Header struct { + // data []byte + Version uint16 + IndexPolicy IndexPolicy + CreatedAt uint32 + StartIndexPtr uint32 + EndIndexPtr uint32 +} + +func NewHeader(input []byte) (*Header, error) { + if len(input) < 16 { + return nil, fmt.Errorf("invalid input buffer") + } + + return &Header{ + Version: binary.LittleEndian.Uint16(input), + IndexPolicy: IndexPolicy(binary.LittleEndian.Uint16(input[2:])), + CreatedAt: binary.LittleEndian.Uint32(input[4:]), + StartIndexPtr: binary.LittleEndian.Uint32(input[8:]), + EndIndexPtr: binary.LittleEndian.Uint32(input[12:]), + }, nil +} + +// --- searcher implementation + +type Searcher struct { + handle *os.File + + ioCount int + + // use it only when this feature enabled. + // Preload the vector index will reduce the number of IO operations + // thus speedup the search process + vectorIndex []byte + + // content buffer. + // running with the whole xdb file cached + contentBuff []byte +} + +func baseNew(dbFile string, vIndex []byte, cBuff []byte) (*Searcher, error) { + var err error + + // content buff first + if cBuff != nil { + return &Searcher{ + vectorIndex: nil, + contentBuff: cBuff, + }, nil + } + + // open the xdb binary file + handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600) + if err != nil { + return nil, err + } + + return &Searcher{ + handle: handle, + vectorIndex: vIndex, + }, nil +} + +func NewWithFileOnly(dbFile string) (*Searcher, error) { + return baseNew(dbFile, nil, nil) +} + +func NewWithVectorIndex(dbFile string, vIndex []byte) (*Searcher, error) { + return baseNew(dbFile, vIndex, nil) +} + +func NewWithBuffer(cBuff []byte) (*Searcher, error) { + return baseNew("", nil, cBuff) +} + +func (s *Searcher) Close() { + if s.handle != nil { + err := s.handle.Close() + if err != nil { + return + } + } +} + +// GetIOCount return the global io count for the last search +func (s *Searcher) GetIOCount() int { + return s.ioCount +} + +// SearchByStr find the region for the specified ip string +func (s *Searcher) SearchByStr(str string) (string, error) { + ip, err := CheckIP(str) + if err != nil { + return "", err + } + + return s.Search(ip) +} + +// Search find the region for the specified long ip +func (s *Searcher) Search(ip uint32) (string, error) { + // reset the global ioCount + s.ioCount = 0 + + // locate the segment index block based on the vector index + var il0 = (ip >> 24) & 0xFF + var il1 = (ip >> 16) & 0xFF + var idx = il0*VectorIndexCols*VectorIndexSize + il1*VectorIndexSize + var sPtr, ePtr = uint32(0), uint32(0) + if s.vectorIndex != nil { + sPtr = binary.LittleEndian.Uint32(s.vectorIndex[idx:]) + ePtr = binary.LittleEndian.Uint32(s.vectorIndex[idx+4:]) + } else if s.contentBuff != nil { + sPtr = binary.LittleEndian.Uint32(s.contentBuff[HeaderInfoLength+idx:]) + ePtr = binary.LittleEndian.Uint32(s.contentBuff[HeaderInfoLength+idx+4:]) + } else { + // read the vector index block + var buff = make([]byte, VectorIndexSize) + err := s.read(int64(HeaderInfoLength+idx), buff) + if err != nil { + return "", fmt.Errorf("read vector index block at %d: %w", HeaderInfoLength+idx, err) + } + + sPtr = binary.LittleEndian.Uint32(buff) + ePtr = binary.LittleEndian.Uint32(buff[4:]) + } + + // fmt.Printf("sPtr=%d, ePtr=%d", sPtr, ePtr) + + // binary search the segment index to get the region + var dataLen, dataPtr = 0, uint32(0) + var buff = make([]byte, SegmentIndexBlockSize) + var l, h = 0, int((ePtr - sPtr) / SegmentIndexBlockSize) + for l <= h { + m := (l + h) >> 1 + p := sPtr + uint32(m*SegmentIndexBlockSize) + err := s.read(int64(p), buff) + if err != nil { + return "", fmt.Errorf("read segment index at %d: %w", p, err) + } + + // decode the data step by step to reduce the unnecessary operations + sip := binary.LittleEndian.Uint32(buff) + if ip < sip { + h = m - 1 + } else { + eip := binary.LittleEndian.Uint32(buff[4:]) + if ip > eip { + l = m + 1 + } else { + dataLen = int(binary.LittleEndian.Uint16(buff[8:])) + dataPtr = binary.LittleEndian.Uint32(buff[10:]) + break + } + } + } + + //fmt.Printf("dataLen: %d, dataPtr: %d", dataLen, dataPtr) + if dataLen == 0 { + return "", nil + } + + // load and return the region data + var regionBuff = make([]byte, dataLen) + err := s.read(int64(dataPtr), regionBuff) + if err != nil { + return "", fmt.Errorf("read region at %d: %w", dataPtr, err) + } + + return string(regionBuff), nil +} + +// do the data read operation based on the setting. +// content buffer first or will read from the file. +// this operation will invoke the Seek for file based read. +func (s *Searcher) read(offset int64, buff []byte) error { + if s.contentBuff != nil { + cLen := copy(buff, s.contentBuff[offset:]) + if cLen != len(buff) { + return fmt.Errorf("incomplete read: readed bytes should be %d", len(buff)) + } + } else { + _, err := s.handle.Seek(offset, 0) + if err != nil { + return fmt.Errorf("seek to %d: %w", offset, err) + } + + s.ioCount++ + rLen, err := s.handle.Read(buff) + if err != nil { + return fmt.Errorf("handle read: %w", err) + } + + if rLen != len(buff) { + return fmt.Errorf("incomplete read: readed bytes should be %d", len(buff)) + } + } + + return nil +} diff --git a/src/framework/utils/ip2region/ip2region.go b/src/framework/utils/ip2region/ip2region.go new file mode 100644 index 00000000..68bd0b16 --- /dev/null +++ b/src/framework/utils/ip2region/ip2region.go @@ -0,0 +1,89 @@ +package ip2region + +import ( + "embed" + "strings" + "time" + + "ems.agt/src/framework/logger" +) + +// 网络地址(内网) +const LOCAT_HOST = "127.0.0.1" + +// 全局查询对象 +var searcher *Searcher + +//go:embed ip2region.xdb +var ip2regionDB embed.FS + +func init() { + // 从 dbPath 加载整个 xdb 到内存 + buf, err := ip2regionDB.ReadFile("ip2region.xdb") + if err != nil { + logger.Fatalf("failed error load xdb from : %s\n", err) + return + } + + // 用全局的 cBuff 创建完全基于内存的查询对象。 + base, err := NewWithBuffer(buf) + if err != nil { + logger.Errorf("failed error create searcher with content: %s\n", err) + return + } + + // 赋值到全局查询对象 + searcher = base +} + +// RegionSearchByIp 查询IP所在地 +// +// 国家|区域|省份|城市|ISP +func RegionSearchByIp(ip string) (string, int, int64) { + ip = ClientIP(ip) + if ip == LOCAT_HOST { + return "0|0|0|内网IP|内网IP", 0, 0 + } + tStart := time.Now() + region, err := searcher.SearchByStr(ip) + if err != nil { + logger.Errorf("failed to SearchIP(%s): %s\n", ip, err) + return "0|0|0|0|0", 0, 0 + } + return region, 0, time.Since(tStart).Milliseconds() +} + +// RealAddressByIp 地址IP所在地 +// +// 218.4.167.70 江苏省 苏州市 +func RealAddressByIp(ip string) string { + ip = ClientIP(ip) + if ip == LOCAT_HOST { + return "内网IP" + } + region, err := searcher.SearchByStr(ip) + if err != nil { + logger.Errorf("failed to SearchIP(%s): %s\n", ip, err) + return "未知" + } + parts := strings.Split(region, "|") + province := parts[2] + city := parts[3] + if province == "0" && city != "0" { + return city + } + return province + " " + city +} + +// ClientIP 处理客户端IP地址显示iPv4 +// +// 转换 ip2region.ClientIP(c.ClientIP()) +func ClientIP(ip string) string { + if strings.HasPrefix(ip, "::ffff:") { + ip = strings.Replace(ip, "::ffff:", "", 1) + } + if ip == LOCAT_HOST || ip == "::1" { + return LOCAT_HOST + } + return ip +} diff --git a/src/framework/utils/ip2region/ip2region.xdb b/src/framework/utils/ip2region/ip2region.xdb new file mode 100644 index 00000000..c78b7928 Binary files /dev/null and b/src/framework/utils/ip2region/ip2region.xdb differ diff --git a/src/framework/utils/ip2region/util.go b/src/framework/utils/ip2region/util.go new file mode 100644 index 00000000..cfef29ea --- /dev/null +++ b/src/framework/utils/ip2region/util.go @@ -0,0 +1,175 @@ +package ip2region + +import ( + "fmt" + "os" + "strconv" + "strings" +) + +var shiftIndex = []int{24, 16, 8, 0} + +func CheckIP(ip string) (uint32, error) { + var ps = strings.Split(strings.TrimSpace(ip), ".") + if len(ps) != 4 { + return 0, fmt.Errorf("invalid ip address `%s`", ip) + } + + var val = uint32(0) + for i, s := range ps { + d, err := strconv.Atoi(s) + if err != nil { + return 0, fmt.Errorf("the %dth part `%s` is not an integer", i, s) + } + + if d < 0 || d > 255 { + return 0, fmt.Errorf("the %dth part `%s` should be an integer bettween 0 and 255", i, s) + } + + val |= uint32(d) << shiftIndex[i] + } + + // convert the ip to integer + return val, nil +} + +func Long2IP(ip uint32) string { + return fmt.Sprintf("%d.%d.%d.%d", (ip>>24)&0xFF, (ip>>16)&0xFF, (ip>>8)&0xFF, ip&0xFF) +} + +func MidIP(sip uint32, eip uint32) uint32 { + return uint32((uint64(sip) + uint64(eip)) >> 1) +} + +// LoadHeader load the header info from the specified handle +func LoadHeader(handle *os.File) (*Header, error) { + _, err := handle.Seek(0, 0) + if err != nil { + return nil, fmt.Errorf("seek to the header: %w", err) + } + + var buff = make([]byte, HeaderInfoLength) + rLen, err := handle.Read(buff) + if err != nil { + return nil, err + } + + if rLen != len(buff) { + return nil, fmt.Errorf("incomplete read: readed bytes should be %d", len(buff)) + } + + return NewHeader(buff) +} + +// LoadHeaderFromFile load header info from the specified db file path +func LoadHeaderFromFile(dbFile string) (*Header, error) { + handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600) + if err != nil { + return nil, fmt.Errorf("open xdb file `%s`: %w", dbFile, err) + } + + defer func(handle *os.File) { + _ = handle.Close() + }(handle) + + header, err := LoadHeader(handle) + if err != nil { + return nil, err + } + + return header, nil +} + +// LoadHeaderFromBuff wrap the header info from the content buffer +func LoadHeaderFromBuff(cBuff []byte) (*Header, error) { + return NewHeader(cBuff[0:256]) +} + +// LoadVectorIndex util function to load the vector index from the specified file handle +func LoadVectorIndex(handle *os.File) ([]byte, error) { + // load all the vector index block + _, err := handle.Seek(HeaderInfoLength, 0) + if err != nil { + return nil, fmt.Errorf("seek to vector index: %w", err) + } + + var buff = make([]byte, VectorIndexRows*VectorIndexCols*VectorIndexSize) + rLen, err := handle.Read(buff) + if err != nil { + return nil, err + } + + if rLen != len(buff) { + return nil, fmt.Errorf("incomplete read: readed bytes should be %d", len(buff)) + } + + return buff, nil +} + +// LoadVectorIndexFromFile load vector index from a specified file path +func LoadVectorIndexFromFile(dbFile string) ([]byte, error) { + handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600) + if err != nil { + return nil, fmt.Errorf("open xdb file `%s`: %w", dbFile, err) + } + + defer func() { + _ = handle.Close() + }() + + vIndex, err := LoadVectorIndex(handle) + if err != nil { + return nil, err + } + + return vIndex, nil +} + +// LoadContent load the whole xdb content from the specified file handle +func LoadContent(handle *os.File) ([]byte, error) { + // get file size + fi, err := handle.Stat() + if err != nil { + return nil, fmt.Errorf("stat: %w", err) + } + + size := fi.Size() + + // seek to the head of the file + _, err = handle.Seek(0, 0) + if err != nil { + return nil, fmt.Errorf("seek to get xdb file length: %w", err) + } + + var buff = make([]byte, size) + rLen, err := handle.Read(buff) + if err != nil { + return nil, err + } + + if rLen != len(buff) { + return nil, fmt.Errorf("incomplete read: readed bytes should be %d", len(buff)) + } + + return buff, nil +} + +// LoadContentFromFile load the whole xdb content from the specified db file path +func LoadContentFromFile(dbFile string) ([]byte, error) { + handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600) + if err != nil { + return nil, fmt.Errorf("open xdb file `%s`: %w", dbFile, err) + } + + defer func() { + _ = handle.Close() + }() + + cBuff, err := LoadContent(handle) + if err != nil { + return nil, err + } + + return cBuff, nil +} + diff --git a/src/framework/utils/parse/parse.go b/src/framework/utils/parse/parse.go new file mode 100644 index 00000000..ff8145c0 --- /dev/null +++ b/src/framework/utils/parse/parse.go @@ -0,0 +1,167 @@ +package parse + +import ( + "fmt" + "image/color" + "reflect" + "regexp" + "strconv" + "strings" + "time" + + "github.com/robfig/cron/v3" +) + +// Number 解析数值型 +func Number(str any) int64 { + switch str := str.(type) { + case string: + if str == "" { + return 0 + } + num, err := strconv.ParseInt(str, 10, 64) + if err != nil { + return 0 + } + return num + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + return reflect.ValueOf(str).Int() + case float32, float64: + return int64(reflect.ValueOf(str).Float()) + default: + return 0 + } +} + +// Boolean 解析布尔型 +func Boolean(str any) bool { + switch str := str.(type) { + case string: + if str == "" || str == "false" || str == "0" { + return false + } + // 尝试将字符串解析为数字 + if num, err := strconv.ParseFloat(str, 64); err == nil { + return num != 0 + } + return true + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + num := reflect.ValueOf(str).Int() + return num != 0 + case float32, float64: + num := reflect.ValueOf(str).Float() + return num != 0 + default: + return false + } +} + +// ConvertToCamelCase 字符串转换驼峰形式 +// +// 字符串 dict/inline/data/:dictId 结果 DictInlineDataDictId +func ConvertToCamelCase(str string) string { + if len(str) == 0 { + return str + } + reg := regexp.MustCompile(`[-_:/]\w`) + result := reg.ReplaceAllStringFunc(str, func(match string) string { + return strings.ToUpper(string(match[1])) + }) + + words := strings.Fields(result) + for i, word := range words { + str := word[1:] + str = strings.ReplaceAll(str, "/", "") + words[i] = strings.ToUpper(word[:1]) + str + } + + return strings.Join(words, "") +} + +// Bit 比特位为单位 +func Bit(bit float64) string { + var GB, MB, KB string + + if bit > float64(1<<30) { + GB = fmt.Sprintf("%0.2f", bit/(1<<30)) + } + + if bit > float64(1<<20) && bit < (1<<30) { + MB = fmt.Sprintf("%.2f", bit/(1<<20)) + } + + if bit > float64(1<<10) && bit < (1<<20) { + KB = fmt.Sprintf("%.2f", bit/(1<<10)) + } + + if GB != "" { + return GB + "GB" + } else if MB != "" { + return MB + "MB" + } else if KB != "" { + return KB + "KB" + } else { + return fmt.Sprintf("%vB", bit) + } +} + +// CronExpression 解析 Cron 表达式,返回下一次执行的时间戳(毫秒) +// +// 【*/5 * * * * ?】 6个参数 +func CronExpression(expression string) int64 { + specParser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor) + schedule, err := specParser.Parse(expression) + if err != nil { + return 0 + } + return schedule.Next(time.Now()).UnixMilli() +} + +// SafeContent 内容值进行安全掩码 +func SafeContent(value string) string { + if len(value) < 3 { + return strings.Repeat("*", len(value)) + } else if len(value) < 6 { + return string(value[0]) + strings.Repeat("*", len(value)-1) + } else if len(value) < 10 { + return string(value[0]) + strings.Repeat("*", len(value)-2) + string(value[len(value)-1]) + } else if len(value) < 15 { + return value[:2] + strings.Repeat("*", len(value)-4) + value[len(value)-2:] + } else { + return value[:3] + strings.Repeat("*", len(value)-6) + value[len(value)-3:] + } +} + +// RemoveDuplicates 数组内字符串去重 +func RemoveDuplicates(ids []string) []string { + uniqueIDs := make(map[string]bool) + uniqueIDSlice := make([]string, 0) + + for _, id := range ids { + _, ok := uniqueIDs[id] + if !ok && id != "" { + uniqueIDs[id] = true + uniqueIDSlice = append(uniqueIDSlice, id) + } + } + + return uniqueIDSlice +} + +// Color 解析颜色 #fafafa +func Color(colorStr string) *color.RGBA { + // 去除 # 号 + colorStr = colorStr[1:] + + // 将颜色字符串拆分为 R、G、B 分量 + r, _ := strconv.ParseInt(colorStr[0:2], 16, 0) + g, _ := strconv.ParseInt(colorStr[2:4], 16, 0) + b, _ := strconv.ParseInt(colorStr[4:6], 16, 0) + + return &color.RGBA{ + R: uint8(r), + G: uint8(g), + B: uint8(b), + A: 255, // 不透明 + } +} diff --git a/src/framework/utils/regular/regular.go b/src/framework/utils/regular/regular.go new file mode 100644 index 00000000..9feebc38 --- /dev/null +++ b/src/framework/utils/regular/regular.go @@ -0,0 +1,86 @@ +package regular + +import ( + "regexp" + + "github.com/dlclark/regexp2" +) + +// Replace 正则替换 +func Replace(originStr, pattern, repStr string) string { + regex := regexp.MustCompile(pattern) + return regex.ReplaceAllString(originStr, repStr) +} + +// 判断是否为有效用户名格式 +// +// 用户名不能以数字开头,可包含大写小写字母,数字,且不少于5位 +func ValidUsername(username string) bool { + if username == "" { + return false + } + pattern := `^[a-zA-Z][a-z0-9A-Z]{5,}` + match, err := regexp.MatchString(pattern, username) + if err != nil { + return false + } + return match +} + +// 判断是否为有效密码格式 +// +// 密码至少包含大小写字母、数字、特殊符号,且不少于6位 +func ValidPassword(password string) bool { + if password == "" { + return false + } + pattern := `^(?![A-Za-z0-9]+$)(?![a-z0-9\W]+$)(?![A-Za-z\W]+$)(?![A-Z0-9\W]+$)[a-zA-Z0-9\W]{6,}$` + re := regexp2.MustCompile(pattern, 0) + match, err := re.MatchString(password) + if err != nil { + return false + } + return match +} + +// 判断是否为有效手机号格式,1开头的11位手机号 +func ValidMobile(mobile string) bool { + if mobile == "" { + return false + } + pattern := `^1[3|4|5|6|7|8|9][0-9]\d{8}$` + match, err := regexp.MatchString(pattern, mobile) + if err != nil { + return false + } + return match +} + +// 判断是否为有效邮箱格式 +func ValidEmail(email string) bool { + if email == "" { + return false + } + pattern := `^(([^<>()\\.,;:\s@"]+(\.[^<>()\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+\.)+[a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{2,}))$` + re := regexp2.MustCompile(pattern, 0) + match, err := re.MatchString(email) + if err != nil { + return false + } + return match +} + +// 判断是否为http(s)://开头 +// +// link 网络链接 +func ValidHttp(link string) bool { + if link == "" { + return false + } + pattern := `^http(s)?:\/\/+` + match, err := regexp.MatchString(pattern, link) + if err != nil { + return false + } + return match +} diff --git a/src/framework/utils/repo/repo.go b/src/framework/utils/repo/repo.go new file mode 100644 index 00000000..47bdf86b --- /dev/null +++ b/src/framework/utils/repo/repo.go @@ -0,0 +1,132 @@ +package repo + +import ( + "fmt" + "reflect" + "strconv" + "strings" + + "ems.agt/src/framework/utils/parse" +) + +// PageNumSize 分页页码记录数 +func PageNumSize(pageNum, pageSize any) (int64, int64) { + // 记录起始索引 + num := parse.Number(pageNum) + if num > 5000 { + num = 5000 + } + if num < 1 { + num = 1 + } + + // 显示记录数 + size := parse.Number(pageSize) + if size > 50000 { + size = 50000 + } + if size < 0 { + size = 10 + } + return num - 1, size +} + +// SetFieldValue 判断结构体内是否存在指定字段并设置值 +func SetFieldValue(obj any, fieldName string, value any) { + // 获取结构体的反射值 + userValue := reflect.ValueOf(obj) + + // 获取字段的反射值 + fieldValue := userValue.Elem().FieldByName(fieldName) + + // 检查字段是否存在 + if fieldValue.IsValid() && fieldValue.CanSet() { + // 获取字段的类型 + fieldType := fieldValue.Type() + + // 转换传入的值类型为字段类型 + switch fieldType.Kind() { + case reflect.String: + if value == nil { + fieldValue.SetString("") + } else { + fieldValue.SetString(fmt.Sprintf("%v", value)) + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + intValue, err := strconv.ParseInt(fmt.Sprintf("%v", value), 10, 64) + if err != nil { + intValue = 0 + } + fieldValue.SetInt(intValue) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + uintValue, err := strconv.ParseUint(fmt.Sprintf("%v", value), 10, 64) + if err != nil { + uintValue = 0 + } + fieldValue.SetUint(uintValue) + case reflect.Float32, reflect.Float64: + floatValue, err := strconv.ParseFloat(fmt.Sprintf("%v", value), 64) + if err != nil { + floatValue = 0 + } + fieldValue.SetFloat(floatValue) + default: + // 设置字段的值 + fieldValue.Set(reflect.ValueOf(value).Convert(fieldValue.Type())) + } + } +} + +// ConvertIdsSlice 将 []string 转换为 []any +func ConvertIdsSlice(ids []string) []any { + // 将 []string 转换为 []any + arr := make([]any, len(ids)) + for i, v := range ids { + arr[i] = v + } + return arr +} + +// 查询-参数值的占位符 +func KeyPlaceholderByQuery(sum int) string { + placeholders := make([]string, sum) + for i := 0; i < sum; i++ { + placeholders[i] = "?" + } + return strings.Join(placeholders, ",") +} + +// 插入-参数映射键值占位符 keys, placeholder, values +func KeyPlaceholderValueByInsert(params map[string]any) ([]string, string, []any) { + // 参数映射的键 + keys := make([]string, len(params)) + // 参数映射的值 + values := make([]any, len(params)) + sum := 0 + for k, v := range params { + keys[sum] = k + values[sum] = v + sum++ + } + // 参数值的占位符 + placeholders := make([]string, sum) + for i := 0; i < sum; i++ { + placeholders[i] = "?" + } + return keys, strings.Join(placeholders, ","), values +} + +// 更新-参数映射键值占位符 keys, values +func KeyValueByUpdate(params map[string]any) ([]string, []any) { + // 参数映射的键 + keys := make([]string, len(params)) + // 参数映射的值 + values := make([]any, len(params)) + sum := 0 + for k, v := range params { + keys[sum] = k + "=?" + values[sum] = v + sum++ + } + return keys, values +} diff --git a/src/framework/utils/token/token.go b/src/framework/utils/token/token.go new file mode 100644 index 00000000..6815eb23 --- /dev/null +++ b/src/framework/utils/token/token.go @@ -0,0 +1,151 @@ +package token + +import ( + "encoding/json" + "errors" + "time" + + "ems.agt/src/framework/config" + cachekeyConstants "ems.agt/src/framework/constants/cachekey" + tokenConstants "ems.agt/src/framework/constants/token" + "ems.agt/src/framework/logger" + redisCahe "ems.agt/src/framework/redis" + "ems.agt/src/framework/utils/generate" + "ems.agt/src/framework/vo" + + jwt "github.com/golang-jwt/jwt/v5" +) + +// Remove 清除登录用户信息UUID +func Remove(tokenStr string) string { + claims, err := Verify(tokenStr) + if err != nil { + logger.Errorf("token verify err %v", err) + return "" + } + // 清除缓存KEY + uuid := claims[tokenConstants.JWT_UUID].(string) + tokenKey := cachekeyConstants.LOGIN_TOKEN_KEY + uuid + hasKey, _ := redisCahe.Has("", tokenKey) + if hasKey { + redisCahe.Del("", tokenKey) + } + return claims[tokenConstants.JWT_NAME].(string) +} + +// Create 令牌生成 +func Create(loginUser *vo.LoginUser, ilobArgs ...string) string { + // 生成用户唯一tokne32位 + loginUser.UUID = generate.Code(32) + + // 设置请求用户登录客户端 + loginUser.IPAddr = ilobArgs[0] + loginUser.LoginLocation = ilobArgs[1] + loginUser.OS = ilobArgs[2] + loginUser.Browser = ilobArgs[3] + + // 设置用户令牌有效期并存入缓存 + Cache(loginUser) + + // 令牌算法 HS256 HS384 HS512 + algorithm := config.Get("jwt.algorithm").(string) + var method *jwt.SigningMethodHMAC + switch algorithm { + case "HS512": + method = jwt.SigningMethodHS512 + case "HS384": + method = jwt.SigningMethodHS384 + case "HS256": + default: + method = jwt.SigningMethodHS256 + } + // 生成令牌负荷绑定uuid标识 + jwtToken := jwt.NewWithClaims(method, jwt.MapClaims{ + tokenConstants.JWT_UUID: loginUser.UUID, + tokenConstants.JWT_KEY: loginUser.UserID, + tokenConstants.JWT_NAME: loginUser.User.UserName, + "exp": loginUser.ExpireTime, + "ait": loginUser.LoginTime, + }) + + // 生成令牌设置密钥 + secret := config.Get("jwt.secret").(string) + tokenStr, err := jwtToken.SignedString([]byte(secret)) + if err != nil { + logger.Infof("jwt sign err : %v", err) + return "" + } + return tokenStr +} + +// Cache 缓存登录用户信息 +func Cache(loginUser *vo.LoginUser) { + // 计算配置的有效期 + expTime := config.Get("jwt.expiresIn").(int) + expTimestamp := time.Duration(expTime) * time.Minute + iatTimestamp := time.Now().UnixMilli() + loginUser.LoginTime = iatTimestamp + loginUser.ExpireTime = iatTimestamp + expTimestamp.Milliseconds() + // 根据登录标识将loginUser缓存 + tokenKey := cachekeyConstants.LOGIN_TOKEN_KEY + loginUser.UUID + jsonBytes, err := json.Marshal(loginUser) + if err != nil { + return + } + redisCahe.SetByExpire("", tokenKey, string(jsonBytes), expTimestamp) +} + +// RefreshIn 验证令牌有效期,相差不足xx分钟,自动刷新缓存 +func RefreshIn(loginUser *vo.LoginUser) { + // 相差不足xx分钟,自动刷新缓存 + refreshTime := config.Get("jwt.refreshIn").(int) + refreshTimestamp := time.Duration(refreshTime) * time.Minute + // 过期时间 + expireTimestamp := loginUser.ExpireTime + currentTimestamp := time.Now().UnixMilli() + if expireTimestamp-currentTimestamp <= refreshTimestamp.Milliseconds() { + Cache(loginUser) + } +} + +// Verify 校验令牌是否有效 +func Verify(tokenString string) (jwt.MapClaims, error) { + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) { + // 判断加密算法是预期的加密算法 + if _, ok := token.Method.(*jwt.SigningMethodHMAC); ok { + secret := config.Get("jwt.secret").(string) + return []byte(secret), nil + } + return nil, jwt.ErrSignatureInvalid + }) + if err != nil { + logger.Errorf("token String Verify : %v", err) + return nil, errors.New("无效身份授权") + } + // 如果解析负荷成功并通过签名校验 + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + return claims, nil + } + return nil, errors.New("token valid error") +} + +// LoginUser 缓存的登录用户信息 +func LoginUser(claims jwt.MapClaims) vo.LoginUser { + uuid := claims[tokenConstants.JWT_UUID].(string) + tokenKey := cachekeyConstants.LOGIN_TOKEN_KEY + uuid + hasKey, _ := redisCahe.Has("", tokenKey) + var loginUser vo.LoginUser + if hasKey { + loginUserStr, _ := redisCahe.Get("", tokenKey) + if loginUserStr == "" { + return loginUser + } + err := json.Unmarshal([]byte(loginUserStr), &loginUser) + if err != nil { + logger.Errorf("loginuser info json err : %v", err) + return loginUser + } + return loginUser + } + return loginUser +} diff --git a/src/framework/utils/ua/ua.go b/src/framework/utils/ua/ua.go new file mode 100644 index 00000000..1a125315 --- /dev/null +++ b/src/framework/utils/ua/ua.go @@ -0,0 +1,8 @@ +package ua + +import "github.com/mssola/user_agent" + +// 获取user-agent信息 +func Info(userAgent string) *user_agent.UserAgent { + return user_agent.New(userAgent) +} diff --git a/src/framework/vo/loginuser.go b/src/framework/vo/loginuser.go new file mode 100644 index 00000000..823d7a6e --- /dev/null +++ b/src/framework/vo/loginuser.go @@ -0,0 +1,39 @@ +package vo + +import systemModel "ems.agt/src/modules/system/model" + +// LoginUser 登录用户身份权限信息对象 +type LoginUser struct { + // UserID 用户ID + UserID string `json:"userId"` + + // DeptID 部门ID + DeptID string `json:"deptId"` + + // UUID 用户唯一标识 + UUID string `json:"uuid"` + + // LoginTime 登录时间时间戳 + LoginTime int64 `json:"loginTime"` + + // ExpireTime 过期时间时间戳 + ExpireTime int64 `json:"expireTime"` + + // IPAddr 登录IP地址 x.x.x.x + IPAddr string `json:"ipaddr"` + + // LoginLocation 登录地点 xx xx + LoginLocation string `json:"loginLocation"` + + // Browser 浏览器类型 + Browser string `json:"browser"` + + // OS 操作系统 + OS string `json:"os"` + + // Permissions 权限列表 + Permissions []string `json:"permissions"` + + // User 用户信息 + User systemModel.SysUser `json:"user"` +} diff --git a/src/framework/vo/result/result.go b/src/framework/vo/result/result.go new file mode 100644 index 00000000..e10b53c6 --- /dev/null +++ b/src/framework/vo/result/result.go @@ -0,0 +1,71 @@ +package result + +import ( + "ems.agt/src/framework/constants/result" +) + +// CodeMsg 响应结果 +func CodeMsg(code int, msg string) map[string]any { + args := make(map[string]any) + args["code"] = code + args["msg"] = msg + return args +} + +// 响应成功结果 map[string]any{} +func Ok(v map[string]any) map[string]any { + args := make(map[string]any) + args["code"] = result.CODE_SUCCESS + args["msg"] = result.MSG_SUCCESS + // v合并到args + for key, value := range v { + args[key] = value + } + return args +} + +// 响应成功结果信息 +func OkMsg(msg string) map[string]any { + args := make(map[string]any) + args["code"] = result.CODE_SUCCESS + args["msg"] = msg + return args +} + +// 响应成功结果数据 +func OkData(data any) map[string]any { + args := make(map[string]any) + args["code"] = result.CODE_SUCCESS + args["msg"] = result.MSG_SUCCESS + args["data"] = data + return args +} + +// 响应失败结果 map[string]any{} +func Err(v map[string]any) map[string]any { + args := make(map[string]any) + args["code"] = result.CODE_ERROR + args["msg"] = result.MSG_ERROR + // v合并到args + for key, value := range v { + args[key] = value + } + return args +} + +// 响应失败结果信息 +func ErrMsg(msg string) map[string]any { + args := make(map[string]any) + args["code"] = result.CODE_ERROR + args["msg"] = msg + return args +} + +// 响应失败结果数据 +func ErrData(data any) map[string]any { + args := make(map[string]any) + args["code"] = result.CODE_ERROR + args["msg"] = result.MSG_ERROR + args["data"] = data + return args +} diff --git a/src/framework/vo/router.go b/src/framework/vo/router.go new file mode 100644 index 00000000..a3cc4083 --- /dev/null +++ b/src/framework/vo/router.go @@ -0,0 +1,17 @@ +package vo + +// Router 路由信息对象 +type Router struct { + // 路由名字 英文首字母大写 + Name string `json:"name"` + // 路由地址 + Path string `json:"path"` + // 其他元素 + Meta RouterMeta `json:"meta"` + // 组件地址 + Component string `json:"component"` + // 重定向地址 + Redirect string `json:"redirect"` + // 子路由 + Children []Router `json:"children,omitempty"` +} diff --git a/src/framework/vo/router_meta.go b/src/framework/vo/router_meta.go new file mode 100644 index 00000000..b3447e05 --- /dev/null +++ b/src/framework/vo/router_meta.go @@ -0,0 +1,17 @@ +package vo + +// RouterMeta 路由元信息对象 +type RouterMeta struct { + // 设置该菜单在侧边栏和面包屑中展示的名字 + Title string `json:"title"` + // 设置该菜单的图标 + Icon string `json:"icon"` + // 设置为true,则不会被 缓存 + Cache bool `json:"cache"` + // 内链地址(http(s)://开头), 打开目标位置 '_blank' | '_self' | '' + Target string `json:"target"` + // 在菜单中隐藏子节点 + HideChildInMenu bool `json:"hideChildInMenu"` + // 在菜单中隐藏自己和子节点 + HideInMenu bool `json:"hideInMenu"` +} diff --git a/src/framework/vo/treeselect.go b/src/framework/vo/treeselect.go new file mode 100644 index 00000000..ea86ba95 --- /dev/null +++ b/src/framework/vo/treeselect.go @@ -0,0 +1,51 @@ +package vo + +import systemModel "ems.agt/src/modules/system/model" + +// TreeSelect 树结构实体类 +type TreeSelect struct { + // ID 节点ID + ID string `json:"id"` + + // Label 节点名称 + Label string `json:"label"` + + // Children 子节点 + Children []TreeSelect `json:"children"` +} + +// SysMenuTreeSelect 使用给定的 SysMenu 对象解析为 TreeSelect 对象 +func SysMenuTreeSelect(sysMenu systemModel.SysMenu) TreeSelect { + t := TreeSelect{} + t.ID = sysMenu.MenuID + t.Label = sysMenu.MenuName + + if len(sysMenu.Children) > 0 { + for _, menu := range sysMenu.Children { + child := SysMenuTreeSelect(menu) + t.Children = append(t.Children, child) + } + } else { + t.Children = []TreeSelect{} + } + + return t +} + +// SysDeptTreeSelect 使用给定的 SysDept 对象解析为 TreeSelect 对象 +func SysDeptTreeSelect(sysDept systemModel.SysDept) TreeSelect { + t := TreeSelect{} + t.ID = sysDept.DeptID + t.Label = sysDept.DeptName + + if len(sysDept.Children) > 0 { + for _, dept := range sysDept.Children { + child := SysDeptTreeSelect(dept) + t.Children = append(t.Children, child) + } + } else { + t.Children = []TreeSelect{} + } + + return t +} diff --git a/src/lib_features/account/account.go b/src/lib_features/account/account.go new file mode 100644 index 00000000..7d4af0d8 --- /dev/null +++ b/src/lib_features/account/account.go @@ -0,0 +1,32 @@ +package libfeatures + +import ( + "time" + + "ems.agt/lib/dborm" + "ems.agt/lib/oauth" + libConfig "ems.agt/restagent/config" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/redis" +) + +// SessionToken 设置登录会话-兼容旧登录方式 +func SessionToken(username, sourceAddr string) bool { + token, _ := redis.Get("", "session_token") + if token != "" { + return true + } + + token = oauth.GenRandToken("omc") // Generate new token to session ID + affected, err := dborm.XormInsertSession(username, sourceAddr, token, libConfig.GetExpiresFromConfig(), libConfig.GetYamlConfig().Auth.Session) + if err != nil { + logger.Errorf("SessionToken XormInsertSession err %v", err) + } + if affected == 1 { + // 过期时间单位秒 配置1800是半小时 + expireTime := time.Duration(int64(libConfig.GetExpiresFromConfig())) * time.Second + redis.SetByExpire("", "session_token", token, expireTime) + return true + } + return false +} diff --git a/src/lib_features/config/config.go b/src/lib_features/config/config.go new file mode 100644 index 00000000..a56a7574 --- /dev/null +++ b/src/lib_features/config/config.go @@ -0,0 +1,53 @@ +package libfeatures + +import ( + "fmt" + + libConf "ems.agt/lib/core/conf" + libGlobal "ems.agt/lib/global" + libConfig "ems.agt/restagent/config" + "github.com/spf13/viper" +) + +// BuildInfo 程序-V查看编译版本号信息 +func BuildInfo() string { + return fmt.Sprintf("OMC restagent version: %s\n%s\n%s\n\n", libGlobal.Version, libGlobal.BuildTime, libGlobal.GoVer) +} + +// ConfigRead 指定配置文件读取 +func ConfigRead(configFile string) { + // 外层lib和features使用的配置 + libConfig.ReadConfig(configFile) + uriPrefix := libConfig.GetYamlConfig().OMC.UriPrefix + if uriPrefix != "" { + libConfig.UriPrefix = uriPrefix + } + if libConfig.GetYamlConfig().TestConfig.Enabled { + libConfig.ReadTestConfigYaml(libConfig.GetYamlConfig().TestConfig.File) + } + // 外层lib和features使用配置 + libConf.InitConfig(configFile) +} + +// 配置文件读取进行内部参数合并 +func ConfigInMerge() { + // 合并外层lib和features使用配置 + for key, value := range libConf.AllSettings() { + // 跳过配置 + if key == "testconfig" || key == "rest" || key == "logger" { + continue + } + // 数据库配置 + if key == "database" { + item := value.(map[string]any) + defaultItem := viper.GetStringMap("gorm.datasource.default") + defaultItem["host"] = item["host"] + defaultItem["port"] = item["port"] + defaultItem["username"] = item["user"] + defaultItem["password"] = item["password"] + defaultItem["database"] = item["name"] + continue + } + viper.Set(key, value) + } +} diff --git a/src/lib_features/readme.md b/src/lib_features/readme.md new file mode 100644 index 00000000..8e91940b --- /dev/null +++ b/src/lib_features/readme.md @@ -0,0 +1,5 @@ +# 外层 lib 和 features 粘合层 + +- config.go 配置合并: restagent.yaml 文件内容,主要是数据库配置 +- account.go 登录会话生成 token +- session.go 中间件方式设置请求头 token 值 diff --git a/src/lib_features/session/session.go b/src/lib_features/session/session.go new file mode 100644 index 00000000..e7c4a110 --- /dev/null +++ b/src/lib_features/session/session.go @@ -0,0 +1,23 @@ +package session + +import ( + "ems.agt/src/framework/redis" + + "github.com/gin-gonic/gin" +) + +// SessionHeader 旧登录方式token头 +func SessionHeader() gin.HandlerFunc { + return func(c *gin.Context) { + // 读取登录生成的会话token + token, err := redis.Get("", "session_token") + if err == nil { + c.Request.Header.Set("Accesstoken", token) + } + + // Accesstoken: omc-ce4d0a86-8515-ad51-3249-4913c95f8e34 + // 调用下一个处理程序 + c.Next() + + } +} diff --git a/src/modules/common/common.go b/src/modules/common/common.go new file mode 100644 index 00000000..a8090847 --- /dev/null +++ b/src/modules/common/common.go @@ -0,0 +1,85 @@ +package common + +import ( + "ems.agt/src/framework/logger" + "ems.agt/src/framework/middleware" + "ems.agt/src/modules/common/controller" + + "github.com/gin-gonic/gin" +) + +// 模块路由注册 +func Setup(router *gin.Engine) { + logger.Infof("开始加载 ====> common 模块路由") + + // 路由主页 + indexGroup := router.Group("/") + indexGroup.GET("", + middleware.RateLimit(middleware.LimitOption{ + Time: 300, + Count: 10, + Type: middleware.LIMIT_IP, + }), + controller.NewIndex.Handler, + ) + + // 验证码操作处理 + indexGroup.GET("/captchaImage", + middleware.RateLimit(middleware.LimitOption{ + Time: 300, + Count: 60, + Type: middleware.LIMIT_IP, + }), + controller.NewCaptcha.Image, + ) + + // 账号身份操作处理 + { + indexGroup.POST("/login", + middleware.RateLimit(middleware.LimitOption{ + Time: 300, + Count: 10, + Type: middleware.LIMIT_IP, + }), + controller.NewAccount.Login, + ) + indexGroup.GET("/getInfo", middleware.PreAuthorize(nil), controller.NewAccount.Info) + indexGroup.GET("/getRouters", middleware.PreAuthorize(nil), controller.NewAccount.Router) + indexGroup.POST("/logout", + middleware.RateLimit(middleware.LimitOption{ + Time: 300, + Count: 5, + Type: middleware.LIMIT_IP, + }), + controller.NewAccount.Logout, + ) + } + + // 账号注册操作处理 + { + indexGroup.POST("/register", + middleware.RateLimit(middleware.LimitOption{ + Time: 300, + Count: 10, + Type: middleware.LIMIT_IP, + }), + controller.NewRegister.UserName, + ) + } + + // 通用请求 + commonGroup := router.Group("/common") + { + commonGroup.GET("/hash", middleware.PreAuthorize(nil), controller.NewCommont.Hash) + } + + // 文件操作处理 + fileGroup := router.Group("/file") + { + fileGroup.GET("/download/:filePath", middleware.PreAuthorize(nil), controller.NewFile.Download) + fileGroup.POST("/upload", middleware.PreAuthorize(nil), controller.NewFile.Upload) + fileGroup.POST("/chunkCheck", middleware.PreAuthorize(nil), controller.NewFile.ChunkCheck) + fileGroup.POST("/chunkUpload", middleware.PreAuthorize(nil), controller.NewFile.ChunkUpload) + fileGroup.POST("/chunkMerge", middleware.PreAuthorize(nil), controller.NewFile.ChunkMerge) + } +} diff --git a/src/modules/common/controller/account.go b/src/modules/common/controller/account.go new file mode 100644 index 00000000..4e137aa1 --- /dev/null +++ b/src/modules/common/controller/account.go @@ -0,0 +1,144 @@ +package controller + +import ( + "ems.agt/src/framework/config" + commonConstants "ems.agt/src/framework/constants/common" + tokenConstants "ems.agt/src/framework/constants/token" + ctxUtils "ems.agt/src/framework/utils/ctx" + tokenUtils "ems.agt/src/framework/utils/token" + "ems.agt/src/framework/vo/result" + libAccount "ems.agt/src/lib_features/account" + commonModel "ems.agt/src/modules/common/model" + commonService "ems.agt/src/modules/common/service" + systemService "ems.agt/src/modules/system/service" + "github.com/gin-gonic/gin" +) + +// 实例化控制层 AccountController 结构体 +var NewAccount = &AccountController{ + accountService: commonService.NewAccountImpl, + sysLogLoginService: systemService.NewSysLogLoginImpl, +} + +// 账号身份操作处理 +// +// PATH / +type AccountController struct { + // 账号身份操作服务 + accountService commonService.IAccount + // 系统登录访问 + sysLogLoginService systemService.ISysLogLogin +} + +// 系统登录 +// +// POST /login +func (s *AccountController) Login(c *gin.Context) { + var loginBody commonModel.LoginBody + if err := c.ShouldBindJSON(&loginBody); err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 当前请求信息 + ipaddr, location := ctxUtils.IPAddrLocation(c) + os, browser := ctxUtils.UaOsBrowser(c) + + // 校验验证码 + err := s.accountService.ValidateCaptcha( + loginBody.Code, + loginBody.UUID, + ) + // 根据错误信息,创建系统访问记录 + if err != nil { + msg := err.Error() + " " + loginBody.Code + s.sysLogLoginService.NewSysLogLogin( + loginBody.Username, commonConstants.STATUS_NO, msg, + ipaddr, location, os, browser, + ) + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + // 登录用户信息 + loginUser, err := s.accountService.LoginByUsername(loginBody.Username, loginBody.Password) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + // 生成令牌,创建系统访问记录 + tokenStr := tokenUtils.Create(&loginUser, ipaddr, location, os, browser) + if tokenStr == "" { + c.JSON(200, result.Err(nil)) + return + } else { + s.sysLogLoginService.NewSysLogLogin( + loginBody.Username, commonConstants.STATUS_YES, "登录成功", + ipaddr, location, os, browser, + ) + } + + // 设置登录会话-兼容旧登录方式 + libAccount.SessionToken(loginBody.Username, ipaddr) + + c.JSON(200, result.OkData(map[string]any{ + tokenConstants.RESPONSE_FIELD: tokenStr, + })) +} + +// 登录用户信息 +// +// GET /getInfo +func (s *AccountController) Info(c *gin.Context) { + loginUser, err := ctxUtils.LoginUser(c) + if err != nil { + c.JSON(401, result.CodeMsg(401, err.Error())) + return + } + + // 角色权限集合,管理员拥有所有权限 + isAdmin := config.IsAdmin(loginUser.UserID) + roles, perms := s.accountService.RoleAndMenuPerms(loginUser.UserID, isAdmin) + + c.JSON(200, result.OkData(map[string]any{ + "user": loginUser.User, + "roles": roles, + "permissions": perms, + })) +} + +// 登录用户路由信息 +// +// GET /getRouters +func (s *AccountController) Router(c *gin.Context) { + userID := ctxUtils.LoginUserToUserID(c) + + // 前端路由,管理员拥有所有 + isAdmin := config.IsAdmin(userID) + buildMenus := s.accountService.RouteMenus(userID, isAdmin) + c.JSON(200, result.OkData(buildMenus)) +} + +// 系统登出 +// +// POST /logout +func (s *AccountController) Logout(c *gin.Context) { + tokenStr := ctxUtils.Authorization(c) + if tokenStr != "" { + // 存在token时记录退出信息 + userName := tokenUtils.Remove(tokenStr) + if userName != "" { + // 当前请求信息 + ipaddr, location := ctxUtils.IPAddrLocation(c) + os, browser := ctxUtils.UaOsBrowser(c) + // 创建系统访问记录 + s.sysLogLoginService.NewSysLogLogin( + userName, commonConstants.STATUS_NO, "退出成功", + ipaddr, location, os, browser, + ) + } + } + + c.JSON(200, result.OkMsg("退出成功")) +} diff --git a/src/modules/common/controller/captcha.go b/src/modules/common/controller/captcha.go new file mode 100644 index 00000000..1a198839 --- /dev/null +++ b/src/modules/common/controller/captcha.go @@ -0,0 +1,129 @@ +package controller + +import ( + "time" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/constants/cachekey" + "ems.agt/src/framework/constants/captcha" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/redis" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + systemService "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/mojocn/base64Captcha" +) + +// 实例化控制层 CaptchaController 结构体 +var NewCaptcha = &CaptchaController{ + sysConfigService: systemService.NewSysConfigImpl, +} + +// 验证码操作处理 +// +// PATH / +type CaptchaController struct { + // 参数配置服务 + sysConfigService systemService.ISysConfig +} + +// 获取验证码 +// +// GET /captchaImage +func (s *CaptchaController) Image(c *gin.Context) { + // 从数据库配置获取验证码开关 true开启,false关闭 + captchaEnabledStr := s.sysConfigService.SelectConfigValueByKey("sys.account.captchaEnabled") + captchaEnabled := parse.Boolean(captchaEnabledStr) + if !captchaEnabled { + c.JSON(200, result.Ok(map[string]any{ + "captchaEnabled": captchaEnabled, + })) + return + } + + // 生成唯一标识 + verifyKey := "" + data := map[string]any{ + "captchaEnabled": captchaEnabled, + "uuid": "", + "img": "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", + } + + // 从数据库配置获取验证码类型 math 数值计算 char 字符验证 + captchaType := s.sysConfigService.SelectConfigValueByKey("sys.account.captchaType") + if captchaType == captcha.TYPE_MATH { + math := config.Get("mathCaptcha").(map[string]any) + driverCaptcha := &base64Captcha.DriverMath{ + //Height png height in pixel. + Height: math["height"].(int), + // Width Captcha png width in pixel. + Width: math["width"].(int), + //NoiseCount text noise count. + NoiseCount: math["noise"].(int), + //ShowLineOptions := OptionShowHollowLine | OptionShowSlimeLine | OptionShowSineLine . + ShowLineOptions: base64Captcha.OptionShowHollowLine, + } + if math["color"].(bool) { + //BgColor captcha image background color (optional) + driverCaptcha.BgColor = parse.Color(math["background"].(string)) + } + // 验证码生成 + id, question, answer := driverCaptcha.GenerateIdQuestionAnswer() + // 验证码表达式解析输出 + item, err := driverCaptcha.DrawCaptcha(question) + if err != nil { + logger.Infof("Generate Id Question Answer %s %s : %v", captchaType, question, err) + } else { + data["uuid"] = id + data["img"] = item.EncodeB64string() + expiration := captcha.EXPIRATION * time.Second + verifyKey = cachekey.CAPTCHA_CODE_KEY + id + redis.SetByExpire("", verifyKey, answer, expiration) + } + } + if captchaType == captcha.TYPE_CHAR { + char := config.Get("charCaptcha").(map[string]any) + driverCaptcha := &base64Captcha.DriverString{ + //Height png height in pixel. + Height: char["height"].(int), + // Width Captcha png width in pixel. + Width: char["width"].(int), + //NoiseCount text noise count. + NoiseCount: char["noise"].(int), + //Length random string length. + Length: char["size"].(int), + //Source is a unicode which is the rand string from. + Source: char["chars"].(string), + //ShowLineOptions := OptionShowHollowLine | OptionShowSlimeLine | OptionShowSineLine . + ShowLineOptions: base64Captcha.OptionShowHollowLine, + } + if char["color"].(bool) { + //BgColor captcha image background color (optional) + driverCaptcha.BgColor = parse.Color(char["background"].(string)) + } + // 验证码生成 + id, question, answer := driverCaptcha.GenerateIdQuestionAnswer() + // 验证码表达式解析输出 + item, err := driverCaptcha.DrawCaptcha(question) + if err != nil { + logger.Infof("Generate Id Question Answer %s %s : %v", captchaType, question, err) + } else { + data["uuid"] = id + data["img"] = item.EncodeB64string() + expiration := captcha.EXPIRATION * time.Second + verifyKey = cachekey.CAPTCHA_CODE_KEY + id + redis.SetByExpire("", verifyKey, answer, expiration) + } + } + + // 本地开发下返回验证码结果,方便接口调试 + if config.Env() == "local" { + text, _ := redis.Get("", verifyKey) + data["text"] = text + c.JSON(200, result.Ok(data)) + return + } + c.JSON(200, result.Ok(data)) +} diff --git a/src/modules/common/controller/common.go b/src/modules/common/controller/common.go new file mode 100644 index 00000000..b3c2708b --- /dev/null +++ b/src/modules/common/controller/common.go @@ -0,0 +1,20 @@ +package controller + +import ( + "github.com/gin-gonic/gin" +) + +// 实例化控制层 CommontController 结构体 +var NewCommont = &CommontController{} + +// 通用请求 +// +// PATH / +type CommontController struct{} + +// 哈希加密 +// +// GET /hash +func (s *CommontController) Hash(c *gin.Context) { + c.String(200, "commont Hash") +} diff --git a/src/modules/common/controller/file.go b/src/modules/common/controller/file.go new file mode 100644 index 00000000..c1f9076f --- /dev/null +++ b/src/modules/common/controller/file.go @@ -0,0 +1,185 @@ +package controller + +import ( + "encoding/base64" + "fmt" + "net/url" + "strings" + + "ems.agt/src/framework/constants/uploadsubpath" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/vo/result" + + "github.com/gin-gonic/gin" +) + +// 实例化控制层 FileController 结构体 +var NewFile = &FileController{} + +// 文件操作处理 +// +// PATH / +type FileController struct{} + +// 下载文件 +// +// GET /download/:filePath +func (s *FileController) Download(c *gin.Context) { + filePath := c.Param("filePath") + if len(filePath) < 8 { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // base64解析出地址 + decodedBytes, err := base64.StdEncoding.DecodeString(filePath) + if err != nil { + c.JSON(400, result.CodeMsg(400, err.Error())) + return + } + routerPath := string(decodedBytes) + // 地址文件名截取 + fileName := routerPath[strings.LastIndex(routerPath, "/")+1:] + + // 响应头 + c.Writer.Header().Set("Content-Disposition", `attachment; filename="`+url.QueryEscape(fileName)+`"`) + c.Writer.Header().Set("Accept-Ranges", "bytes") + c.Writer.Header().Set("Content-Type", "application/octet-stream") + + // 断点续传 + headerRange := c.GetHeader("Range") + resultMap, err := file.ReadUploadFileStream(routerPath, headerRange) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + if headerRange != "" { + c.Writer.Header().Set("Content-Range", fmt.Sprint(resultMap["range"])) + c.Writer.Header().Set("Content-Length", fmt.Sprint(resultMap["chunkSize"])) + c.Status(206) + } else { + c.Writer.Header().Set("Content-Length", fmt.Sprint(resultMap["fileSize"])) + c.Status(200) + + } + c.Writer.Write(resultMap["data"].([]byte)) +} + +// 上传文件 +// +// POST /upload +func (s *FileController) Upload(c *gin.Context) { + // 上传的文件 + formFile, err := c.FormFile("file") + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // 子路径 + subPath := c.PostForm("subPath") + if _, ok := uploadsubpath.UploadSubpath[subPath]; !ok { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 上传文件转存 + upFilePath, err := file.TransferUploadFile(formFile, subPath, nil) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + newFileName := upFilePath[strings.LastIndex(upFilePath, "/")+1:] + c.JSON(200, result.OkData(map[string]string{ + "url": "http://" + c.Request.Host + upFilePath, + "fileName": upFilePath, + "newFileName": newFileName, + "originalFileName": formFile.Filename, + })) +} + +// 切片文件检查 +// +// POST /chunkCheck +func (s *FileController) ChunkCheck(c *gin.Context) { + var body struct { + // 唯一标识 + Identifier string `json:"identifier" binding:"required"` + // 文件名 + FileName string `json:"fileName" binding:"required"` + } + err := c.ShouldBindJSON(&body) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 读取标识目录 + chunks, err := file.ChunkCheckFile(body.Identifier, body.FileName) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + c.JSON(200, result.OkData(chunks)) +} + +// 切片文件合并 +// +// POST /chunkMerge +func (s *FileController) ChunkMerge(c *gin.Context) { + var body struct { + // 唯一标识 + Identifier string `json:"identifier" binding:"required"` + // 文件名 + FileName string `json:"fileName" binding:"required"` + // 子路径类型 + SubPath string `json:"subPath" binding:"required"` + } + err := c.ShouldBindJSON(&body) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + if _, ok := uploadsubpath.UploadSubpath[body.SubPath]; !ok { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 切片文件合并 + mergeFilePath, err := file.ChunkMergeFile(body.Identifier, body.FileName, body.SubPath) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + newFileName := mergeFilePath[strings.LastIndex(mergeFilePath, "/")+1:] + c.JSON(200, result.OkData(map[string]string{ + "url": "http://" + c.Request.Host + mergeFilePath, + "fileName": mergeFilePath, + "newFileName": newFileName, + "originalFileName": body.FileName, + })) +} + +// 切片文件上传 +// +// POST /chunkUpload +func (s *FileController) ChunkUpload(c *gin.Context) { + // 切片编号 + index := c.PostForm("index") + // 切片唯一标识 + identifier := c.PostForm("identifier") + // 上传的文件 + formFile, err := c.FormFile("file") + if index == "" || identifier == "" || err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 上传文件转存 + chunkFilePath, err := file.TransferChunkUploadFile(formFile, index, identifier) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + c.JSON(206, result.OkData(chunkFilePath)) +} diff --git a/src/modules/common/controller/index.go b/src/modules/common/controller/index.go new file mode 100644 index 00000000..e295e10a --- /dev/null +++ b/src/modules/common/controller/index.go @@ -0,0 +1,28 @@ +package controller + +import ( + "fmt" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/vo/result" + + "github.com/gin-gonic/gin" +) + +// 实例化控制层 IndexController 结构体 +var NewIndex = &IndexController{} + +// 路由主页 +// +// PATH / +type IndexController struct{} + +// 根路由 +// +// GET / +func (s *IndexController) Handler(c *gin.Context) { + name := config.Get("framework.name").(string) + version := config.Get("framework.version").(string) + str := "欢迎使用%s后台管理框架,当前版本:%s,请通过前端管理地址访问。" + c.JSON(200, result.OkMsg(fmt.Sprintf(str, name, version))) +} diff --git a/src/modules/common/controller/register.go b/src/modules/common/controller/register.go new file mode 100644 index 00000000..2b3e945f --- /dev/null +++ b/src/modules/common/controller/register.go @@ -0,0 +1,88 @@ +package controller + +import ( + "strings" + + commonConstants "ems.agt/src/framework/constants/common" + ctxUtils "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/regular" + "ems.agt/src/framework/vo/result" + commonModel "ems.agt/src/modules/common/model" + commonService "ems.agt/src/modules/common/service" + systemService "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" +) + +// 实例化控制层 RegisterController 结构体 +var NewRegister = &RegisterController{ + registerService: commonService.NewRegisterImpl, + sysLogLoginService: systemService.NewSysLogLoginImpl, +} + +// 账号注册操作处理 +// +// PATH / +type RegisterController struct { + // 账号注册操作服务 + registerService commonService.IRegister + // 系统登录访问 + sysLogLoginService systemService.ISysLogLogin +} + +// 账号注册 +// +// GET /captchaImage +func (s *RegisterController) UserName(c *gin.Context) { + var registerBody commonModel.RegisterBody + if err := c.ShouldBindJSON(®isterBody); err != nil { + c.JSON(400, result.ErrMsg("参数错误")) + return + } + + // 判断必传参数 + if !regular.ValidUsername(registerBody.Username) { + c.JSON(200, result.ErrMsg("账号不能以数字开头,可包含大写小写字母,数字,且不少于5位")) + return + } + if !regular.ValidPassword(registerBody.Password) { + c.JSON(200, result.ErrMsg("登录密码至少包含大小写字母、数字、特殊符号,且不少于6位")) + return + } + if registerBody.Password != registerBody.ConfirmPassword { + c.JSON(200, result.ErrMsg("用户确认输入密码不一致")) + return + } + + // 当前请求信息 + ipaddr, location := ctxUtils.IPAddrLocation(c) + os, browser := ctxUtils.UaOsBrowser(c) + + // 校验验证码 + err := s.registerService.ValidateCaptcha( + registerBody.Code, + registerBody.UUID, + ) + // 根据错误信息,创建系统访问记录 + if err != nil { + msg := err.Error() + " " + registerBody.Code + s.sysLogLoginService.NewSysLogLogin( + registerBody.Username, commonConstants.STATUS_NO, msg, + ipaddr, location, os, browser, + ) + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + infoStr := s.registerService.ByUserName(registerBody.Username, registerBody.Password, registerBody.UserType) + if !strings.HasPrefix(infoStr, "注册") { + msg := registerBody.Username + " 注册成功 " + infoStr + s.sysLogLoginService.NewSysLogLogin( + registerBody.Username, commonConstants.STATUS_NO, msg, + ipaddr, location, os, browser, + ) + c.JSON(200, result.OkMsg("注册成功")) + return + } + c.JSON(200, result.ErrMsg(infoStr)) +} diff --git a/src/modules/common/model/login_body.go b/src/modules/common/model/login_body.go new file mode 100644 index 00000000..ec37763e --- /dev/null +++ b/src/modules/common/model/login_body.go @@ -0,0 +1,16 @@ +package model + +// LoginBody 用户登录对象 +type LoginBody struct { + // Username 用户名 + Username string `json:"username" binding:"required"` + + // Password 用户密码 + Password string `json:"password" binding:"required"` + + // Code 验证码 + Code string `json:"code"` + + // UUID 验证码唯一标识 + UUID string `json:"uuid"` +} diff --git a/src/modules/common/model/register_body.go b/src/modules/common/model/register_body.go new file mode 100644 index 00000000..4bf11142 --- /dev/null +++ b/src/modules/common/model/register_body.go @@ -0,0 +1,22 @@ +package model + +// RegisterBody 用户注册对象 +type RegisterBody struct { + // Username 用户名 + Username string `json:"username" binding:"required"` + + // Password 用户密码 + Password string `json:"password" binding:"required"` + + // ConfirmPassword 用户确认密码 + ConfirmPassword string `json:"confirmPassword" binding:"required"` + + // Code 验证码 + Code string `json:"code"` + + // UUID 验证码唯一标识 + UUID string `json:"uuid"` + + // UserType 标记用户类型 + UserType string `json:"userType"` +} diff --git a/src/modules/common/service/account.go b/src/modules/common/service/account.go new file mode 100644 index 00000000..5f8c42f8 --- /dev/null +++ b/src/modules/common/service/account.go @@ -0,0 +1,21 @@ +package service + +import "ems.agt/src/framework/vo" + +// 账号身份操作服务 服务层接口 +type IAccount interface { + // ValidateCaptcha 校验验证码 + ValidateCaptcha(code, uuid string) error + + // LoginByUsername 登录生成token + LoginByUsername(username, password string) (vo.LoginUser, error) + + // ClearLoginRecordCache 清除错误记录次数 + ClearLoginRecordCache(username string) bool + + // RoleAndMenuPerms 角色和菜单数据权限 + RoleAndMenuPerms(userId string, isAdmin bool) ([]string, []string) + + // RouteMenus 前端路由所需要的菜单 + RouteMenus(userId string, isAdmin bool) []vo.Router +} diff --git a/src/modules/common/service/account.impl.go b/src/modules/common/service/account.impl.go new file mode 100644 index 00000000..a598a833 --- /dev/null +++ b/src/modules/common/service/account.impl.go @@ -0,0 +1,167 @@ +package service + +import ( + "errors" + "fmt" + "time" + + "ems.agt/src/framework/config" + adminConstants "ems.agt/src/framework/constants/admin" + "ems.agt/src/framework/constants/cachekey" + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/redis" + "ems.agt/src/framework/utils/crypto" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo" + systemService "ems.agt/src/modules/system/service" +) + +// 实例化服务层 AccountImpl 结构体 +var NewAccountImpl = &AccountImpl{ + sysUserService: systemService.NewSysUserImpl, + sysConfigService: systemService.NewSysConfigImpl, + sysRoleService: systemService.NewSysRoleImpl, + sysMenuService: systemService.NewSysMenuImpl, +} + +// 账号身份操作服务 服务层处理 +type AccountImpl struct { + // 用户信息服务 + sysUserService systemService.ISysUser + // 参数配置服务 + sysConfigService systemService.ISysConfig + // 角色服务 + sysRoleService systemService.ISysRole + // 菜单服务 + sysMenuService systemService.ISysMenu +} + +// ValidateCaptcha 校验验证码 +func (s *AccountImpl) ValidateCaptcha(code, uuid string) error { + // 验证码检查,从数据库配置获取验证码开关 true开启,false关闭 + captchaEnabledStr := s.sysConfigService.SelectConfigValueByKey("sys.account.captchaEnabled") + if !parse.Boolean(captchaEnabledStr) { + return nil + } + if code == "" || uuid == "" { + return errors.New("验证码信息错误") + } + verifyKey := cachekey.CAPTCHA_CODE_KEY + uuid + captcha, _ := redis.Get("", verifyKey) + if captcha == "" { + return errors.New("验证码已失效") + } + redis.Del("", verifyKey) + if captcha != code { + return errors.New("验证码错误") + } + return nil +} + +// LoginByUsername 登录创建用户信息 +func (s *AccountImpl) LoginByUsername(username, password string) (vo.LoginUser, error) { + loginUser := vo.LoginUser{} + + // 检查密码重试次数 + retrykey, retryCount, lockTime, err := s.passwordRetryCount(username) + if err != nil { + return loginUser, err + } + + // 查询用户登录账号 + sysUser := s.sysUserService.SelectUserByUserName(username) + if sysUser.UserName != username { + return loginUser, errors.New("用户不存在或密码错误") + } + if sysUser.DelFlag == common.STATUS_YES { + return loginUser, errors.New("对不起,您的账号已被删除") + } + if sysUser.Status == common.STATUS_NO { + return loginUser, errors.New("对不起,您的账号已禁用") + } + + // 检验用户密码 + compareBool := crypto.BcryptCompare(password, sysUser.Password) + if !compareBool { + redis.SetByExpire("", retrykey, retryCount+1, lockTime) + return loginUser, errors.New("用户不存在或密码错误") + } else { + // 清除错误记录次数 + s.ClearLoginRecordCache(username) + } + + // 登录用户信息 + loginUser.UserID = sysUser.UserID + loginUser.DeptID = sysUser.DeptID + loginUser.User = sysUser + // 用户权限组标识 + isAdmin := config.IsAdmin(sysUser.UserID) + if isAdmin { + loginUser.Permissions = []string{adminConstants.PERMISSION} + } else { + perms := s.sysMenuService.SelectMenuPermsByUserId(sysUser.UserID) + loginUser.Permissions = parse.RemoveDuplicates(perms) + } + return loginUser, nil +} + +// ClearLoginRecordCache 清除错误记录次数 +func (s *AccountImpl) ClearLoginRecordCache(username string) bool { + cacheKey := cachekey.PWD_ERR_CNT_KEY + username + hasKey, _ := redis.Has("", cacheKey) + if hasKey { + delOk, _ := redis.Del("", cacheKey) + return delOk + } + return false +} + +// passwordRetryCount 密码重试次数 +func (s *AccountImpl) passwordRetryCount(username string) (string, int64, time.Duration, error) { + // 验证登录次数和错误锁定时间 + maxRetryCount := config.Get("user.password.maxRetryCount").(int) + lockTime := config.Get("user.password.lockTime").(int) + // 验证缓存记录次数 + retrykey := cachekey.PWD_ERR_CNT_KEY + username + retryCount, err := redis.Get("", retrykey) + if retryCount == "" || err != nil { + retryCount = "0" + } + // 是否超过错误值 + retryCountInt64 := parse.Number(retryCount) + if retryCountInt64 >= int64(maxRetryCount) { + msg := fmt.Sprintf("密码输入错误 %d 次,帐户锁定 %d 分钟", maxRetryCount, lockTime) + return retrykey, retryCountInt64, time.Duration(lockTime) * time.Minute, errors.New(msg) + } + return retrykey, retryCountInt64, time.Duration(lockTime) * time.Minute, nil +} + +// RoleAndMenuPerms 角色和菜单数据权限 +func (s *AccountImpl) RoleAndMenuPerms(userId string, isAdmin bool) ([]string, []string) { + if isAdmin { + return []string{adminConstants.ROLE_KEY}, []string{adminConstants.PERMISSION} + } else { + // 角色key + roleGroup := []string{} + roles := s.sysRoleService.SelectRoleListByUserId(userId) + for _, role := range roles { + roleGroup = append(roleGroup, role.RoleKey) + } + // 菜单权限key + perms := s.sysMenuService.SelectMenuPermsByUserId(userId) + return parse.RemoveDuplicates(roleGroup), parse.RemoveDuplicates(perms) + } +} + +// RouteMenus 前端路由所需要的菜单 +func (s *AccountImpl) RouteMenus(userId string, isAdmin bool) []vo.Router { + var buildMenus []vo.Router + if isAdmin { + menus := s.sysMenuService.SelectMenuTreeByUserId("*") + buildMenus = s.sysMenuService.BuildRouteMenus(menus, "") + } else { + menus := s.sysMenuService.SelectMenuTreeByUserId(userId) + buildMenus = s.sysMenuService.BuildRouteMenus(menus, "") + } + return buildMenus +} diff --git a/src/modules/common/service/register.go b/src/modules/common/service/register.go new file mode 100644 index 00000000..6570c50a --- /dev/null +++ b/src/modules/common/service/register.go @@ -0,0 +1,10 @@ +package service + +// 账号注册操作处理 服务层接口 +type IRegister interface { + // ValidateCaptcha 校验验证码 + ValidateCaptcha(code, uuid string) error + + // ByUserName 账号注册 + ByUserName(username, password, userType string) string +} diff --git a/src/modules/common/service/register.impl.go b/src/modules/common/service/register.impl.go new file mode 100644 index 00000000..5d536fcd --- /dev/null +++ b/src/modules/common/service/register.impl.go @@ -0,0 +1,100 @@ +package service + +import ( + "errors" + "fmt" + + "ems.agt/src/framework/constants/cachekey" + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/redis" + "ems.agt/src/framework/utils/parse" + systemModel "ems.agt/src/modules/system/model" + systemService "ems.agt/src/modules/system/service" +) + +// 实例化服务层 RegisterImpl 结构体 +var NewRegisterImpl = &RegisterImpl{ + sysUserService: systemService.NewSysUserImpl, + sysConfigService: systemService.NewSysConfigImpl, + sysRoleService: systemService.NewSysRoleImpl, +} + +// 账号注册操作处理 服务层处理 +type RegisterImpl struct { + // 用户信息服务 + sysUserService systemService.ISysUser + // 参数配置服务 + sysConfigService systemService.ISysConfig + // 角色服务 + sysRoleService systemService.ISysRole +} + +// ValidateCaptcha 校验验证码 +func (s *RegisterImpl) ValidateCaptcha(code, uuid string) error { + // 验证码检查,从数据库配置获取验证码开关 true开启,false关闭 + captchaEnabledStr := s.sysConfigService.SelectConfigValueByKey("sys.account.captchaEnabled") + if !parse.Boolean(captchaEnabledStr) { + return nil + } + if code == "" || uuid == "" { + return errors.New("验证码信息错误") + } + verifyKey := cachekey.CAPTCHA_CODE_KEY + uuid + captcha, err := redis.Get("", verifyKey) + if captcha == "" || err != nil { + return errors.New("验证码已失效") + } + redis.Del("", verifyKey) + if captcha != code { + return errors.New("验证码错误") + } + return nil +} + +// ByUserName 账号注册 +func (s *RegisterImpl) ByUserName(username, password, userType string) string { + // 检查用户登录账号是否唯一 + uniqueUserName := s.sysUserService.CheckUniqueUserName(username, "") + if !uniqueUserName { + return fmt.Sprintf("注册用户【%s】失败,注册账号已存在", username) + } + + sysUser := systemModel.SysUser{ + UserName: username, + NickName: username, // 昵称使用名称账号 + Password: password, // 原始密码 + Status: common.STATUS_YES, // 账号状态激活 + DeptID: "100", // 归属部门为根节点 + CreateBy: "注册", // 创建来源 + } + // 标记用户类型 + if userType == "" { + sysUser.UserType = "sys" + } + // 新增用户的角色管理 + sysUser.RoleIDs = s.registerRoleInit(userType) + // 新增用户的岗位管理 + sysUser.PostIDs = s.registerPostInit(userType) + + insertId := s.sysUserService.InsertUser(sysUser) + if insertId != "" { + return insertId + } + return "注册失败,请联系系统管理人员" +} + +// registerRoleInit 注册初始角色 +func (s *RegisterImpl) registerRoleInit(userType string) []string { + if userType == "sys" { + return []string{} + } + return []string{} +} + +// registerPostInit 注册初始岗位 +func (s *RegisterImpl) registerPostInit(userType string) []string { + if userType == "sys" { + return []string{} + } + return []string{} +} diff --git a/src/modules/monitor/controller/sys_cache.go b/src/modules/monitor/controller/sys_cache.go new file mode 100644 index 00000000..17312874 --- /dev/null +++ b/src/modules/monitor/controller/sys_cache.go @@ -0,0 +1,149 @@ +package controller + +import ( + "ems.agt/src/framework/constants/cachekey" + "ems.agt/src/framework/redis" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/monitor/model" + + "github.com/gin-gonic/gin" +) + +// 实例化控制层 SysCacheController 结构体 +var NewSysCache = &SysCacheController{} + +// 缓存监控信息 +// +// PATH /monitor/cache +type SysCacheController struct{} + +// Redis信息 +// +// GET / +func (s *SysCacheController) Info(c *gin.Context) { + c.JSON(200, result.OkData(map[string]any{ + "info": redis.Info(""), + "dbSize": redis.KeySize(""), + "commandStats": redis.CommandStats(""), + })) +} + +// 缓存名称列表 +// +// GET /getNames +func (s *SysCacheController) Names(c *gin.Context) { + caches := []model.SysCache{ + model.NewSysCacheNames("用户信息", cachekey.LOGIN_TOKEN_KEY), + model.NewSysCacheNames("配置信息", cachekey.SYS_CONFIG_KEY), + model.NewSysCacheNames("数据字典", cachekey.SYS_DICT_KEY), + model.NewSysCacheNames("验证码", cachekey.CAPTCHA_CODE_KEY), + model.NewSysCacheNames("防重提交", cachekey.REPEAT_SUBMIT_KEY), + model.NewSysCacheNames("限流处理", cachekey.RATE_LIMIT_KEY), + model.NewSysCacheNames("密码错误次数", cachekey.PWD_ERR_CNT_KEY), + } + c.JSON(200, result.OkData(caches)) +} + +// 缓存名称下键名列表 +// +// GET /getKeys/:cacheName +func (s *SysCacheController) Keys(c *gin.Context) { + cacheName := c.Param("cacheName") + if cacheName == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + caches := []model.SysCache{} + + // 遍历组装 + cacheKeys, _ := redis.GetKeys("", cacheName+":*") + for _, key := range cacheKeys { + caches = append(caches, model.NewSysCacheKeys(cacheName, key)) + } + + c.JSON(200, result.OkData(caches)) +} + +// 缓存内容 +// +// GET /getValue/:cacheName/:cacheKey +func (s *SysCacheController) Value(c *gin.Context) { + cacheName := c.Param("cacheName") + cacheKey := c.Param("cacheKey") + if cacheName == "" || cacheKey == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + cacheValue, err := redis.Get("", cacheName+":"+cacheKey) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + sysCache := model.NewSysCacheValue(cacheName, cacheKey, cacheValue) + c.JSON(200, result.OkData(sysCache)) +} + +// 删除缓存名称下键名列表 +// +// DELETE /clearCacheName/:cacheName +func (s *SysCacheController) ClearCacheName(c *gin.Context) { + cacheName := c.Param("cacheName") + if cacheName == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + cacheKeys, err := redis.GetKeys("", cacheName+":*") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + ok, _ := redis.DelKeys("", cacheKeys) + if ok { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 删除缓存键名 +// +// DELETE /clearCacheKey/:cacheName/:cacheKey +func (s *SysCacheController) ClearCacheKey(c *gin.Context) { + cacheName := c.Param("cacheName") + cacheKey := c.Param("cacheKey") + if cacheName == "" || cacheKey == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + ok, _ := redis.Del("", cacheName+":"+cacheKey) + if ok { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 安全清理缓存名称 +// +// DELETE /clearCacheSafe +func (s *SysCacheController) ClearCacheSafe(c *gin.Context) { + caches := []model.SysCache{ + model.NewSysCacheNames("配置信息", cachekey.SYS_CONFIG_KEY), + model.NewSysCacheNames("数据字典", cachekey.SYS_DICT_KEY), + model.NewSysCacheNames("验证码", cachekey.CAPTCHA_CODE_KEY), + model.NewSysCacheNames("防重提交", cachekey.REPEAT_SUBMIT_KEY), + model.NewSysCacheNames("限流处理", cachekey.RATE_LIMIT_KEY), + model.NewSysCacheNames("密码错误次数", cachekey.PWD_ERR_CNT_KEY), + } + for _, v := range caches { + cacheKeys, err := redis.GetKeys("", v.CacheName+":*") + if err != nil { + continue + } + redis.DelKeys("", cacheKeys) + } + c.JSON(200, result.Ok(nil)) +} diff --git a/src/modules/monitor/controller/sys_job.go b/src/modules/monitor/controller/sys_job.go new file mode 100644 index 00000000..31bed89e --- /dev/null +++ b/src/modules/monitor/controller/sys_job.go @@ -0,0 +1,338 @@ +package controller + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/monitor/model" + "ems.agt/src/modules/monitor/service" + systemService "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysJobLogController 结构体 +var NewSysJob = &SysJobController{ + sysJobService: service.NewSysJobImpl, + sysDictDataService: systemService.NewSysDictDataImpl, +} + +// 调度任务信息 +// +// PATH /monitor/job +type SysJobController struct { + // 调度任务服务 + sysJobService service.ISysJob + // 字典数据服务 + sysDictDataService systemService.ISysDictData +} + +// 调度任务列表 +// +// GET /list +func (s *SysJobController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + data := s.sysJobService.SelectJobPage(querys) + c.JSON(200, result.Ok(data)) +} + +// 调度任务信息 +// +// GET /:jobId +func (s *SysJobController) Info(c *gin.Context) { + jobId := c.Param("jobId") + if jobId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + data := s.sysJobService.SelectJobById(jobId) + if data.JobID == jobId { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 调度任务新增 +// +// POST / +func (s *SysJobController) Add(c *gin.Context) { + var body model.SysJob + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.JobID != "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查cron表达式格式 + if parse.CronExpression(body.CronExpression) == 0 { + msg := fmt.Sprintf("调度任务新增【%s】失败,Cron表达式不正确", body.JobName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查任务调用传入参数是否json格式 + if body.TargetParams != "" { + msg := fmt.Sprintf("调度任务新增【%s】失败,任务传入参数json字符串不正确", body.JobName) + if len(body.TargetParams) < 7 { + c.JSON(200, result.ErrMsg(msg)) + return + } + if !json.Valid([]byte(body.TargetParams)) { + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + // 检查属性值唯一 + uniqueJob := s.sysJobService.CheckUniqueJobName(body.JobName, body.JobGroup, "") + if !uniqueJob { + msg := fmt.Sprintf("调度任务新增【%s】失败,同任务组内有相同任务名称", body.JobName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysJobService.InsertJob(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 调度任务修改 +// +// PUT / +func (s *SysJobController) Edit(c *gin.Context) { + var body model.SysJob + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.JobID == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查cron表达式格式 + if parse.CronExpression(body.CronExpression) == 0 { + msg := fmt.Sprintf("调度任务修改【%s】失败,Cron表达式不正确", body.JobName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查任务调用传入参数是否json格式 + if body.TargetParams != "" { + msg := fmt.Sprintf("调度任务修改【%s】失败,任务传入参数json字符串不正确", body.JobName) + if len(body.TargetParams) < 7 { + c.JSON(200, result.ErrMsg(msg)) + return + } + if !json.Valid([]byte(body.TargetParams)) { + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + // 检查属性值唯一 + uniqueJob := s.sysJobService.CheckUniqueJobName(body.JobName, body.JobGroup, body.JobID) + if !uniqueJob { + msg := fmt.Sprintf("调度任务修改【%s】失败,同任务组内有相同任务名称", body.JobName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysJobService.UpdateJob(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 调度任务删除 +// +// DELETE /:jobIds +func (s *SysJobController) Remove(c *gin.Context) { + jobIds := c.Param("jobIds") + if jobIds == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(jobIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows, err := s.sysJobService.DeleteJobByIds(uniqueIDs) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) +} + +// 调度任务修改状态 +// +// PUT /changeStatus +func (s *SysJobController) Status(c *gin.Context) { + var body struct { + // 任务ID + JobId string `json:"jobId" binding:"required"` + // 状态 + Status string `json:"status" binding:"required"` + } + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否存在 + job := s.sysJobService.SelectJobById(body.JobId) + if job.JobID != body.JobId { + c.JSON(200, result.ErrMsg("没有权限访问调度任务数据!")) + return + } + + // 与旧值相等不变更 + if job.Status == body.Status { + c.JSON(200, result.ErrMsg("变更状态与旧值相等!")) + return + } + + // 更新状态 + job.Status = body.Status + job.UpdateBy = ctx.LoginUserToUserName(c) + ok := s.sysJobService.ChangeStatus(job) + if ok { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 调度任务立即执行一次 +// +// PUT /run/:jobId +func (s *SysJobController) Run(c *gin.Context) { + jobId := c.Param("jobId") + if jobId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否存在 + job := s.sysJobService.SelectJobById(jobId) + if job.JobID != jobId { + c.JSON(200, result.ErrMsg("没有权限访问调度任务数据!")) + return + } + + ok := s.sysJobService.RunQueueJob(job) + if ok { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 调度任务重置刷新队列 +// +// PUT /resetQueueJob +func (s *SysJobController) ResetQueueJob(c *gin.Context) { + s.sysJobService.ResetQueueJob() + c.JSON(200, result.Ok(nil)) +} + +// 导出调度任务信息 +// +// POST /export +func (s *SysJobController) Export(c *gin.Context) { + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + data := s.sysJobService.SelectJobPage(querys) + if data["total"].(int64) == 0 { + c.JSON(200, result.ErrMsg("导出数据记录为空")) + return + } + rows := data["rows"].([]model.SysJob) + + // 导出文件名称 + fileName := fmt.Sprintf("job_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "任务编号", + "B1": "任务名称", + "C1": "任务组名", + "D1": "调用目标", + "E1": "传入参数", + "F1": "执行表达式", + "G1": "出错策略", + "H1": "并发执行", + "I1": "任务状态", + "J1": "备注说明", + } + // 读取任务组名字典数据 + dictSysJobGroup := s.sysDictDataService.SelectDictDataByType("sys_job_group") + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 任务组名 + sysJobGroup := "" + for _, v := range dictSysJobGroup { + if row.JobGroup == v.DictValue { + sysJobGroup = v.DictLabel + break + } + } + misfirePolicy := "放弃执行" + if row.MisfirePolicy == "1" { + misfirePolicy = "立即执行" + } else if row.MisfirePolicy == "2" { + misfirePolicy = "执行一次" + } + concurrent := "禁止" + if row.Concurrent == "1" { + concurrent = "允许" + } + // 状态 + statusValue := "失败" + if row.Status == "1" { + statusValue = "成功" + } + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.JobID, + "B" + idx: row.JobName, + "C" + idx: sysJobGroup, + "D" + idx: row.InvokeTarget, + "E" + idx: row.TargetParams, + "F" + idx: row.CronExpression, + "G" + idx: misfirePolicy, + "H" + idx: concurrent, + "I" + idx: statusValue, + "J" + idx: row.Remark, + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} diff --git a/src/modules/monitor/controller/sys_job_log.go b/src/modules/monitor/controller/sys_job_log.go new file mode 100644 index 00000000..640b1fa4 --- /dev/null +++ b/src/modules/monitor/controller/sys_job_log.go @@ -0,0 +1,167 @@ +package controller + +import ( + "fmt" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/monitor/model" + "ems.agt/src/modules/monitor/service" + systemService "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" +) + +// 实例化控制层 SysJobLogController 结构体 +var NewSysJobLog = &SysJobLogController{ + sysJobLogService: service.NewSysJobLogImpl, + sysDictDataService: systemService.NewSysDictDataImpl, +} + +// 调度任务日志信息 +// +// PATH /monitor/jobLog +type SysJobLogController struct { + // 调度任务日志服务 + sysJobLogService service.ISysJobLog + // 字典数据服务 + sysDictDataService systemService.ISysDictData +} + +// 调度任务日志列表 +// +// GET /list +func (s *SysJobLogController) List(c *gin.Context) { + // 查询参数转换map + querys := ctx.QueryMap(c) + list := s.sysJobLogService.SelectJobLogPage(querys) + c.JSON(200, result.Ok(list)) +} + +// 调度任务日志信息 +// +// GET /:jobLogId +func (s *SysJobLogController) Info(c *gin.Context) { + jobLogId := c.Param("jobLogId") + if jobLogId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysJobLogService.SelectJobLogById(jobLogId) + if data.JobLogID == jobLogId { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 调度任务日志删除 +// +// DELETE /:jobLogIds +func (s *SysJobLogController) Remove(c *gin.Context) { + jobLogIds := c.Param("jobLogIds") + if jobLogIds == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 处理字符转id数组后去重 + ids := strings.Split(jobLogIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows := s.sysJobLogService.DeleteJobLogByIds(uniqueIDs) + if rows > 0 { + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 调度任务日志清空 +// +// DELETE /clean +func (s *SysJobLogController) Clean(c *gin.Context) { + err := s.sysJobLogService.CleanJobLog() + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + c.JSON(200, result.Ok(nil)) +} + +// 导出调度任务日志信息 +// +// POST /export +func (s *SysJobLogController) Export(c *gin.Context) { + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + data := s.sysJobLogService.SelectJobLogPage(querys) + if data["total"].(int64) == 0 { + c.JSON(200, result.ErrMsg("导出数据记录为空")) + return + } + rows := data["rows"].([]model.SysJobLog) + + // 导出文件名称 + fileName := fmt.Sprintf("jobLog_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "日志序号", + "B1": "任务名称", + "C1": "任务组名", + "D1": "调用目标", + "E1": "传入参数", + "F1": "日志信息", + "G1": "执行状态", + "H1": "记录时间", + } + // 读取任务组名字典数据 + dictSysJobGroup := s.sysDictDataService.SelectDictDataByType("sys_job_group") + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 任务组名 + sysJobGroup := "" + for _, v := range dictSysJobGroup { + if row.JobGroup == v.DictValue { + sysJobGroup = v.DictLabel + break + } + } + // 状态 + statusValue := "失败" + if row.Status == "1" { + statusValue = "成功" + } + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.JobLogID, + "B" + idx: row.JobName, + "C" + idx: sysJobGroup, + "D" + idx: row.InvokeTarget, + "E" + idx: row.TargetParams, + "F" + idx: row.JobMsg, + "G" + idx: statusValue, + "H" + idx: date.ParseDateToStr(row.CreateTime, date.YYYY_MM_DD_HH_MM_SS), + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} diff --git a/src/modules/monitor/controller/sys_user_online.go b/src/modules/monitor/controller/sys_user_online.go new file mode 100644 index 00000000..721ee67b --- /dev/null +++ b/src/modules/monitor/controller/sys_user_online.go @@ -0,0 +1,126 @@ +package controller + +import ( + "encoding/json" + "sort" + "strings" + + "ems.agt/src/framework/constants/cachekey" + "ems.agt/src/framework/redis" + "ems.agt/src/framework/vo" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/monitor/model" + "ems.agt/src/modules/monitor/service" + + "github.com/gin-gonic/gin" +) + +// 实例化控制层 SysUserOnlineController 结构体 +var NewSysUserOnline = &SysUserOnlineController{ + sysUserOnlineService: service.NewSysUserOnlineImpl, +} + +// 在线用户监控 +// +// PATH /monitor/online +type SysUserOnlineController struct { + // 在线用户服务 + sysUserOnlineService service.ISysUserOnline +} + +// 在线用户列表 +// +// GET /list +func (s *SysUserOnlineController) List(c *gin.Context) { + ipaddr := c.Query("ipaddr") + userName := c.Query("userName") + + // 获取所有在线用户key + keys, _ := redis.GetKeys("", cachekey.LOGIN_TOKEN_KEY+"*") + + // 分批获取 + arr := make([]string, 0) + for i := 0; i < len(keys); i += 20 { + end := i + 20 + if end > len(keys) { + end = len(keys) + } + chunk := keys[i:end] + values, _ := redis.GetBatch("", chunk) + for _, v := range values { + arr = append(arr, v.(string)) + } + } + + // 遍历字符串信息解析组合可用对象 + userOnlines := make([]model.SysUserOnline, 0) + for _, str := range arr { + if str == "" { + continue + } + + var loginUser vo.LoginUser + err := json.Unmarshal([]byte(str), &loginUser) + if err != nil { + continue + } + + onlineUser := s.sysUserOnlineService.LoginUserToUserOnline(loginUser) + if onlineUser.TokenID != "" { + userOnlines = append(userOnlines, onlineUser) + } + } + + // 根据查询条件过滤 + filteredUserOnlines := make([]model.SysUserOnline, 0) + if ipaddr != "" && userName != "" { + for _, o := range userOnlines { + if strings.Contains(o.IPAddr, ipaddr) && strings.Contains(o.UserName, userName) { + filteredUserOnlines = append(filteredUserOnlines, o) + } + } + } else if ipaddr != "" { + for _, o := range userOnlines { + if strings.Contains(o.IPAddr, ipaddr) { + filteredUserOnlines = append(filteredUserOnlines, o) + } + } + } else if userName != "" { + for _, o := range userOnlines { + if strings.Contains(o.UserName, userName) { + filteredUserOnlines = append(filteredUserOnlines, o) + } + } + } else { + filteredUserOnlines = userOnlines + } + + // 按登录时间排序 + sort.Slice(filteredUserOnlines, func(i, j int) bool { + return filteredUserOnlines[j].LoginTime > filteredUserOnlines[i].LoginTime + }) + + c.JSON(200, result.Ok(map[string]any{ + "total": len(filteredUserOnlines), + "rows": filteredUserOnlines, + })) +} + +// 在线用户强制退出 +// +// DELETE /:tokenId +func (s *SysUserOnlineController) ForceLogout(c *gin.Context) { + tokenId := c.Param("tokenId") + if tokenId == "" || tokenId == "*" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 删除token + ok, _ := redis.Del("", cachekey.LOGIN_TOKEN_KEY+tokenId) + if ok { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} diff --git a/src/modules/monitor/controller/system_info.go b/src/modules/monitor/controller/system_info.go new file mode 100644 index 00000000..13004e17 --- /dev/null +++ b/src/modules/monitor/controller/system_info.go @@ -0,0 +1,36 @@ +package controller + +import ( + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/monitor/service" + + "github.com/gin-gonic/gin" +) + +// 实例化控制层 SystemInfoController 结构体 +var NewSystemInfo = &SystemInfoController{ + systemInfogService: service.NewSystemInfoImpl, +} + +// 服务器监控信息 +// +// PATH /monitor/system-info +type SystemInfoController struct { + // 服务器系统相关信息服务 + systemInfogService service.ISystemInfo +} + +// 服务器信息 +// +// GET / +func (s *SystemInfoController) Info(c *gin.Context) { + c.JSON(200, result.OkData(map[string]any{ + "project": s.systemInfogService.ProjectInfo(), + "cpu": s.systemInfogService.CPUInfo(), + "memory": s.systemInfogService.MemoryInfo(), + "network": s.systemInfogService.NetworkInfo(), + "time": s.systemInfogService.TimeInfo(), + "system": s.systemInfogService.SystemInfo(), + "disk": s.systemInfogService.DiskInfo(), + })) +} diff --git a/src/modules/monitor/model/sys_cache.go b/src/modules/monitor/model/sys_cache.go new file mode 100644 index 00000000..29e79bca --- /dev/null +++ b/src/modules/monitor/model/sys_cache.go @@ -0,0 +1,41 @@ +package model + +import "strings" + +// SysCache 缓存信息对象 +type SysCache struct { + CacheName string `json:"cacheName"` // 缓存名称 + CacheKey string `json:"cacheKey"` // 缓存键名 + CacheValue string `json:"cacheValue"` // 缓存内容 + Remark string `json:"remark"` // 备注 +} + +// NewSysCacheNames 创建新的缓存名称列表项实例 +func NewSysCacheNames(cacheName string, cacheKey string) SysCache { + return SysCache{ + CacheName: cacheKey[:len(cacheKey)-1], + CacheKey: "", + CacheValue: "", + Remark: cacheName, + } +} + +// NewSysCacheKeys 创建新的缓存键名列表项实例 +func NewSysCacheKeys(cacheName string, cacheKey string) SysCache { + return SysCache{ + CacheName: cacheName, + CacheKey: strings.Replace(cacheKey, cacheName+":", "", 1), + CacheValue: "", + Remark: "", + } +} + +// NewSysCacheValue 创建新的缓存键名内容项实例 +func NewSysCacheValue(cacheName string, cacheKey string, cacheValue string) SysCache { + return SysCache{ + CacheName: cacheName, + CacheKey: cacheKey, + CacheValue: cacheValue, + Remark: "", + } +} diff --git a/src/modules/monitor/model/sys_job.go b/src/modules/monitor/model/sys_job.go new file mode 100644 index 00000000..598f1293 --- /dev/null +++ b/src/modules/monitor/model/sys_job.go @@ -0,0 +1,33 @@ +package model + +// SysJob 调度任务信息表 sys_job +type SysJob struct { + // 任务ID + JobID string `json:"jobId"` + // 任务名称 + JobName string `json:"jobName" binding:"required"` + // 任务组名 + JobGroup string `json:"jobGroup" binding:"required"` + // 调用目标字符串 + InvokeTarget string `json:"invokeTarget" binding:"required"` + // 调用目标传入参数 + TargetParams string `json:"targetParams"` + // cron执行表达式 + CronExpression string `json:"cronExpression" binding:"required"` + // 计划执行错误策略(1立即执行 2执行一次 3放弃执行) + MisfirePolicy string `json:"misfirePolicy"` + // 是否并发执行(0禁止 1允许) + Concurrent string `json:"concurrent"` + // 任务状态(0暂停 1正常) + Status string `json:"status"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + // 备注 + Remark string `json:"remark"` +} diff --git a/src/modules/monitor/model/sys_job_log.go b/src/modules/monitor/model/sys_job_log.go new file mode 100644 index 00000000..c9a27bcd --- /dev/null +++ b/src/modules/monitor/model/sys_job_log.go @@ -0,0 +1,23 @@ +package model + +// SysJobLog 定时任务调度日志表 sys_job_log +type SysJobLog struct { + // 日志序号 + JobLogID string `json:"jobLogId"` + // 任务名称 + JobName string `json:"jobName"` + // 任务组名 + JobGroup string `json:"jobGroup"` + // 调用目标字符串 + InvokeTarget string `json:"invokeTarget"` + // 调用目标传入参数 + TargetParams string `json:"targetParams"` + // 日志信息 + JobMsg string `json:"jobMsg"` + // 执行状态(0失败 1正常) + Status string `json:"status"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 消耗时间(毫秒) + CostTime int64 `json:"costTime"` +} diff --git a/src/modules/monitor/model/sys_user_online.go b/src/modules/monitor/model/sys_user_online.go new file mode 100644 index 00000000..2c8a1b2f --- /dev/null +++ b/src/modules/monitor/model/sys_user_online.go @@ -0,0 +1,21 @@ +package model + +// SysUserOnline 当前在线会话对象 +type SysUserOnline struct { + // 会话编号 + TokenID string `json:"tokenId"` + // 部门名称 + DeptName string `json:"deptName"` + // 用户名称 + UserName string `json:"userName"` + // 登录IP地址 + IPAddr string `json:"ipaddr"` + // 登录地址 + LoginLocation string `json:"loginLocation"` + // 浏览器类型 + Browser string `json:"browser"` + // 操作系统 + OS string `json:"os"` + // 登录时间 + LoginTime int64 `json:"loginTime"` +} diff --git a/src/modules/monitor/monitor.go b/src/modules/monitor/monitor.go new file mode 100644 index 00000000..549de18a --- /dev/null +++ b/src/modules/monitor/monitor.go @@ -0,0 +1,160 @@ +package monitor + +import ( + "ems.agt/src/framework/logger" + "ems.agt/src/framework/middleware" + "ems.agt/src/framework/middleware/collectlogs" + "ems.agt/src/framework/middleware/repeat" + "ems.agt/src/modules/monitor/controller" + "ems.agt/src/modules/monitor/processor" + "ems.agt/src/modules/monitor/service" + + "github.com/gin-gonic/gin" +) + +// Setup 模块路由注册 +func Setup(router *gin.Engine) { + logger.Infof("开始加载 ====> monitor 模块路由") + + // 启动时需要的初始参数 + InitLoad() + + // 服务器服务信息 + router.GET("/monitor/system-info", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:system:info"}}), + controller.NewSystemInfo.Info, + ) + + // 缓存服务信息 + sysCacheGroup := router.Group("/monitor/cache") + { + sysCacheGroup.GET("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:info"}}), + controller.NewSysCache.Info, + ) + sysCacheGroup.GET("/getNames", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:list"}}), + controller.NewSysCache.Names, + ) + sysCacheGroup.GET("/getKeys/:cacheName", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:list"}}), + controller.NewSysCache.Keys, + ) + sysCacheGroup.GET("/getValue/:cacheName/:cacheKey", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:query"}}), + controller.NewSysCache.Value, + ) + sysCacheGroup.DELETE("/clearCacheName/:cacheName", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:remove"}}), + controller.NewSysCache.ClearCacheName, + ) + sysCacheGroup.DELETE("/clearCacheKey/:cacheName/:cacheKey", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:remove"}}), + controller.NewSysCache.ClearCacheKey, + ) + sysCacheGroup.DELETE("/clearCacheSafe", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:remove"}}), + controller.NewSysCache.ClearCacheSafe, + ) + } + + // 调度任务日志信息 + sysJobLogGroup := router.Group("/monitor/jobLog") + { + sysJobLogGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:list"}}), + controller.NewSysJobLog.List, + ) + sysJobLogGroup.GET("/:jobLogId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:query"}}), + controller.NewSysJobLog.Info, + ) + sysJobLogGroup.DELETE("/:jobLogIds", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("调度任务日志信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysJobLog.Remove, + ) + sysJobLogGroup.DELETE("/clean", + repeat.RepeatSubmit(5), + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("调度任务日志信息", collectlogs.BUSINESS_TYPE_CLEAN)), + controller.NewSysJobLog.Clean, + ) + sysJobLogGroup.POST("/export", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:export"}}), + collectlogs.OperateLog(collectlogs.OptionNew("调度任务日志信息", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewSysJobLog.Export, + ) + } + + // 调度任务信息 + sysJobGroup := router.Group("/monitor/job") + { + sysJobGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:list"}}), + controller.NewSysJob.List, + ) + sysJobGroup.GET("/:jobId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:query"}}), + controller.NewSysJob.Info, + ) + sysJobGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("调度任务信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysJob.Add, + ) + sysJobGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("调度任务信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysJob.Edit, + ) + sysJobGroup.DELETE("/:jobIds", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("调度任务信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysJob.Remove, + ) + sysJobGroup.PUT("/changeStatus", + repeat.RepeatSubmit(5), + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:changeStatus"}}), + collectlogs.OperateLog(collectlogs.OptionNew("调度任务信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysJob.Status, + ) + sysJobGroup.PUT("/run/:jobId", + repeat.RepeatSubmit(10), + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:changeStatus"}}), + collectlogs.OperateLog(collectlogs.OptionNew("调度任务信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysJob.Run, + ) + sysJobGroup.PUT("/resetQueueJob", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:changeStatus"}}), + collectlogs.OperateLog(collectlogs.OptionNew("调度任务信息", collectlogs.BUSINESS_TYPE_CLEAN)), + controller.NewSysJob.ResetQueueJob, + ) + sysJobGroup.POST("/export", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:export"}}), + collectlogs.OperateLog(collectlogs.OptionNew("调度任务信息", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewSysJob.Export, + ) + } + + // 在线用户监控 + sysUserOnlineGroup := router.Group("/monitor/online") + { + sysUserOnlineGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:online:list"}}), + controller.NewSysUserOnline.List, + ) + sysUserOnlineGroup.DELETE("/:tokenId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:online:forceLogout"}}), + controller.NewSysUserOnline.ForceLogout, + ) + } +} + +// InitLoad 初始参数 +func InitLoad() { + // 初始化定时任务处理 + processor.InitCronQueue() + // 启动时,初始化调度任务 + service.NewSysJobImpl.ResetQueueJob() +} diff --git a/src/modules/monitor/processor/bar/bar.go b/src/modules/monitor/processor/bar/bar.go new file mode 100644 index 00000000..ad2ace16 --- /dev/null +++ b/src/modules/monitor/processor/bar/bar.go @@ -0,0 +1,60 @@ +package bar + +import ( + "time" + + "ems.agt/src/framework/cron" + "ems.agt/src/framework/logger" +) + +var NewProcessor = &BarProcessor{ + progress: 0, + count: 0, +} + +// bar 队列任务处理 +type BarProcessor struct { + // 任务进度 + progress int + // 执行次数 + count int +} + +func (s *BarProcessor) Execute(data any) any { + logger.Infof("执行 %d 次,上次进度: %d ", s.count, s.progress) + s.count++ + + options := data.(cron.JobData) + sysJob := options.SysJob + logger.Infof("重复 %v 任务ID %s", options.Repeat, sysJob.JobID) + + // 实现任务处理逻辑 + i := 0 + s.progress = i + for i < 5 { + // 获取任务进度 + progress := s.progress + logger.Infof("jonId: %s => 任务进度:%d", sysJob.JobID, progress) + // 延迟响应 + time.Sleep(time.Second * 2) + // 程序中途执行错误 + if i == 3 { + // arr := [1]int{1} + // arr[i] = 3 + // fmt.Println(arr) + // return "i = 3" + panic("程序中途执行错误") + } + i++ + // 改变任务进度 + s.progress = i + } + + // 返回结果,用于记录执行结果 + return map[string]any{ + "repeat": options.Repeat, + "jobName": sysJob.JobName, + "invokeTarget": sysJob.InvokeTarget, + "targetParams": sysJob.TargetParams, + } +} diff --git a/src/modules/monitor/processor/foo/foo.go b/src/modules/monitor/processor/foo/foo.go new file mode 100644 index 00000000..ec6d6750 --- /dev/null +++ b/src/modules/monitor/processor/foo/foo.go @@ -0,0 +1,52 @@ +package foo + +import ( + "time" + + "ems.agt/src/framework/cron" + "ems.agt/src/framework/logger" +) + +var NewProcessor = &FooProcessor{ + progress: 0, + count: 0, +} + +// foo 队列任务处理 +type FooProcessor struct { + // 任务进度 + progress int + // 执行次数 + count int +} + +func (s *FooProcessor) Execute(data any) any { + logger.Infof("执行 %d 次,上次进度: %d ", s.count, s.progress) + s.count++ + + options := data.(cron.JobData) + sysJob := options.SysJob + logger.Infof("重复 %v 任务ID %s", options.Repeat, sysJob.JobID) + + // 实现任务处理逻辑 + i := 0 + s.progress = i + for i < 20 { + // 获取任务进度 + progress := s.progress + logger.Infof("jonId: %s => 任务进度:%d", sysJob.JobID, progress) + // 延迟响应 + time.Sleep(time.Second * 2) + i++ + // 改变任务进度 + s.progress = i + } + + // 返回结果,用于记录执行结果 + return map[string]any{ + "repeat": options.Repeat, + "jobName": sysJob.JobName, + "invokeTarget": sysJob.InvokeTarget, + "targetParams": sysJob.TargetParams, + } +} diff --git a/src/modules/monitor/processor/processor.go b/src/modules/monitor/processor/processor.go new file mode 100644 index 00000000..74909d6b --- /dev/null +++ b/src/modules/monitor/processor/processor.go @@ -0,0 +1,15 @@ +package processor + +import ( + "ems.agt/src/framework/cron" + "ems.agt/src/modules/monitor/processor/bar" + "ems.agt/src/modules/monitor/processor/foo" + "ems.agt/src/modules/monitor/processor/simple" +) + +// InitCronQueue 初始定时任务队列 +func InitCronQueue() { + cron.CreateQueue("simple", simple.NewProcessor) + cron.CreateQueue("foo", foo.NewProcessor) + cron.CreateQueue("bar", bar.NewProcessor) +} diff --git a/src/modules/monitor/processor/simple/simple.go b/src/modules/monitor/processor/simple/simple.go new file mode 100644 index 00000000..d0339376 --- /dev/null +++ b/src/modules/monitor/processor/simple/simple.go @@ -0,0 +1,26 @@ +package simple + +import ( + "ems.agt/src/framework/cron" + "ems.agt/src/framework/logger" +) + +var NewProcessor = &simpleProcessor{} + +// simple 队列任务处理 +type simpleProcessor struct{} + +func (s *simpleProcessor) Execute(data any) any { + options := data.(cron.JobData) + + sysJob := options.SysJob + logger.Infof("重复 %v 任务ID %s", options.Repeat, sysJob.JobID) + + // 返回结果,用于记录执行结果 + return map[string]any{ + "repeat": options.Repeat, + "jobName": sysJob.JobName, + "invokeTarget": sysJob.InvokeTarget, + "targetParams": sysJob.TargetParams, + } +} diff --git a/src/modules/monitor/repository/sys_job.go b/src/modules/monitor/repository/sys_job.go new file mode 100644 index 00000000..7fa93f8c --- /dev/null +++ b/src/modules/monitor/repository/sys_job.go @@ -0,0 +1,29 @@ +package repository + +import ( + "ems.agt/src/modules/monitor/model" +) + +// ISysJob 调度任务表 数据层接口 +type ISysJob interface { + // SelectJobPage 分页查询调度任务集合 + SelectJobPage(query map[string]any) map[string]any + + // SelectJobList 查询调度任务集合 + SelectJobList(sysJob model.SysJob) []model.SysJob + + // SelectJobByIds 通过调度ID查询调度任务信息 + SelectJobByIds(jobIds []string) []model.SysJob + + // CheckUniqueJob 校验调度任务是否唯一 + CheckUniqueJob(sysJob model.SysJob) string + + // InsertJob 新增调度任务信息 + InsertJob(sysJob model.SysJob) string + + // UpdateJob 修改调度任务信息 + UpdateJob(sysJob model.SysJob) int64 + + // DeleteJobByIds 批量删除调度任务信息 + DeleteJobByIds(jobIds []string) int64 +} diff --git a/src/modules/monitor/repository/sys_job.impl.go b/src/modules/monitor/repository/sys_job.impl.go new file mode 100644 index 00000000..12eceb26 --- /dev/null +++ b/src/modules/monitor/repository/sys_job.impl.go @@ -0,0 +1,344 @@ +package repository + +import ( + "fmt" + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/monitor/model" +) + +// 实例化数据层 SysJobImpl 结构体 +var NewSysJobImpl = &SysJobImpl{ + selectSql: `select job_id, job_name, job_group, invoke_target, target_params, cron_expression, + misfire_policy, concurrent, status, create_by, create_time, remark from sys_job`, + + resultMap: map[string]string{ + "job_id": "JobID", + "job_name": "JobName", + "job_group": "JobGroup", + "invoke_target": "InvokeTarget", + "target_params": "TargetParams", + "cron_expression": "CronExpression", + "misfire_policy": "MisfirePolicy", + "concurrent": "Concurrent", + "status": "Status", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + "remark": "Remark", + }, +} + +// SysJobImpl 调度任务表 数据层处理 +type SysJobImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysJobImpl) convertResultRows(rows []map[string]any) []model.SysJob { + arr := make([]model.SysJob, 0) + for _, row := range rows { + sysJob := model.SysJob{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysJob, keyMapper, value) + } + } + arr = append(arr, sysJob) + } + return arr +} + +// SelectJobPage 分页查询调度任务集合 +func (r *SysJobImpl) SelectJobPage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["jobName"]; ok && v != "" { + conditions = append(conditions, "job_name like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["jobGroup"]; ok && v != "" { + conditions = append(conditions, "job_group = ?") + params = append(params, v) + } + if v, ok := query["invokeTarget"]; ok && v != "" { + conditions = append(conditions, "invoke_target like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "status = ?") + params = append(params, v) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysJob{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_job" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := r.selectSql + whereSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectJobList 查询调度任务集合 +func (r *SysJobImpl) SelectJobList(sysJob model.SysJob) []model.SysJob { + // 查询条件拼接 + var conditions []string + var params []any + if sysJob.JobName != "" { + conditions = append(conditions, "job_name like concat(?, '%')") + params = append(params, sysJob.JobName) + } + if sysJob.JobGroup != "" { + conditions = append(conditions, "job_group = ?") + params = append(params, sysJob.JobGroup) + } + if sysJob.InvokeTarget != "" { + conditions = append(conditions, "invoke_target like concat(?, '%')") + params = append(params, sysJob.InvokeTarget) + } + if sysJob.Status != "" { + conditions = append(conditions, "status = ?") + params = append(params, sysJob.Status) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + querySql := r.selectSql + whereSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysJob{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectJobByIds 通过调度ID查询调度任务信息 +func (r *SysJobImpl) SelectJobByIds(jobIds []string) []model.SysJob { + placeholder := repo.KeyPlaceholderByQuery(len(jobIds)) + querySql := r.selectSql + " where job_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(jobIds) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysJob{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// CheckUniqueJob 校验调度任务是否唯一 +func (r *SysJobImpl) CheckUniqueJob(sysJob model.SysJob) string { + // 查询条件拼接 + var conditions []string + var params []any + if sysJob.JobName != "" { + conditions = append(conditions, "job_name = ?") + params = append(params, sysJob.JobName) + } + if sysJob.JobGroup != "" { + conditions = append(conditions, "job_group = ?") + params = append(params, sysJob.JobGroup) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } else { + return "" + } + + // 查询数据 + querySql := "select job_id as 'str' from sys_job " + whereSql + " limit 1" + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} + +// InsertJob 新增调度任务信息 +func (r *SysJobImpl) InsertJob(sysJob model.SysJob) string { + // 参数拼接 + params := make(map[string]any) + if sysJob.JobID != "" { + params["job_id"] = sysJob.JobID + } + if sysJob.JobName != "" { + params["job_name"] = sysJob.JobName + } + if sysJob.JobGroup != "" { + params["job_group"] = sysJob.JobGroup + } + if sysJob.InvokeTarget != "" { + params["invoke_target"] = sysJob.InvokeTarget + } + if sysJob.TargetParams != "" { + params["target_params"] = sysJob.TargetParams + } + if sysJob.CronExpression != "" { + params["cron_expression"] = sysJob.CronExpression + } + if sysJob.MisfirePolicy != "" { + params["misfire_policy"] = sysJob.MisfirePolicy + } + if sysJob.Concurrent != "" { + params["concurrent"] = sysJob.Concurrent + } + if sysJob.Status != "" { + params["status"] = sysJob.Status + } + if sysJob.Remark != "" { + params["remark"] = sysJob.Remark + } + if sysJob.CreateBy != "" { + params["create_by"] = sysJob.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_job (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// UpdateJob 修改调度任务信息 +func (r *SysJobImpl) UpdateJob(sysJob model.SysJob) int64 { + // 参数拼接 + params := make(map[string]any) + if sysJob.JobName != "" { + params["job_name"] = sysJob.JobName + } + if sysJob.JobGroup != "" { + params["job_group"] = sysJob.JobGroup + } + if sysJob.InvokeTarget != "" { + params["invoke_target"] = sysJob.InvokeTarget + } + if sysJob.TargetParams != "" { + params["target_params"] = sysJob.TargetParams + } + if sysJob.CronExpression != "" { + params["cron_expression"] = sysJob.CronExpression + } + if sysJob.MisfirePolicy != "" { + params["misfire_policy"] = sysJob.MisfirePolicy + } + if sysJob.Concurrent != "" { + params["concurrent"] = sysJob.Concurrent + } + if sysJob.Status != "" { + params["status"] = sysJob.Status + } + if sysJob.Remark != "" { + params["remark"] = sysJob.Remark + } + if sysJob.UpdateBy != "" { + params["update_by"] = sysJob.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_job set " + strings.Join(keys, ",") + " where job_id = ?" + + // 执行更新 + values = append(values, sysJob.JobID) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// DeleteJobByIds 批量删除调度任务信息 +func (r *SysJobImpl) DeleteJobByIds(jobIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(jobIds)) + sql := "delete from sys_job where job_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(jobIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/monitor/repository/sys_job_log.go b/src/modules/monitor/repository/sys_job_log.go new file mode 100644 index 00000000..6579e471 --- /dev/null +++ b/src/modules/monitor/repository/sys_job_log.go @@ -0,0 +1,26 @@ +package repository + +import ( + "ems.agt/src/modules/monitor/model" +) + +// 调度任务日志表 数据层接口 +type ISysJobLog interface { + // 分页查询调度任务日志集合 + SelectJobLogPage(query map[string]any) map[string]any + + // 查询调度任务日志集合 + SelectJobLogList(sysJobLog model.SysJobLog) []model.SysJobLog + + // 通过调度ID查询调度任务日志信息 + SelectJobLogById(jobLogId string) model.SysJobLog + + // 新增调度任务日志信息 + InsertJobLog(sysJobLog model.SysJobLog) string + + // 批量删除调度任务日志信息 + DeleteJobLogByIds(jobLogId []string) int64 + + // 清空调度任务日志 + CleanJobLog() error +} diff --git a/src/modules/monitor/repository/sys_job_log.impl.go b/src/modules/monitor/repository/sys_job_log.impl.go new file mode 100644 index 00000000..cf506c4d --- /dev/null +++ b/src/modules/monitor/repository/sys_job_log.impl.go @@ -0,0 +1,272 @@ +package repository + +import ( + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/monitor/model" +) + +// 实例化数据层 SysJobLogImpl 结构体 +var NewSysJobLogImpl = &SysJobLogImpl{ + selectSql: `select job_log_id, job_name, job_group, invoke_target, + target_params, job_msg, status, create_time, cost_time from sys_job_log`, + + resultMap: map[string]string{ + "job_log_id": "JobLogID", + "job_name": "JobName", + "job_group": "JobGroup", + "invoke_target": "InvokeTarget", + "target_params": "TargetParams", + "job_msg": "JobMsg", + "status": "Status", + "create_time": "CreateTime", + "cost_time": "CostTime", + }, +} + +// SysJobLogImpl 调度任务日志表 数据层处理 +type SysJobLogImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysJobLogImpl) convertResultRows(rows []map[string]any) []model.SysJobLog { + arr := make([]model.SysJobLog, 0) + for _, row := range rows { + sysJobLog := model.SysJobLog{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysJobLog, keyMapper, value) + } + } + arr = append(arr, sysJobLog) + } + return arr +} + +// 分页查询调度任务日志集合 +func (r *SysJobLogImpl) SelectJobLogPage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["jobName"]; ok && v != "" { + conditions = append(conditions, "job_name like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["jobGroup"]; ok && v != "" { + conditions = append(conditions, "job_group = ?") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "status = ?") + params = append(params, v) + } + if v, ok := query["invokeTarget"]; ok && v != "" { + conditions = append(conditions, "invoke_target like concat(?, '%')") + params = append(params, v) + } + beginTime, ok := query["beginTime"] + if !ok { + beginTime, ok = query["params[beginTime]"] + } + if ok && beginTime != "" { + conditions = append(conditions, "create_time >= ?") + beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD) + params = append(params, beginDate.UnixMilli()) + } + endTime, ok := query["endTime"] + if !ok { + endTime, ok = query["params[endTime]"] + } + if ok && endTime != "" { + conditions = append(conditions, "create_time <= ?") + endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD) + params = append(params, endDate.UnixMilli()) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysJobLog{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_job_log" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " order by job_log_id desc limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := r.selectSql + whereSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// 查询调度任务日志集合 +func (r *SysJobLogImpl) SelectJobLogList(sysJobLog model.SysJobLog) []model.SysJobLog { + // 查询条件拼接 + var conditions []string + var params []any + if sysJobLog.JobName != "" { + conditions = append(conditions, "job_name like concat(?, '%')") + params = append(params, sysJobLog.JobName) + } + if sysJobLog.JobGroup != "" { + conditions = append(conditions, "job_group = ?") + params = append(params, sysJobLog.JobGroup) + } + if sysJobLog.Status != "" { + conditions = append(conditions, "status = ?") + params = append(params, sysJobLog.Status) + } + if sysJobLog.InvokeTarget != "" { + conditions = append(conditions, "invoke_target like concat(?, '%')") + params = append(params, sysJobLog.InvokeTarget) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + querySql := r.selectSql + whereSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysJobLog{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// 通过调度ID查询调度任务日志信息 +func (r *SysJobLogImpl) SelectJobLogById(jobLogId string) model.SysJobLog { + querySql := r.selectSql + " where job_log_id = ?" + results, err := datasource.RawDB("", querySql, []any{jobLogId}) + if err != nil { + logger.Errorf("query err => %v", err) + return model.SysJobLog{} + } + // 转换实体 + rows := r.convertResultRows(results) + if len(rows) > 0 { + return rows[0] + } + return model.SysJobLog{} +} + +// 新增调度任务日志信息 +func (r *SysJobLogImpl) InsertJobLog(sysJobLog model.SysJobLog) string { + // 参数拼接 + params := make(map[string]any) + params["create_time"] = time.Now().UnixMilli() + if sysJobLog.JobLogID != "" { + params["job_log_id"] = sysJobLog.JobLogID + } + if sysJobLog.JobName != "" { + params["job_name"] = sysJobLog.JobName + } + if sysJobLog.JobGroup != "" { + params["job_group"] = sysJobLog.JobGroup + } + if sysJobLog.InvokeTarget != "" { + params["invoke_target"] = sysJobLog.InvokeTarget + } + if sysJobLog.TargetParams != "" { + params["target_params"] = sysJobLog.TargetParams + } + if sysJobLog.JobMsg != "" { + params["job_msg"] = sysJobLog.JobMsg + } + if sysJobLog.Status != "" { + params["status"] = sysJobLog.Status + } + if sysJobLog.CostTime > 0 { + params["cost_time"] = sysJobLog.CostTime + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_job_log (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// 批量删除调度任务日志信息 +func (r *SysJobLogImpl) DeleteJobLogByIds(jobLogIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(jobLogIds)) + sql := "delete from sys_job_log where job_log_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(jobLogIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// 清空调度任务日志 +func (r *SysJobLogImpl) CleanJobLog() error { + sql := "truncate table sys_job_log" + _, err := datasource.ExecDB("", sql, []any{}) + return err +} diff --git a/src/modules/monitor/service/sys_job.go b/src/modules/monitor/service/sys_job.go new file mode 100644 index 00000000..9e9a5ca8 --- /dev/null +++ b/src/modules/monitor/service/sys_job.go @@ -0,0 +1,38 @@ +package service + +import ( + "ems.agt/src/modules/monitor/model" +) + +// ISysJob 调度任务信息 服务层接口 +type ISysJob interface { + // SelectJobPage 分页查询调度任务集合 + SelectJobPage(query map[string]any) map[string]any + + // SelectJobList 查询调度任务集合 + SelectJobList(sysJob model.SysJob) []model.SysJob + + // SelectJobById 通过调度ID查询调度任务信息 + SelectJobById(jobId string) model.SysJob + + // CheckUniqueJobName 校验调度任务名称和组是否唯一 + CheckUniqueJobName(jobName, jobGroup, jobId string) bool + + // InsertJob 新增调度任务信息 + InsertJob(sysJob model.SysJob) string + + // UpdateJob 修改调度任务信息 + UpdateJob(sysJob model.SysJob) int64 + + // DeleteJobByIds 批量删除调度任务信息 + DeleteJobByIds(jobIds []string) (int64, error) + + // ChangeStatus 任务调度状态修改 + ChangeStatus(sysJob model.SysJob) bool + + // RunQueueJob 立即运行一次调度任务 + RunQueueJob(sysJob model.SysJob) bool + + // ResetQueueJob 重置初始调度任务 + ResetQueueJob() +} diff --git a/src/modules/monitor/service/sys_job.impl.go b/src/modules/monitor/service/sys_job.impl.go new file mode 100644 index 00000000..a6fd24e7 --- /dev/null +++ b/src/modules/monitor/service/sys_job.impl.go @@ -0,0 +1,190 @@ +package service + +import ( + "errors" + + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/cron" + "ems.agt/src/modules/monitor/model" + "ems.agt/src/modules/monitor/repository" +) + +// 实例化服务层 SysJobImpl 结构体 +var NewSysJobImpl = &SysJobImpl{ + sysJobRepository: repository.NewSysJobImpl, +} + +// SysJobImpl 调度任务 服务层处理 +type SysJobImpl struct { + // 调度任务数据信息 + sysJobRepository repository.ISysJob +} + +// SelectJobPage 分页查询调度任务集合 +func (r *SysJobImpl) SelectJobPage(query map[string]any) map[string]any { + return r.sysJobRepository.SelectJobPage(query) +} + +// SelectJobList 查询调度任务集合 +func (r *SysJobImpl) SelectJobList(sysJob model.SysJob) []model.SysJob { + return r.sysJobRepository.SelectJobList(sysJob) +} + +// SelectJobById 通过调度ID查询调度任务信息 +func (r *SysJobImpl) SelectJobById(jobId string) model.SysJob { + if jobId == "" { + return model.SysJob{} + } + jobs := r.sysJobRepository.SelectJobByIds([]string{jobId}) + if len(jobs) > 0 { + return jobs[0] + } + return model.SysJob{} +} + +// CheckUniqueJobName 校验调度任务名称和组是否唯一 +func (r *SysJobImpl) CheckUniqueJobName(jobName, jobGroup, jobId string) bool { + uniqueId := r.sysJobRepository.CheckUniqueJob(model.SysJob{ + JobName: jobName, + JobGroup: jobGroup, + }) + if uniqueId == jobId { + return true + } + return uniqueId == "" +} + +// InsertJob 新增调度任务信息 +func (r *SysJobImpl) InsertJob(sysJob model.SysJob) string { + insertId := r.sysJobRepository.InsertJob(sysJob) + if insertId == "" && sysJob.Status == common.STATUS_YES { + sysJob.JobID = insertId + r.insertQueueJob(sysJob, true) + } + return insertId +} + +// UpdateJob 修改调度任务信息 +func (r *SysJobImpl) UpdateJob(sysJob model.SysJob) int64 { + rows := r.sysJobRepository.UpdateJob(sysJob) + if rows > 0 { + //状态正常添加队列任务 + if sysJob.Status == common.STATUS_YES { + r.insertQueueJob(sysJob, true) + } + // 状态禁用删除队列任务 + if sysJob.Status == common.STATUS_NO { + r.deleteQueueJob(sysJob) + } + } + return rows +} + +// DeleteJobByIds 批量删除调度任务信息 +func (r *SysJobImpl) DeleteJobByIds(jobIds []string) (int64, error) { + // 检查是否存在 + jobs := r.sysJobRepository.SelectJobByIds(jobIds) + if len(jobs) <= 0 { + return 0, errors.New("没有权限访问调度任务数据!") + } + if len(jobs) == len(jobIds) { + // 清除任务 + for _, job := range jobs { + r.deleteQueueJob(job) + } + rows := r.sysJobRepository.DeleteJobByIds(jobIds) + return rows, nil + } + return 0, errors.New("删除调度任务信息失败!") +} + +// ChangeStatus 任务调度状态修改 +func (r *SysJobImpl) ChangeStatus(sysJob model.SysJob) bool { + // 更新状态 + newSysJob := model.SysJob{ + JobID: sysJob.JobID, + Status: sysJob.Status, + UpdateBy: sysJob.UpdateBy, + } + rows := r.sysJobRepository.UpdateJob(newSysJob) + if rows > 0 { + //状态正常添加队列任务 + if sysJob.Status == common.STATUS_YES { + r.insertQueueJob(sysJob, true) + } + // 状态禁用删除队列任务 + if sysJob.Status == common.STATUS_NO { + r.deleteQueueJob(sysJob) + } + return true + } + return false +} + +// ResetQueueJob 重置初始调度任务 +func (r *SysJobImpl) ResetQueueJob() { + // 获取注册的队列名称 + queueNames := cron.QueueNames() + if len(queueNames) == 0 { + return + } + // 查询系统中定义状态为正常启用的任务 + sysJobs := r.sysJobRepository.SelectJobList(model.SysJob{ + Status: common.STATUS_YES, + }) + for _, sysJob := range sysJobs { + for _, name := range queueNames { + if name == sysJob.InvokeTarget { + r.insertQueueJob(sysJob, true) + } + } + } +} + +// RunQueueJob 立即运行一次调度任务 +func (r *SysJobImpl) RunQueueJob(sysJob model.SysJob) bool { + return r.insertQueueJob(sysJob, false) +} + +// insertQueueJob 添加调度任务 +func (r *SysJobImpl) insertQueueJob(sysJob model.SysJob, repeat bool) bool { + // 获取队列 Processor + queue := cron.GetQueue(sysJob.InvokeTarget) + if queue.Name != sysJob.InvokeTarget { + return false + } + + // 给执行任务数据参数 + options := cron.JobData{ + Repeat: repeat, + SysJob: sysJob, + } + + // 不是重复任务的情况,立即执行一次 + if !repeat { + // 执行单次任务 + status := queue.RunJob(options, cron.JobOptions{ + JobId: sysJob.JobID, + }) + // 执行中或等待中的都返回正常 + return status == cron.Active || status == cron.Waiting + } + + // 执行重复任务 + queue.RunJob(options, cron.JobOptions{ + JobId: sysJob.JobID, + Cron: sysJob.CronExpression, + }) + + return true +} + +// deleteQueueJob 删除调度任务 +func (r *SysJobImpl) deleteQueueJob(sysJob model.SysJob) bool { + // 获取队列 Processor + queue := cron.GetQueue(sysJob.InvokeTarget) + if queue.Name != sysJob.InvokeTarget { + return false + } + return queue.RemoveJob(sysJob.JobID) +} diff --git a/src/modules/monitor/service/sys_job_log.go b/src/modules/monitor/service/sys_job_log.go new file mode 100644 index 00000000..ea4c1e8c --- /dev/null +++ b/src/modules/monitor/service/sys_job_log.go @@ -0,0 +1,23 @@ +package service + +import ( + "ems.agt/src/modules/monitor/model" +) + +// ISysJobLog 调度任务日志 服务层接口 +type ISysJobLog interface { + // SelectJobLogPage 分页查询调度任务日志集合 + SelectJobLogPage(query map[string]any) map[string]any + + // SelectJobLogList 查询调度任务日志集合 + SelectJobLogList(sysJobLog model.SysJobLog) []model.SysJobLog + + // SelectJobLogById 通过调度ID查询调度任务日志信息 + SelectJobLogById(jobLogId string) model.SysJobLog + + // DeleteJobLogByIds 批量删除调度任务日志信息 + DeleteJobLogByIds(jobLogIds []string) int64 + + // CleanJobLog 清空调度任务日志 + CleanJobLog() error +} diff --git a/src/modules/monitor/service/sys_job_log.impl.go b/src/modules/monitor/service/sys_job_log.impl.go new file mode 100644 index 00000000..369845be --- /dev/null +++ b/src/modules/monitor/service/sys_job_log.impl.go @@ -0,0 +1,42 @@ +package service + +import ( + "ems.agt/src/modules/monitor/model" + "ems.agt/src/modules/monitor/repository" +) + +// 实例化服务层 SysJobLogImpl 结构体 +var NewSysJobLogImpl = &SysJobLogImpl{ + sysJobLogRepository: repository.NewSysJobLogImpl, +} + +// SysJobLogImpl 调度任务日志 服务层处理 +type SysJobLogImpl struct { + // 调度任务日志数据信息 + sysJobLogRepository repository.ISysJobLog +} + +// SelectJobLogPage 分页查询调度任务日志集合 +func (s *SysJobLogImpl) SelectJobLogPage(query map[string]any) map[string]any { + return s.sysJobLogRepository.SelectJobLogPage(query) +} + +// SelectJobLogList 查询调度任务日志集合 +func (s *SysJobLogImpl) SelectJobLogList(sysJobLog model.SysJobLog) []model.SysJobLog { + return s.sysJobLogRepository.SelectJobLogList(sysJobLog) +} + +// SelectJobLogById 通过调度ID查询调度任务日志信息 +func (s *SysJobLogImpl) SelectJobLogById(jobLogId string) model.SysJobLog { + return s.sysJobLogRepository.SelectJobLogById(jobLogId) +} + +// DeleteJobLogByIds 批量删除调度任务日志信息 +func (s *SysJobLogImpl) DeleteJobLogByIds(jobLogIds []string) int64 { + return s.sysJobLogRepository.DeleteJobLogByIds(jobLogIds) +} + +// CleanJobLog 清空调度任务日志 +func (s *SysJobLogImpl) CleanJobLog() error { + return s.sysJobLogRepository.CleanJobLog() +} diff --git a/src/modules/monitor/service/sys_user_online.go b/src/modules/monitor/service/sys_user_online.go new file mode 100644 index 00000000..b74f348f --- /dev/null +++ b/src/modules/monitor/service/sys_user_online.go @@ -0,0 +1,12 @@ +package service + +import ( + "ems.agt/src/framework/vo" + "ems.agt/src/modules/monitor/model" +) + +// ISysUserOnline 在线用户 服务层接口 +type ISysUserOnline interface { + // LoginUserToUserOnline 设置在线用户信息 + LoginUserToUserOnline(loginUser vo.LoginUser) model.SysUserOnline +} diff --git a/src/modules/monitor/service/sys_user_online.impl.go b/src/modules/monitor/service/sys_user_online.impl.go new file mode 100644 index 00000000..dbfe4270 --- /dev/null +++ b/src/modules/monitor/service/sys_user_online.impl.go @@ -0,0 +1,33 @@ +package service + +import ( + "ems.agt/src/framework/vo" + "ems.agt/src/modules/monitor/model" +) + +// 实例化服务层 SysUserOnlineImpl 结构体 +var NewSysUserOnlineImpl = &SysUserOnlineImpl{} + +// SysUserOnlineImpl 在线用户 服务层处理 +type SysUserOnlineImpl struct{} + +// LoginUserToUserOnline 设置在线用户信息 +func (r *SysUserOnlineImpl) LoginUserToUserOnline(loginUser vo.LoginUser) model.SysUserOnline { + if loginUser.UserID == "" { + return model.SysUserOnline{} + } + + sysUserOnline := model.SysUserOnline{ + TokenID: loginUser.UUID, + UserName: loginUser.User.UserName, + IPAddr: loginUser.IPAddr, + LoginLocation: loginUser.LoginLocation, + Browser: loginUser.Browser, + OS: loginUser.OS, + LoginTime: loginUser.LoginTime, + } + if loginUser.User.DeptID != "" { + sysUserOnline.DeptName = loginUser.User.Dept.DeptName + } + return sysUserOnline +} diff --git a/src/modules/monitor/service/system_info.go b/src/modules/monitor/service/system_info.go new file mode 100644 index 00000000..e2be65fd --- /dev/null +++ b/src/modules/monitor/service/system_info.go @@ -0,0 +1,25 @@ +package service + +// ISystemInfo 服务器系统相关信息 服务层接口 +type ISystemInfo interface { + // ProjectInfo 程序项目信息 + ProjectInfo() map[string]any + + // SystemInfo 系统信息 + SystemInfo() map[string]any + + // TimeInfo 系统时间信息 + TimeInfo() map[string]string + + // MemoryInfo 内存信息 + MemoryInfo() map[string]any + + // CPUInfo CPU信息 + CPUInfo() map[string]any + + // NetworkInfo 网络信息 + NetworkInfo() map[string]string + + // DiskInfo 磁盘信息 + DiskInfo() []map[string]string +} diff --git a/src/modules/monitor/service/system_info.impl.go b/src/modules/monitor/service/system_info.impl.go new file mode 100644 index 00000000..c6f3f37b --- /dev/null +++ b/src/modules/monitor/service/system_info.impl.go @@ -0,0 +1,236 @@ +package service + +import ( + "bufio" + "fmt" + "os" + "runtime" + "strings" + "time" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/utils/parse" + + "github.com/shirou/gopsutil/v3/cpu" + "github.com/shirou/gopsutil/v3/disk" + "github.com/shirou/gopsutil/v3/host" + "github.com/shirou/gopsutil/v3/mem" + "github.com/shirou/gopsutil/v3/net" +) + +// 实例化服务层 SystemInfoImpl 结构体 +var NewSystemInfoImpl = &SystemInfoImpl{} + +// SystemInfoImpl 服务器系统相关信息 服务层处理 +type SystemInfoImpl struct{} + +// ProjectInfo 程序项目信息 +func (s *SystemInfoImpl) ProjectInfo() map[string]any { + // 获取工作目录 + appDir, err := os.Getwd() + if err != nil { + appDir = "" + } + // 项目依赖 + dependencies := s.dependencies() + return map[string]any{ + "appDir": appDir, + "env": config.Env(), + "name": config.Get("framework.name"), + "version": config.Get("framework.version"), + "dependencies": dependencies, + } +} + +// dependencies 读取mod内项目包依赖 +func (s *SystemInfoImpl) dependencies() map[string]string { + var pkgs = make(map[string]string) + + // 打开 go.mod 文件 + file, err := os.Open("go.mod") + if err != nil { + return pkgs + } + defer file.Close() + + // 使用 bufio.Scanner 逐行读取文件内容 + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + line = strings.TrimSpace(line) + + // 行不为空,不以module\require开头,不带有 // indirect 注释,则解析包名和版本 + prefixLine := strings.HasPrefix(line, "module") || strings.HasPrefix(line, "require") || strings.HasPrefix(line, "go ") + suffixLine := strings.HasSuffix(line, ")") || strings.HasSuffix(line, "// indirect") + if line == "" || prefixLine || suffixLine { + continue + } + + modInfo := strings.Split(line, " ") + if len(modInfo) >= 2 { + moduleName := strings.TrimSpace(modInfo[0]) + version := strings.TrimSpace(modInfo[1]) + pkgs[moduleName] = version + } + } + + if err := scanner.Err(); err != nil { + pkgs["scanner-err"] = err.Error() + } + return pkgs +} + +// SystemInfo 系统信息 +func (s *SystemInfoImpl) SystemInfo() map[string]any { + info, err := host.Info() + if err != nil { + info.Platform = err.Error() + } + // 用户目录 + homeDir, err := os.UserHomeDir() + if err != nil { + homeDir = "" + } + cmd, err := os.Executable() + if err != nil { + cmd = "" + } + return map[string]any{ + "platform": info.Platform, + "go": runtime.Version(), + "processId": os.Getpid(), + "arch": info.KernelArch, + "uname": runtime.GOARCH, + "release": info.OS, + "hostname": info.Hostname, + "homeDir": homeDir, + "cmd": cmd, + "execCommand": strings.Join(os.Args, " "), + } +} + +// TimeInfo 系统时间信息 +func (s *SystemInfoImpl) TimeInfo() map[string]string { + // 获取当前时间 + current := time.Now().Format("2006-01-02 15:04:05") + // 获取程序运行时间 + uptime := time.Since(config.RunTime()).String() + // 获取时区 + timezone := time.Now().Format("-0700 MST") + // 获取时区名称 + timezoneName := time.Now().Format("MST") + + return map[string]string{ + "current": current, + "uptime": uptime, + "timezone": timezone, + "timezoneName": timezoneName, + } +} + +// MemoryInfo 内存信息 +func (s *SystemInfoImpl) MemoryInfo() map[string]any { + memInfo, err := mem.VirtualMemory() + if err != nil { + memInfo.UsedPercent = 0 + memInfo.Available = 0 + memInfo.Total = 0 + } + + var memStats runtime.MemStats + runtime.ReadMemStats(&memStats) + + return map[string]any{ + "usage": fmt.Sprintf("%.2f", memInfo.UsedPercent), // 内存利用率 + "freemem": parse.Bit(float64(memInfo.Available)), // 可用内存大小(GB) + "totalmem": parse.Bit(float64(memInfo.Total)), // 总内存大小(GB) + "rss": parse.Bit(float64(memStats.Sys)), // 常驻内存大小(RSS) + "heapTotal": parse.Bit(float64(memStats.HeapSys)), // 堆总大小 + "heapUsed": parse.Bit(float64(memStats.HeapAlloc)), // 堆已使用大小 + "external": parse.Bit(float64(memStats.Sys - memStats.HeapSys)), // 外部内存大小(非堆) + } +} + +// CPUInfo CPU信息 +func (s *SystemInfoImpl) CPUInfo() map[string]any { + var core int32 = 0 + var speed string = "未知" + var model string = "未知" + cpuInfo, err := cpu.Info() + if err == nil { + core = cpuInfo[0].Cores + speed = fmt.Sprintf("%.0fMHz", cpuInfo[0].Mhz) + model = strings.TrimSpace(cpuInfo[0].ModelName) + } + + useds := []string{} + cpuPercent, err := cpu.Percent(0, true) + if err == nil { + for _, v := range cpuPercent { + useds = append(useds, fmt.Sprintf("%.2f", v)) + } + } + + return map[string]any{ + "model": model, + "speed": speed, + "core": core, + "coreUsed": useds, + } +} + +// NetworkInfo 网络信息 +func (s *SystemInfoImpl) NetworkInfo() map[string]string { + ipAddrs := make(map[string]string) + interfaces, err := net.Interfaces() + if err == nil { + for _, iface := range interfaces { + name := iface.Name + if name[len(name)-1] == '0' { + name = name[0 : len(name)-1] + name = strings.Trim(name, "") + } + // ignore localhost + if name == "lo" { + continue + } + var addrs []string + for _, v := range iface.Addrs { + prefix := strings.Split(v.Addr, "/")[0] + if strings.Contains(prefix, "::") { + addrs = append(addrs, fmt.Sprintf("IPv6 %s", prefix)) + } + if strings.Contains(prefix, ".") { + addrs = append(addrs, fmt.Sprintf("IPv4 %s", prefix)) + } + } + ipAddrs[name] = strings.Join(addrs, " / ") + } + } + return ipAddrs +} + +// DiskInfo 磁盘信息 +func (s *SystemInfoImpl) DiskInfo() []map[string]string { + disks := make([]map[string]string, 0) + + partitions, err := disk.Partitions(false) + if err != nil { + return disks + } + + for _, partition := range partitions { + usage, err := disk.Usage(partition.Mountpoint) + if err != nil { + continue + } + disks = append(disks, map[string]string{ + "size": parse.Bit(float64(usage.Total)), + "used": parse.Bit(float64(usage.Used)), + "avail": parse.Bit(float64(usage.Free)), + "pcent": fmt.Sprintf("%.1f%%", usage.UsedPercent), + "target": partition.Device, + }) + } + return disks +} diff --git a/src/modules/system/controller/sys_config.go b/src/modules/system/controller/sys_config.go new file mode 100644 index 00000000..73503657 --- /dev/null +++ b/src/modules/system/controller/sys_config.go @@ -0,0 +1,220 @@ +package controller + +import ( + "fmt" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysConfigController 结构体 +var NewSysConfig = &SysConfigController{ + sysConfigService: service.NewSysConfigImpl, +} + +// 参数配置信息 +// +// PATH /system/config +type SysConfigController struct { + // 参数配置服务 + sysConfigService service.ISysConfig +} + +// 参数配置列表 +// +// GET /list +func (s *SysConfigController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + data := s.sysConfigService.SelectConfigPage(querys) + c.JSON(200, result.Ok(data)) +} + +// 参数配置信息 +// +// GET /:configId +func (s *SysConfigController) Info(c *gin.Context) { + configId := c.Param("configId") + if configId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysConfigService.SelectConfigById(configId) + if data.ConfigID == configId { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 参数配置新增 +// +// POST / +func (s *SysConfigController) Add(c *gin.Context) { + var body model.SysConfig + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.ConfigID != "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查属性值唯一 + uniqueConfigKey := s.sysConfigService.CheckUniqueConfigKey(body.ConfigKey, "") + if !uniqueConfigKey { + msg := fmt.Sprintf("参数配置新增【%s】失败,参数键名已存在", body.ConfigKey) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysConfigService.InsertConfig(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 参数配置修改 +// +// PUT / +func (s *SysConfigController) Edit(c *gin.Context) { + var body model.SysConfig + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.ConfigID == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查属性值唯一 + uniqueConfigKey := s.sysConfigService.CheckUniqueConfigKey(body.ConfigKey, body.ConfigID) + if !uniqueConfigKey { + msg := fmt.Sprintf("参数配置修改【%s】失败,参数键名已存在", body.ConfigKey) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查是否存在 + config := s.sysConfigService.SelectConfigById(body.ConfigID) + if config.ConfigID != body.ConfigID { + c.JSON(200, result.ErrMsg("没有权限访问参数配置数据!")) + return + } + + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysConfigService.UpdateConfig(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 参数配置删除 +// +// DELETE /:configIds +func (s *SysConfigController) Remove(c *gin.Context) { + configIds := c.Param("configIds") + if configIds == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(configIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows, err := s.sysConfigService.DeleteConfigByIds(uniqueIDs) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) +} + +// 参数配置刷新缓存 +// +// PUT /refreshCache +func (s *SysConfigController) RefreshCache(c *gin.Context) { + s.sysConfigService.ResetConfigCache() + c.JSON(200, result.Ok(nil)) +} + +// 参数配置根据参数键名 +// +// GET /configKey/:configKey +func (s *SysConfigController) ConfigKey(c *gin.Context) { + configKey := c.Param("configKey") + if configKey == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + key := s.sysConfigService.SelectConfigValueByKey(configKey) + if key != "" { + c.JSON(200, result.OkData(key)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 导出参数配置信息 +// +// POST /export +func (s *SysConfigController) Export(c *gin.Context) { + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + data := s.sysConfigService.SelectConfigPage(querys) + if data["total"].(int64) == 0 { + c.JSON(200, result.ErrMsg("导出数据记录为空")) + return + } + rows := data["rows"].([]model.SysConfig) + + // 导出文件名称 + fileName := fmt.Sprintf("config_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "参数编号", + "B1": "参数名称", + "C1": "参数键名", + "D1": "参数键值", + "E1": "系统内置", + } + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + typeValue := "否" + if row.ConfigType == "Y" { + typeValue = "是" + } + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.ConfigID, + "B" + idx: row.ConfigName, + "C" + idx: row.ConfigKey, + "D" + idx: row.ConfigValue, + "E" + idx: typeValue, + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} diff --git a/src/modules/system/controller/sys_dept.go b/src/modules/system/controller/sys_dept.go new file mode 100644 index 00000000..046cce90 --- /dev/null +++ b/src/modules/system/controller/sys_dept.go @@ -0,0 +1,309 @@ +package controller + +import ( + "fmt" + "strings" + + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysDeptController 结构体 +var NewSysDept = &SysDeptController{ + sysDeptService: service.NewSysDeptImpl, +} + +// 部门信息 +// +// PATH /system/dept +type SysDeptController struct { + // 部门服务 + sysDeptService service.ISysDept +} + +// 部门列表 +// +// GET /list +func (s *SysDeptController) List(c *gin.Context) { + var querys struct { + // 部门ID + DeptID string `json:"deptId"` + // 父部门ID + ParentID string `json:"parentId" ` + // 部门名称 + DeptName string `json:"deptName" ` + // 部门状态(0正常 1停用) + Status string `json:"status"` + } + err := c.ShouldBindQuery(&querys) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + SysDeptController := model.SysDept{ + DeptID: querys.DeptID, + ParentID: querys.ParentID, + DeptName: querys.DeptName, + Status: querys.Status, + } + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "") + data := s.sysDeptService.SelectDeptList(SysDeptController, dataScopeSQL) + c.JSON(200, result.OkData(data)) +} + +// 部门信息 +// +// GET /:deptId +func (s *SysDeptController) Info(c *gin.Context) { + deptId := c.Param("deptId") + if deptId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysDeptService.SelectDeptById(deptId) + if data.DeptID == deptId { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 部门新增 +// +// POST / +func (s *SysDeptController) Add(c *gin.Context) { + var body model.SysDept + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.DeptID != "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 父级ID不为0是要检查 + if body.ParentID != "0" { + deptParent := s.sysDeptService.SelectDeptById(body.ParentID) + if deptParent.DeptID != body.ParentID { + c.JSON(200, result.ErrMsg("没有权限访问部门数据!")) + return + } + if deptParent.Status == common.STATUS_NO { + msg := fmt.Sprintf("上级部门【%s】停用,不允许新增", deptParent.DeptName) + c.JSON(200, result.ErrMsg(msg)) + return + } + if deptParent.DelFlag == common.STATUS_YES { + msg := fmt.Sprintf("上级部门【%s】已删除,不允许新增", deptParent.DeptName) + c.JSON(200, result.ErrMsg(msg)) + return + } + body.Ancestors = deptParent.Ancestors + "," + body.ParentID + } else { + body.Ancestors = "0" + } + + // 检查同级下名称唯一 + uniqueDeptName := s.sysDeptService.CheckUniqueDeptName(body.DeptName, body.ParentID, "") + if !uniqueDeptName { + msg := fmt.Sprintf("部门新增【%s】失败,部门名称已存在", body.DeptName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysDeptService.InsertDept(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 部门修改 +// +// PUT / +func (s *SysDeptController) Edit(c *gin.Context) { + var body model.SysDept + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.DeptID == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 上级部门不能选自己 + if body.DeptID == body.ParentID { + msg := fmt.Sprintf("部门修改【%s】失败,上级部门不能是自己", body.DeptName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查数据是否存在 + deptInfo := s.sysDeptService.SelectDeptById(body.DeptID) + if deptInfo.DeptID != body.DeptID { + c.JSON(200, result.ErrMsg("没有权限访问部门数据!")) + return + } + // 父级ID不为0是要检查 + if body.ParentID != "0" { + deptParent := s.sysDeptService.SelectDeptById(body.ParentID) + if deptParent.DeptID != body.ParentID { + c.JSON(200, result.ErrMsg("没有权限访问部门数据!")) + return + } + } + + // 检查同级下名称唯一 + uniqueDeptName := s.sysDeptService.CheckUniqueDeptName(body.DeptName, body.ParentID, body.DeptID) + if !uniqueDeptName { + msg := fmt.Sprintf("部门修改【%s】失败,部门名称已存在", body.DeptName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 上级停用需要检查下级是否有在使用 + if body.Status == common.STATUS_NO { + hasChild := s.sysDeptService.HasChildByDeptId(body.DeptID) + if hasChild > 0 { + msg := fmt.Sprintf("该部门包含未停用的子部门数量:%d", hasChild) + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysDeptService.UpdateDept(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 部门删除 +// +// DELETE /:deptId +func (s *SysDeptController) Remove(c *gin.Context) { + deptId := c.Param("deptId") + if deptId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查数据是否存在 + dept := s.sysDeptService.SelectDeptById(deptId) + if dept.DeptID != deptId { + c.JSON(200, result.ErrMsg("没有权限访问部门数据!")) + return + } + + // 检查是否存在子部门 + hasChild := s.sysDeptService.HasChildByDeptId(deptId) + if hasChild > 0 { + msg := fmt.Sprintf("不允许删除,存在子部门数:%d", hasChild) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查是否分配给用户 + existUser := s.sysDeptService.CheckDeptExistUser(deptId) + if existUser > 0 { + msg := fmt.Sprintf("不允许删除,部门已分配给用户数:%d", existUser) + c.JSON(200, result.ErrMsg(msg)) + return + } + + rows := s.sysDeptService.DeleteDeptById(deptId) + if rows > 0 { + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 部门列表(排除节点) +// +// GET /list/exclude/:deptId +func (s *SysDeptController) ExcludeChild(c *gin.Context) { + deptId := c.Param("deptId") + if deptId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "") + data := s.sysDeptService.SelectDeptList(model.SysDept{}, dataScopeSQL) + + // 过滤排除节点 + filtered := make([]model.SysDept, 0) + for _, dept := range data { + hasAncestor := false + ancestorList := strings.Split(dept.Ancestors, ",") + for _, ancestor := range ancestorList { + if ancestor == deptId { + hasAncestor = true + break + } + } + if !(dept.DeptID == deptId || hasAncestor) { + filtered = append(filtered, dept) + } + } + c.JSON(200, result.OkData(filtered)) +} + +// 部门树结构列表 +// +// GET /treeSelect +func (s *SysDeptController) TreeSelect(c *gin.Context) { + var querys struct { + // 部门ID + DeptID string `json:"deptId"` + // 父部门ID + ParentID string `json:"parentId" ` + // 部门名称 + DeptName string `json:"deptName" ` + // 部门状态(0正常 1停用) + Status string `json:"status"` + } + err := c.ShouldBindQuery(&querys) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + SysDeptController := model.SysDept{ + DeptID: querys.DeptID, + ParentID: querys.ParentID, + DeptName: querys.DeptName, + Status: querys.Status, + } + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "") + data := s.sysDeptService.SelectDeptTreeSelect(SysDeptController, dataScopeSQL) + c.JSON(200, result.OkData(data)) +} + +// 部门树结构列表(指定角色) +// +// GET /roleDeptTreeSelect/:roleId +func (s *SysDeptController) RoleDeptTreeSelect(c *gin.Context) { + roleId := c.Param("roleId") + if roleId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "") + deptTreeSelect := s.sysDeptService.SelectDeptTreeSelect(model.SysDept{}, dataScopeSQL) + checkedKeys := s.sysDeptService.SelectDeptListByRoleId(roleId) + c.JSON(200, result.OkData(map[string]any{ + "depts": deptTreeSelect, + "checkedKeys": checkedKeys, + })) +} diff --git a/src/modules/system/controller/sys_dict_data.go b/src/modules/system/controller/sys_dict_data.go new file mode 100644 index 00000000..8c1509a9 --- /dev/null +++ b/src/modules/system/controller/sys_dict_data.go @@ -0,0 +1,244 @@ +package controller + +import ( + "fmt" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysDictDataController 结构体 +var NewSysDictData = &SysDictDataController{ + sysDictDataService: service.NewSysDictDataImpl, + sysDictTypeService: service.NewSysDictTypeImpl, +} + +// 字典类型对应的字典数据信息 +// +// PATH /system/dict/data +type SysDictDataController struct { + // 字典数据服务 + sysDictDataService service.ISysDictData + // 字典类型服务 + sysDictTypeService service.ISysDictType +} + +// 字典数据列表 +// +// GET /list +func (s *SysDictDataController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + data := s.sysDictDataService.SelectDictDataPage(querys) + c.JSON(200, result.Ok(data)) +} + +// 字典数据详情 +// +// GET /:dictCode +func (s *SysDictDataController) Info(c *gin.Context) { + dictCode := c.Param("dictCode") + if dictCode == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysDictDataService.SelectDictDataByCode(dictCode) + if data.DictCode == dictCode { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 字典数据新增 +// +// POST / +func (s *SysDictDataController) Add(c *gin.Context) { + var body model.SysDictData + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.DictCode != "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查字典类型是否存在 + sysDictType := s.sysDictTypeService.SelectDictTypeByType(body.DictType) + if sysDictType.DictType != body.DictType { + c.JSON(200, result.ErrMsg("没有权限访问字典类型数据!")) + return + } + + // 检查字典标签唯一 + uniqueDictLabel := s.sysDictDataService.CheckUniqueDictLabel(body.DictType, body.DictLabel, "") + if !uniqueDictLabel { + msg := fmt.Sprintf("数据新增【%s】失败,该字典类型下标签名已存在", body.DictLabel) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查字典键值唯一 + uniqueDictValue := s.sysDictDataService.CheckUniqueDictValue(body.DictType, body.DictValue, "") + if !uniqueDictValue { + msg := fmt.Sprintf("数据新增【%s】失败,该字典类型下标签值已存在", body.DictValue) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysDictDataService.InsertDictData(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 字典类型修改 +// +// PUT / +func (s *SysDictDataController) Edit(c *gin.Context) { + var body model.SysDictData + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.DictCode == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查字典类型是否存在 + sysDictType := s.sysDictTypeService.SelectDictTypeByType(body.DictType) + if sysDictType.DictType != body.DictType { + c.JSON(200, result.ErrMsg("没有权限访问字典类型数据!")) + return + } + + // 检查字典编码是否存在 + SysDictDataController := s.sysDictDataService.SelectDictDataByCode(body.DictCode) + if SysDictDataController.DictCode != body.DictCode { + c.JSON(200, result.ErrMsg("没有权限访问字典编码数据!")) + return + } + + // 检查字典标签唯一 + uniqueDictLabel := s.sysDictDataService.CheckUniqueDictLabel(body.DictType, body.DictLabel, body.DictCode) + if !uniqueDictLabel { + msg := fmt.Sprintf("数据修改【%s】失败,该字典类型下标签名已存在", body.DictLabel) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查字典键值唯一 + uniqueDictValue := s.sysDictDataService.CheckUniqueDictValue(body.DictType, body.DictValue, body.DictCode) + if !uniqueDictValue { + msg := fmt.Sprintf("数据修改【%s】失败,该字典类型下标签值已存在", body.DictValue) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysDictDataService.UpdateDictData(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 字典数据删除 +// +// DELETE /:dictCodes +func (s *SysDictDataController) Remove(c *gin.Context) { + dictCodes := c.Param("dictCodes") + if dictCodes == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(dictCodes, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows, err := s.sysDictDataService.DeleteDictDataByCodes(uniqueIDs) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) +} + +// 字典数据列表(指定字典类型) +// +// GET /type/:dictType +func (s *SysDictDataController) DictType(c *gin.Context) { + dictType := c.Param("dictType") + if dictType == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + data := s.sysDictDataService.SelectDictDataByType(dictType) + c.JSON(200, result.OkData(data)) +} + +// 字典数据列表导出 +// +// POST /export +func (s *SysDictDataController) Export(c *gin.Context) { + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + data := s.sysDictDataService.SelectDictDataPage(querys) + if data["total"].(int64) == 0 { + c.JSON(200, result.ErrMsg("导出数据记录为空")) + return + } + rows := data["rows"].([]model.SysDictData) + + // 导出文件名称 + fileName := fmt.Sprintf("dict_data_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "字典编码", + "B1": "字典排序", + "C1": "字典标签", + "D1": "字典键值", + "E1": "字典类型", + "F1": "状态", + } + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + statusValue := "停用" + if row.Status == "1" { + statusValue = "正常" + } + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.DictCode, + "B" + idx: row.DictSort, + "C" + idx: row.DictLabel, + "D" + idx: row.DictValue, + "E" + idx: row.DictType, + "F" + idx: statusValue, + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} diff --git a/src/modules/system/controller/sys_dict_type.go b/src/modules/system/controller/sys_dict_type.go new file mode 100644 index 00000000..59697789 --- /dev/null +++ b/src/modules/system/controller/sys_dict_type.go @@ -0,0 +1,242 @@ +package controller + +import ( + "fmt" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysDictTypeController 结构体 +var NewSysDictType = &SysDictTypeController{ + sysDictTypeService: service.NewSysDictTypeImpl, +} + +// 字典类型信息 +// +// PATH /system/dict/type +type SysDictTypeController struct { + // 字典类型服务 + sysDictTypeService service.ISysDictType +} + +// 字典类型列表 +// +// GET /list +func (s *SysDictTypeController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + data := s.sysDictTypeService.SelectDictTypePage(querys) + c.JSON(200, result.Ok(data)) +} + +// 字典类型信息 +// +// GET /:dictId +func (s *SysDictTypeController) Info(c *gin.Context) { + dictId := c.Param("dictId") + if dictId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysDictTypeService.SelectDictTypeByID(dictId) + if data.DictID == dictId { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 字典类型新增 +// +// POST / +func (s *SysDictTypeController) Add(c *gin.Context) { + var body model.SysDictType + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.DictID != "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查字典名称唯一 + uniqueDictName := s.sysDictTypeService.CheckUniqueDictName(body.DictName, "") + if !uniqueDictName { + msg := fmt.Sprintf("字典新增【%s】失败,字典名称已存在", body.DictName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查字典类型唯一 + uniqueDictType := s.sysDictTypeService.CheckUniqueDictType(body.DictType, "") + if !uniqueDictType { + msg := fmt.Sprintf("字典新增【%s】失败,字典类型已存在", body.DictType) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysDictTypeService.InsertDictType(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 字典类型修改 +// +// PUT / +func (s *SysDictTypeController) Edit(c *gin.Context) { + var body model.SysDictType + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.DictID == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查数据是否存在 + dictInfo := s.sysDictTypeService.SelectDictTypeByID(body.DictID) + if dictInfo.DictID != body.DictID { + c.JSON(200, result.ErrMsg("没有权限访问字典类型数据!")) + return + } + + // 检查字典名称唯一 + uniqueDictName := s.sysDictTypeService.CheckUniqueDictName(body.DictName, body.DictID) + if !uniqueDictName { + msg := fmt.Sprintf("字典修改【%s】失败,字典名称已存在", body.DictName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查字典类型唯一 + uniqueDictType := s.sysDictTypeService.CheckUniqueDictType(body.DictType, body.DictID) + if !uniqueDictType { + msg := fmt.Sprintf("字典修改【%s】失败,字典类型已存在", body.DictType) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysDictTypeService.UpdateDictType(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 字典类型删除 +// +// DELETE /:dictIds +func (s *SysDictTypeController) Remove(c *gin.Context) { + dictIds := c.Param("dictIds") + if dictIds == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(dictIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows, err := s.sysDictTypeService.DeleteDictTypeByIDs(uniqueIDs) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) +} + +// 字典类型刷新缓存 +// +// PUT /refreshCache +func (s *SysDictTypeController) RefreshCache(c *gin.Context) { + s.sysDictTypeService.ResetDictCache() + c.JSON(200, result.Ok(nil)) +} + +// 字典类型选择框列表 +// +// GET /getDictOptionselect +func (s *SysDictTypeController) DictOptionselect(c *gin.Context) { + data := s.sysDictTypeService.SelectDictTypeList(model.SysDictType{ + Status: common.STATUS_YES, + }) + + type labelValue struct { + Label string `json:"label"` + Value string `json:"value"` + } + + // 数据组 + arr := []labelValue{} + for _, v := range data { + arr = append(arr, labelValue{ + Label: v.DictName, + Value: v.DictType, + }) + } + c.JSON(200, result.OkData(arr)) +} + +// 字典类型列表导出 +// +// POST /export +func (s *SysDictTypeController) Export(c *gin.Context) { + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + data := s.sysDictTypeService.SelectDictTypePage(querys) + if data["total"].(int64) == 0 { + c.JSON(200, result.ErrMsg("导出数据记录为空")) + return + } + rows := data["rows"].([]model.SysDictType) + + // 导出文件名称 + fileName := fmt.Sprintf("dict_type_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "字典主键", + "B1": "字典名称", + "C1": "字典类型", + "D1": "状态", + } + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + statusValue := "停用" + if row.Status == "1" { + statusValue = "正常" + } + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.DictID, + "B" + idx: row.DictName, + "C" + idx: row.DictType, + "D" + idx: statusValue, + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} diff --git a/src/modules/system/controller/sys_log_login.go b/src/modules/system/controller/sys_log_login.go new file mode 100644 index 00000000..c3976d06 --- /dev/null +++ b/src/modules/system/controller/sys_log_login.go @@ -0,0 +1,158 @@ +package controller + +import ( + "fmt" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + commonService "ems.agt/src/modules/common/service" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" +) + +// 实例化控制层 SysLogLoginController 结构体 +var NewSysLogLogin = &SysLogLoginController{ + sysLogLoginService: service.NewSysLogLoginImpl, + accountService: commonService.NewAccountImpl, +} + +// 系统登录日志信息 +// +// PATH /system/log/login +type SysLogLoginController struct { + // 系统登录日志服务 + sysLogLoginService service.ISysLogLogin + // 账号身份操作服务 + accountService commonService.IAccount +} + +// 系统登录日志列表 +// +// GET /list +func (s *SysLogLoginController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + data := s.sysLogLoginService.SelectSysLogLoginPage(querys) + c.JSON(200, result.Ok(data)) +} + +// 系统登录日志删除 +// +// DELETE /:infoIds +func (s *SysLogLoginController) Remove(c *gin.Context) { + infoIds := c.Param("infoIds") + if infoIds == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 处理字符转id数组后去重 + ids := strings.Split(infoIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows := s.sysLogLoginService.DeleteSysLogLoginByIds(uniqueIDs) + if rows > 0 { + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 系统登录日志清空 +// +// DELETE /clean +func (s *SysLogLoginController) Clean(c *gin.Context) { + err := s.sysLogLoginService.CleanSysLogLogin() + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + c.JSON(200, result.Ok(nil)) +} + +// 系统登录日志账户解锁 +// +// PUT /unlock/:userName +func (s *SysLogLoginController) Unlock(c *gin.Context) { + userName := c.Param("userName") + if userName == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + ok := s.accountService.ClearLoginRecordCache(userName) + if ok { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 导出系统登录日志信息 +// +// POST /export +func (s *SysLogLoginController) Export(c *gin.Context) { + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + data := s.sysLogLoginService.SelectSysLogLoginPage(querys) + if data["total"].(int64) == 0 { + c.JSON(200, result.ErrMsg("导出数据记录为空")) + return + } + rows := data["rows"].([]model.SysLogLogin) + + // 导出文件名称 + fileName := fmt.Sprintf("sys_log_login_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "序号", + "B1": "用户账号", + "C1": "登录状态", + "D1": "登录地址", + "E1": "登录地点", + "F1": "浏览器", + "G1": "操作系统", + "H1": "提示消息", + "I1": "访问时间", + } + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 状态 + statusValue := "失败" + if row.Status == "1" { + statusValue = "成功" + } + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.LoginID, + "B" + idx: row.UserName, + "C" + idx: statusValue, + "D" + idx: row.IPAddr, + "E" + idx: row.LoginLocation, + "F" + idx: row.Browser, + "G" + idx: row.OS, + "H" + idx: row.Msg, + "I" + idx: date.ParseDateToStr(row.LoginTime, date.YYYY_MM_DD_HH_MM_SS), + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} diff --git a/src/modules/system/controller/sys_log_operate.go b/src/modules/system/controller/sys_log_operate.go new file mode 100644 index 00000000..1620f03e --- /dev/null +++ b/src/modules/system/controller/sys_log_operate.go @@ -0,0 +1,155 @@ +package controller + +import ( + "fmt" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" +) + +// 实例化控制层 SysLogOperateController 结构体 +var NewSysLogOperate = &SysLogOperateController{ + SysLogOperateService: service.NewSysLogOperateImpl, +} + +// 操作日志记录信息 +// +// PATH /system/log/operate +type SysLogOperateController struct { + // 操作日志服务 + SysLogOperateService service.ISysLogOperate +} + +// 操作日志列表 +// +// GET /list +func (s *SysLogOperateController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + data := s.SysLogOperateService.SelectSysLogOperatePage(querys) + c.JSON(200, result.Ok(data)) +} + +// 操作日志删除 +// +// DELETE /:operIds +func (s *SysLogOperateController) Remove(c *gin.Context) { + operIds := c.Param("operIds") + if operIds == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 处理字符转id数组后去重 + ids := strings.Split(operIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows := s.SysLogOperateService.DeleteSysLogOperateByIds(uniqueIDs) + if rows > 0 { + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 操作日志清空 +// +// DELETE /clean +func (s *SysLogOperateController) Clean(c *gin.Context) { + err := s.SysLogOperateService.CleanSysLogOperate() + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + c.JSON(200, result.Ok(nil)) +} + +// 导出操作日志 +// +// POST /export +func (s *SysLogOperateController) Export(c *gin.Context) { + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + data := s.SysLogOperateService.SelectSysLogOperatePage(querys) + if data["total"].(int64) == 0 { + c.JSON(200, result.ErrMsg("导出数据记录为空")) + return + } + rows := data["rows"].([]model.SysLogOperate) + + // 导出文件名称 + fileName := fmt.Sprintf("sys_log_operate_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "操作序号", + "B1": "操作模块", + "C1": "业务类型", + "D1": "请求方法", + "E1": "请求方式", + "F1": "操作类别", + "G1": "操作人员", + "H1": "部门名称", + "I1": "请求地址", + "J1": "操作地址", + "K1": "操作地点", + "L1": "请求参数", + "M1": "操作消息", + "N1": "状态", + "O1": "消耗时间(毫秒)", + "P1": "操作时间", + } + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 业务类型 + businessType := "" + // 操作类别 + OperatorType := "" + // 状态 + statusValue := "失败" + if row.Status == "1" { + statusValue = "成功" + } + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.OperID, + "B" + idx: row.Title, + "C" + idx: businessType, + "D" + idx: row.Method, + "E" + idx: row.RequestMethod, + "F" + idx: OperatorType, + "G" + idx: row.OperName, + "H" + idx: row.DeptName, + "I" + idx: row.OperURL, + "J" + idx: row.OperIP, + "K" + idx: row.OperLocation, + "L" + idx: row.OperParam, + "M" + idx: row.OperMsg, + "N" + idx: statusValue, + "O" + idx: row.CostTime, + "P" + idx: date.ParseDateToStr(row.OperTime, date.YYYY_MM_DD_HH_MM_SS), + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} diff --git a/src/modules/system/controller/sys_menu.go b/src/modules/system/controller/sys_menu.go new file mode 100644 index 00000000..6e75ee73 --- /dev/null +++ b/src/modules/system/controller/sys_menu.go @@ -0,0 +1,287 @@ +package controller + +import ( + "fmt" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/constants/menu" + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/regular" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysMenuController 结构体 +var NewSysMenu = &SysMenuController{ + sysMenuService: service.NewSysMenuImpl, +} + +// 菜单信息 +// +// PATH /system/menu +type SysMenuController struct { + // 菜单服务 + sysMenuService service.ISysMenu +} + +// 菜单列表 +// +// GET /list +func (s *SysMenuController) List(c *gin.Context) { + query := model.SysMenu{} + if v, ok := c.GetQuery("menuName"); ok { + query.MenuName = v + } + if v, ok := c.GetQuery("status"); ok { + query.Status = v + } + + userId := ctx.LoginUserToUserID(c) + if config.IsAdmin(userId) { + userId = "*" + } + data := s.sysMenuService.SelectMenuList(query, userId) + c.JSON(200, result.OkData(data)) +} + +// 菜单信息 +// +// GET /:menuId +func (s *SysMenuController) Info(c *gin.Context) { + menuId := c.Param("menuId") + if menuId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysMenuService.SelectMenuById(menuId) + if data.MenuID == menuId { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 菜单新增 +// +// POST / +func (s *SysMenuController) Add(c *gin.Context) { + var body model.SysMenu + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.MenuID != "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 目录和菜单检查地址唯一 + if menu.TYPE_DIR == body.MenuType || menu.TYPE_MENU == body.MenuType { + uniqueNenuPath := s.sysMenuService.CheckUniqueMenuPath(body.Path, "") + if !uniqueNenuPath { + msg := fmt.Sprintf("菜单新增【%s】失败,菜单路由地址已存在", body.MenuName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + // 检查名称唯一 + uniqueNenuName := s.sysMenuService.CheckUniqueMenuName(body.MenuName, body.ParentID, "") + if !uniqueNenuName { + msg := fmt.Sprintf("菜单新增【%s】失败,菜单名称已存在", body.MenuName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 外链菜单需要符合网站http(s)开头 + if body.IsFrame == common.STATUS_NO && !regular.ValidHttp(body.Path) { + msg := fmt.Sprintf("菜单新增【%s】失败,非内部地址必须以http(s)://开头", body.MenuName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysMenuService.InsertMenu(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 菜单修改 +// +// PUT / +func (s *SysMenuController) Edit(c *gin.Context) { + var body model.SysMenu + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.MenuID == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 上级菜单不能选自己 + if body.MenuID == body.ParentID { + msg := fmt.Sprintf("菜单修改【%s】失败,上级菜单不能选择自己", body.MenuName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查数据是否存在 + menuInfo := s.sysMenuService.SelectMenuById(body.MenuID) + if menuInfo.MenuID != body.MenuID { + c.JSON(200, result.ErrMsg("没有权限访问菜单数据")) + return + } + // 父级ID不为0是要检查 + if body.ParentID != "0" { + menuParent := s.sysMenuService.SelectMenuById(body.ParentID) + if menuParent.MenuID != body.ParentID { + c.JSON(200, result.ErrMsg("没有权限访问菜单数据")) + return + } + // 禁用菜单时检查父菜单是否使用 + if body.Status == common.STATUS_YES && menuParent.Status == common.STATUS_NO { + c.JSON(200, result.ErrMsg("上级菜单未启用!")) + return + } + } + + // 目录和菜单检查地址唯一 + if menu.TYPE_DIR == body.MenuType || menu.TYPE_MENU == body.MenuType { + uniqueNenuPath := s.sysMenuService.CheckUniqueMenuPath(body.Path, body.MenuID) + if !uniqueNenuPath { + msg := fmt.Sprintf("菜单修改【%s】失败,菜单路由地址已存在", body.MenuName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + // 检查名称唯一 + uniqueNenuName := s.sysMenuService.CheckUniqueMenuName(body.MenuName, body.ParentID, body.MenuID) + if !uniqueNenuName { + msg := fmt.Sprintf("菜单修改【%s】失败,菜单名称已存在", body.MenuName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 外链菜单需要符合网站http(s)开头 + if body.IsFrame == common.STATUS_NO && !regular.ValidHttp(body.Path) { + msg := fmt.Sprintf("菜单修改【%s】失败,非内部地址必须以http(s)://开头", body.MenuName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 禁用菜单时检查子菜单是否使用 + if body.Status == common.STATUS_NO { + hasStatus := s.sysMenuService.HasChildByMenuIdAndStatus(body.MenuID, common.STATUS_YES) + if hasStatus > 0 { + msg := fmt.Sprintf("不允许禁用,存在使用子菜单数:%d", hasStatus) + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysMenuService.UpdateMenu(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 菜单删除 +// +// DELETE /:menuId +func (s *SysMenuController) Remove(c *gin.Context) { + menuId := c.Param("menuId") + if menuId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查数据是否存在 + menu := s.sysMenuService.SelectMenuById(menuId) + if menu.MenuID != menuId { + c.JSON(200, result.ErrMsg("没有权限访问菜单数据!")) + return + } + + // 检查是否存在子菜单 + hasChild := s.sysMenuService.HasChildByMenuIdAndStatus(menuId, "") + if hasChild > 0 { + msg := fmt.Sprintf("不允许删除,存在子菜单数:%d", hasChild) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查是否分配给角色 + existRole := s.sysMenuService.CheckMenuExistRole(menuId) + if existRole > 0 { + msg := fmt.Sprintf("不允许删除,菜单已分配给角色数:%d", existRole) + c.JSON(200, result.ErrMsg(msg)) + return + } + + rows := s.sysMenuService.DeleteMenuById(menuId) + if rows > 0 { + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 菜单树结构列表 +// +// GET /treeSelect +func (s *SysMenuController) TreeSelect(c *gin.Context) { + query := model.SysMenu{} + if v, ok := c.GetQuery("menuName"); ok { + query.MenuName = v + } + if v, ok := c.GetQuery("status"); ok { + query.Status = v + } + + userId := ctx.LoginUserToUserID(c) + if config.IsAdmin(userId) { + userId = "*" + } + data := s.sysMenuService.SelectMenuTreeSelectByUserId(query, userId) + c.JSON(200, result.OkData(data)) + +} + +// 菜单树结构列表(指定角色) +// +// GET /roleMenuTreeSelect/:roleId +func (s *SysMenuController) RoleMenuTreeSelect(c *gin.Context) { + roleId := c.Param("roleId") + if roleId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + query := model.SysMenu{} + if v, ok := c.GetQuery("menuName"); ok { + query.MenuName = v + } + if v, ok := c.GetQuery("status"); ok { + query.Status = v + } + + userId := ctx.LoginUserToUserID(c) + if config.IsAdmin(userId) { + userId = "*" + } + menuTreeSelect := s.sysMenuService.SelectMenuTreeSelectByUserId(query, userId) + checkedKeys := s.sysMenuService.SelectMenuListByRoleId(roleId) + c.JSON(200, result.OkData(map[string]any{ + "menus": menuTreeSelect, + "checkedKeys": checkedKeys, + })) +} diff --git a/src/modules/system/controller/sys_notice.go b/src/modules/system/controller/sys_notice.go new file mode 100644 index 00000000..29e18619 --- /dev/null +++ b/src/modules/system/controller/sys_notice.go @@ -0,0 +1,126 @@ +package controller + +import ( + "fmt" + "strings" + + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysNoticeController 结构体 +var NewSysNotice = &SysNoticeController{ + sysNoticeService: service.NewSysNoticeImpl, +} + +// 通知公告信息 +// +// PATH /system/notice +type SysNoticeController struct { + // 公告服务 + sysNoticeService service.ISysNotice +} + +// 通知公告列表 +// +// GET /list +func (s *SysNoticeController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + data := s.sysNoticeService.SelectNoticePage(querys) + c.JSON(200, result.Ok(data)) +} + +// 通知公告信息 +// +// GET /:noticeId +func (s *SysNoticeController) Info(c *gin.Context) { + noticeId := c.Param("noticeId") + if noticeId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysNoticeService.SelectNoticeById(noticeId) + if data.NoticeID == noticeId { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 通知公告新增 +// +// POST / +func (s *SysNoticeController) Add(c *gin.Context) { + var body model.SysNotice + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.NoticeID != "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysNoticeService.InsertNotice(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 通知公告修改 +// +// PUT / +func (s *SysNoticeController) Edit(c *gin.Context) { + var body model.SysNotice + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.NoticeID == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否存在 + notice := s.sysNoticeService.SelectNoticeById(body.NoticeID) + if notice.NoticeID != body.NoticeID { + c.JSON(200, result.ErrMsg("没有权限访问公告信息数据!")) + return + } + + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysNoticeService.UpdateNotice(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 通知公告删除 +// +// DELETE /:noticeIds +func (s *SysNoticeController) Remove(c *gin.Context) { + noticeIds := c.Param("noticeIds") + if noticeIds == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(noticeIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows, err := s.sysNoticeService.DeleteNoticeByIds(uniqueIDs) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) +} diff --git a/src/modules/system/controller/sys_post.go b/src/modules/system/controller/sys_post.go new file mode 100644 index 00000000..e38eca3c --- /dev/null +++ b/src/modules/system/controller/sys_post.go @@ -0,0 +1,211 @@ +package controller + +import ( + "fmt" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysPostController 结构体 +var NewSysPost = &SysPostController{ + sysPostService: service.NewSysPostImpl, +} + +// 岗位信息 +// +// PATH /system/post +type SysPostController struct { + // 岗位服务 + sysPostService service.ISysPost +} + +// 岗位列表 +// +// GET /list +func (s *SysPostController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + data := s.sysPostService.SelectPostPage(querys) + c.JSON(200, result.Ok(data)) +} + +// 岗位信息 +// +// GET /:postId +func (s *SysPostController) Info(c *gin.Context) { + postId := c.Param("postId") + if postId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysPostService.SelectPostById(postId) + if data.PostID == postId { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 岗位新增 +// +// POST / +func (s *SysPostController) Add(c *gin.Context) { + var body model.SysPost + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.PostID != "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查名称唯一 + uniqueuPostName := s.sysPostService.CheckUniquePostName(body.PostName, "") + if !uniqueuPostName { + msg := fmt.Sprintf("岗位新增【%s】失败,岗位名称已存在", body.PostName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查编码属性值唯一 + uniquePostCode := s.sysPostService.CheckUniquePostCode(body.PostCode, "") + if !uniquePostCode { + msg := fmt.Sprintf("岗位新增【%s】失败,岗位编码已存在", body.PostCode) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysPostService.InsertPost(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 岗位修改 +// +// PUT / +func (s *SysPostController) Edit(c *gin.Context) { + var body model.SysPost + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.PostID == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否存在 + post := s.sysPostService.SelectPostById(body.PostID) + if post.PostID != body.PostID { + c.JSON(200, result.ErrMsg("没有权限访问岗位数据!")) + return + } + + // 检查名称唯一 + uniqueuPostName := s.sysPostService.CheckUniquePostName(body.PostName, body.PostID) + if !uniqueuPostName { + msg := fmt.Sprintf("岗位修改【%s】失败,岗位名称已存在", body.PostName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查编码属性值唯一 + uniquePostCode := s.sysPostService.CheckUniquePostCode(body.PostCode, body.PostID) + if !uniquePostCode { + msg := fmt.Sprintf("岗位修改【%s】失败,岗位编码已存在", body.PostCode) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysPostService.UpdatePost(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 岗位删除 +// +// DELETE /:postIds +func (s *SysPostController) Remove(c *gin.Context) { + postIds := c.Param("postIds") + if postIds == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(postIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows, err := s.sysPostService.DeletePostByIds(uniqueIDs) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) +} + +// 导出岗位信息 +// +// POST /export +func (s *SysPostController) Export(c *gin.Context) { + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + data := s.sysPostService.SelectPostPage(querys) + if data["total"].(int64) == 0 { + c.JSON(200, result.ErrMsg("导出数据记录为空")) + return + } + rows := data["rows"].([]model.SysPost) + + // 导出文件名称 + fileName := fmt.Sprintf("post_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "岗位编号", + "B1": "岗位编码", + "C1": "岗位名称", + "D1": "岗位排序", + "E1": "状态", + } + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + statusValue := "停用" + if row.Status == "1" { + statusValue = "正常" + } + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.PostID, + "B" + idx: row.PostCode, + "C" + idx: row.PostName, + "D" + idx: row.PostSort, + "E" + idx: statusValue, + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} diff --git a/src/modules/system/controller/sys_profile.go b/src/modules/system/controller/sys_profile.go new file mode 100644 index 00000000..e0f59990 --- /dev/null +++ b/src/modules/system/controller/sys_profile.go @@ -0,0 +1,282 @@ +package controller + +import ( + "fmt" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/constants/admin" + "ems.agt/src/framework/constants/uploadsubpath" + "ems.agt/src/framework/utils/crypto" + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/regular" + "ems.agt/src/framework/utils/token" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysProfileController 结构体 +var NewSysProfile = &SysProfileController{ + sysUserService: service.NewSysUserImpl, + sysRoleService: service.NewSysRoleImpl, + sysPostService: service.NewSysPostImpl, + sysMenuService: service.NewSysMenuImpl, +} + +// 个人信息 +// +// PATH /system/user/profile +type SysProfileController struct { + // 用户服务 + sysUserService service.ISysUser + // 角色服务 + sysRoleService service.ISysRole + // 岗位服务 + sysPostService service.ISysPost + // 菜单服务 + sysMenuService service.ISysMenu +} + +// 个人信息 +// +// GET / +func (s *SysProfileController) Info(c *gin.Context) { + loginUser, err := ctx.LoginUser(c) + if err != nil { + c.JSON(401, result.CodeMsg(401, err.Error())) + return + } + + // 查询用户所属角色组 + roleGroup := []string{} + roles := s.sysRoleService.SelectRoleListByUserId(loginUser.UserID) + for _, role := range roles { + roleGroup = append(roleGroup, role.RoleName) + } + isAdmin := config.IsAdmin(loginUser.UserID) + if isAdmin { + roleGroup = append(roleGroup, "管理员") + } + + // 查询用户所属岗位组 + postGroup := []string{} + posts := s.sysPostService.SelectPostListByUserId(loginUser.UserID) + for _, post := range posts { + postGroup = append(postGroup, post.PostName) + } + + c.JSON(200, result.OkData(map[string]any{ + "user": loginUser.User, + "roleGroup": parse.RemoveDuplicates(roleGroup), + "postGroup": parse.RemoveDuplicates(postGroup), + })) +} + +// 个人信息修改 +// +// PUT / +func (s *SysProfileController) UpdateProfile(c *gin.Context) { + var body struct { + // 昵称 + NickName string `json:"nickName" binding:"required"` + // 性别 + Sex string `json:"sex" binding:"required"` + // 手机号 + PhoneNumber string `json:"phonenumber"` + // 邮箱 + Email string `json:"email"` + } + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.Sex == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 登录用户信息 + loginUser, err := ctx.LoginUser(c) + if err != nil { + c.JSON(401, result.CodeMsg(401, err.Error())) + return + } + userId := loginUser.UserID + userName := loginUser.User.UserName + + // 检查手机号码格式并判断是否唯一 + if body.PhoneNumber != "" { + if regular.ValidMobile(body.PhoneNumber) { + uniquePhone := s.sysUserService.CheckUniquePhone(body.PhoneNumber, userId) + if !uniquePhone { + msg := fmt.Sprintf("修改用户【%s】失败,手机号码已存在", userName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } else { + msg := fmt.Sprintf("修改用户【%s】失败,手机号码格式错误", userName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } else { + body.PhoneNumber = "nil" + } + + // 检查邮箱格式并判断是否唯一 + if body.Email != "" { + if regular.ValidEmail(body.Email) { + uniqueEmail := s.sysUserService.CheckUniqueEmail(body.Email, userId) + if !uniqueEmail { + msg := fmt.Sprintf("修改用户【%s】失败,邮箱已存在", userName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } else { + msg := fmt.Sprintf("修改用户【%s】失败,邮箱格式错误", userName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } else { + body.Email = "nil" + } + + // 用户基本资料 + sysUser := model.SysUser{ + UserID: userId, + UpdateBy: userName, + NickName: body.NickName, + PhoneNumber: body.PhoneNumber, + Email: body.Email, + Sex: body.Sex, + } + rows := s.sysUserService.UpdateUser(sysUser) + if rows > 0 { + // 更新缓存用户信息 + loginUser.User = s.sysUserService.SelectUserByUserName(userName) + // 用户权限组标识 + isAdmin := config.IsAdmin(sysUser.UserID) + if isAdmin { + loginUser.Permissions = []string{admin.PERMISSION} + } else { + perms := s.sysMenuService.SelectMenuPermsByUserId(sysUser.UserID) + loginUser.Permissions = parse.RemoveDuplicates(perms) + } + // 刷新令牌信息 + token.Cache(&loginUser) + + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.ErrMsg("上传图片异常")) +} + +// 个人重置密码 +// +// PUT /updatePwd +func (s *SysProfileController) UpdatePwd(c *gin.Context) { + var body struct { + // 旧密码 + OldPassword string `json:"oldPassword" binding:"required"` + // 新密码 + NewPassword string `json:"newPassword" binding:"required"` + } + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 登录用户信息 + loginUser, err := ctx.LoginUser(c) + if err != nil { + c.JSON(401, result.CodeMsg(401, err.Error())) + return + } + userId := loginUser.UserID + userName := loginUser.User.UserName + + // 查询当前登录用户信息得到密码值 + user := s.sysUserService.SelectUserById(userId) + if user.UserID != userId { + c.JSON(200, result.ErrMsg("没有权限访问用户数据!")) + return + } + + // 检查匹配用户密码 + oldCompare := crypto.BcryptCompare(body.OldPassword, user.Password) + if !oldCompare { + c.JSON(200, result.ErrMsg("修改密码失败,旧密码错误")) + return + } + newCompare := crypto.BcryptCompare(body.NewPassword, user.Password) + if newCompare { + c.JSON(200, result.ErrMsg("新密码不能与旧密码相同")) + return + } + + // 修改新密码 + sysUser := model.SysUser{ + UserID: userId, + UpdateBy: userName, + Password: body.NewPassword, + } + rows := s.sysUserService.UpdateUser(sysUser) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 个人头像上传 +// +// POST /avatar +func (s *SysProfileController) Avatar(c *gin.Context) { + formFile, err := c.FormFile("file") + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 上传文件转存 + filePath, err := file.TransferUploadFile(formFile, uploadsubpath.AVATART, []string{".jpg", ".jpeg", ".png"}) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + // 登录用户信息 + loginUser, err := ctx.LoginUser(c) + if err != nil { + c.JSON(401, result.CodeMsg(401, err.Error())) + return + } + + // 更新头像地址 + sysUser := model.SysUser{ + UserID: loginUser.UserID, + UpdateBy: loginUser.User.UserName, + Avatar: filePath, + } + rows := s.sysUserService.UpdateUser(sysUser) + if rows > 0 { + // 更新缓存用户信息 + loginUser.User = s.sysUserService.SelectUserByUserName(loginUser.User.UserName) + // 用户权限组标识 + isAdmin := config.IsAdmin(sysUser.UserID) + if isAdmin { + loginUser.Permissions = []string{admin.PERMISSION} + } else { + perms := s.sysMenuService.SelectMenuPermsByUserId(sysUser.UserID) + loginUser.Permissions = parse.RemoveDuplicates(perms) + } + // 刷新令牌信息 + token.Cache(&loginUser) + + c.JSON(200, result.OkData(filePath)) + return + } + c.JSON(200, result.Err(nil)) +} diff --git a/src/modules/system/controller/sys_role.go b/src/modules/system/controller/sys_role.go new file mode 100644 index 00000000..94535a95 --- /dev/null +++ b/src/modules/system/controller/sys_role.go @@ -0,0 +1,408 @@ +package controller + +import ( + "fmt" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/constants/admin" + "ems.agt/src/framework/constants/roledatascope" + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysRoleController 结构体 +var NewSysRole = &SysRoleController{ + sysRoleService: service.NewSysRoleImpl, + sysUserService: service.NewSysUserImpl, +} + +// 角色信息 +// +// PATH /system/role +type SysRoleController struct { + // 角色服务 + sysRoleService service.ISysRole + // 用户服务 + sysUserService service.ISysUser +} + +// 角色列表 +// +// GET /list +func (s *SysRoleController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "") + data := s.sysRoleService.SelectRolePage(querys, dataScopeSQL) + c.JSON(200, result.Ok(data)) +} + +// 角色信息详情 +// +// GET /:roleId +func (s *SysRoleController) Info(c *gin.Context) { + roleId := c.Param("roleId") + if roleId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysRoleService.SelectRoleById(roleId) + if data.RoleID == roleId { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 角色信息新增 +// +// POST / +func (s *SysRoleController) Add(c *gin.Context) { + var body model.SysRole + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.RoleID != "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 判断角色名称是否唯一 + uniqueRoleName := s.sysRoleService.CheckUniqueRoleName(body.RoleName, "") + if !uniqueRoleName { + msg := fmt.Sprintf("角色新增【%s】失败,角色名称已存在", body.RoleName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 判断角色键值是否唯一 + uniqueRoleKey := s.sysRoleService.CheckUniqueRoleKey(body.RoleKey, "") + if !uniqueRoleKey { + msg := fmt.Sprintf("角色新增【%s】失败,角色键值已存在", body.RoleName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysRoleService.InsertRole(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 角色信息修改 +// +// PUT / +func (s *SysRoleController) Edit(c *gin.Context) { + var body model.SysRole + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.RoleID == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否管理员角色 + if body.RoleID == admin.ROLE_ID { + c.JSON(200, result.ErrMsg("不允许操作管理员角色")) + return + } + + // 检查是否存在 + role := s.sysRoleService.SelectRoleById(body.RoleID) + if role.RoleID != body.RoleID { + c.JSON(200, result.ErrMsg("没有权限访问角色数据!")) + return + } + + // 判断角色名称是否唯一 + uniqueRoleName := s.sysRoleService.CheckUniqueRoleName(body.RoleName, body.RoleID) + if !uniqueRoleName { + msg := fmt.Sprintf("角色修改【%s】失败,角色名称已存在", body.RoleName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 判断角色键值是否唯一 + uniqueRoleKey := s.sysRoleService.CheckUniqueRoleKey(body.RoleKey, body.RoleID) + if !uniqueRoleKey { + msg := fmt.Sprintf("角色修改【%s】失败,角色键值已存在", body.RoleName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysRoleService.UpdateRole(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 角色信息删除 +// +// DELETE /:roleIds +func (s *SysRoleController) Remove(c *gin.Context) { + roleIds := c.Param("roleIds") + if roleIds == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(roleIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + // 检查是否管理员角色 + for _, id := range uniqueIDs { + if id == admin.ROLE_ID { + c.JSON(200, result.ErrMsg("不允许操作管理员角色")) + return + } + } + rows, err := s.sysRoleService.DeleteRoleByIds(uniqueIDs) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) +} + +// 角色状态变更 +// +// PUT /changeStatus +func (s *SysRoleController) Status(c *gin.Context) { + var body struct { + // 角色ID + RoleID string `json:"roleId" binding:"required"` + // 状态 + Status string `json:"status" binding:"required"` + } + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否管理员角色 + if body.RoleID == admin.ROLE_ID { + c.JSON(200, result.ErrMsg("不允许操作管理员角色")) + return + } + + // 检查是否存在 + role := s.sysRoleService.SelectRoleById(body.RoleID) + if role.RoleID != body.RoleID { + c.JSON(200, result.ErrMsg("没有权限访问角色数据!")) + return + } + + // 与旧值相等不变更 + if role.Status == body.Status { + c.JSON(200, result.ErrMsg("变更状态与旧值相等!")) + return + } + + // 更新状态不刷新缓存 + userName := ctx.LoginUserToUserName(c) + SysRoleController := model.SysRole{ + RoleID: body.RoleID, + Status: body.Status, + UpdateBy: userName, + } + rows := s.sysRoleService.UpdateRole(SysRoleController) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 角色数据权限修改 +// +// PUT /dataScope +func (s *SysRoleController) DataScope(c *gin.Context) { + var body struct { + // 角色ID + RoleID string `json:"roleId"` + // 部门组(数据权限) + DeptIds []string `json:"deptIds"` + // 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限) + DataScope string `json:"dataScope"` + // 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示) + DeptCheckStrictly string `json:"deptCheckStrictly"` + } + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否管理员角色 + if body.RoleID == admin.ROLE_ID { + c.JSON(200, result.ErrMsg("不允许操作管理员角色")) + return + } + + // 检查是否存在 + role := s.sysRoleService.SelectRoleById(body.RoleID) + if role.RoleID != body.RoleID { + c.JSON(200, result.ErrMsg("没有权限访问角色数据!")) + return + } + + // 更新数据权限 + userName := ctx.LoginUserToUserName(c) + SysRoleController := model.SysRole{ + RoleID: body.RoleID, + DeptIds: body.DeptIds, + DataScope: body.DataScope, + DeptCheckStrictly: body.DeptCheckStrictly, + UpdateBy: userName, + } + rows := s.sysRoleService.AuthDataScope(SysRoleController) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 角色分配用户列表 +// +// GET /authUser/allocatedList +func (s *SysRoleController) AuthUserAllocatedList(c *gin.Context) { + querys := ctx.QueryMap(c) + roleId, ok := querys["roleId"] + if !ok || roleId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否存在 + role := s.sysRoleService.SelectRoleById(roleId.(string)) + if role.RoleID != roleId { + c.JSON(200, result.ErrMsg("没有权限访问角色数据!")) + return + } + + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "u") + data := s.sysUserService.SelectAllocatedPage(querys, dataScopeSQL) + c.JSON(200, result.Ok(data)) +} + +// 角色分配选择授权 +// +// PUT /authUser/checked +func (s *SysRoleController) AuthUserChecked(c *gin.Context) { + var body struct { + // 角色ID + RoleID string `json:"roleId" binding:"required"` + // 用户ID组 + UserIDs string `json:"userIds" binding:"required"` + // 选择操作 添加true 取消false + Checked bool `json:"checked"` + } + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 处理字符转id数组后去重 + ids := strings.Split(body.UserIDs, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + + // 检查是否存在 + role := s.sysRoleService.SelectRoleById(body.RoleID) + if role.RoleID != body.RoleID { + c.JSON(200, result.ErrMsg("没有权限访问角色数据!")) + return + } + + var rows int64 + if body.Checked { + rows = s.sysRoleService.InsertAuthUsers(body.RoleID, uniqueIDs) + } else { + rows = s.sysRoleService.DeleteAuthUsers(body.RoleID, uniqueIDs) + } + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 导出角色信息 +// +// POST /export +func (s *SysRoleController) Export(c *gin.Context) { + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "") + data := s.sysRoleService.SelectRolePage(querys, dataScopeSQL) + if data["total"].(int64) == 0 { + c.JSON(200, result.ErrMsg("导出数据记录为空")) + return + } + rows := data["rows"].([]model.SysRole) + + // 导出文件名称 + fileName := fmt.Sprintf("role_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "角色序号", + "B1": "角色名称", + "C1": "角色权限", + "D1": "角色排序", + "E1": "数据范围", + "F1": "角色状态", + } + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 数据范围 + dataScope := "空" + if v, ok := roledatascope.RoleDataScope[row.DataScope]; ok { + dataScope = v + } + // 角色状态 + statusValue := "停用" + if row.Status == "1" { + statusValue = "正常" + } + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.RoleID, + "B" + idx: row.RoleName, + "C" + idx: row.RoleKey, + "D" + idx: row.RoleSort, + "E" + idx: dataScope, + "F" + idx: statusValue, + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} diff --git a/src/modules/system/controller/sys_user.go b/src/modules/system/controller/sys_user.go new file mode 100644 index 00000000..220cd2df --- /dev/null +++ b/src/modules/system/controller/sys_user.go @@ -0,0 +1,481 @@ +package controller + +import ( + "fmt" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/constants/admin" + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/regular" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysUserController 结构体 +var NewSysUser = &SysUserController{ + sysUserService: service.NewSysUserImpl, + sysRoleService: service.NewSysRoleImpl, + sysPostService: service.NewSysPostImpl, + sysDictDataService: service.NewSysDictDataImpl, +} + +// 用户信息 +// +// PATH /system/user +type SysUserController struct { + // 用户服务 + sysUserService service.ISysUser + // 角色服务 + sysRoleService service.ISysRole + // 岗位服务 + sysPostService service.ISysPost + // 字典数据服务 + sysDictDataService service.ISysDictData +} + +// 用户信息列表 +// +// GET /list +func (s *SysUserController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "u") + data := s.sysUserService.SelectUserPage(querys, dataScopeSQL) + c.JSON(200, result.Ok(data)) +} + +// 用户信息详情 +// +// GET /:userId +func (s *SysUserController) Info(c *gin.Context) { + userId := c.Param("userId") + if userId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // 查询系统角色列表 + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "") + roles := s.sysRoleService.SelectRoleList(model.SysRole{}, dataScopeSQL) + + // 不是系统指定管理员需要排除其角色 + if !config.IsAdmin(userId) { + rolesFilter := make([]model.SysRole, 0) + for _, r := range roles { + if r.RoleID != admin.ROLE_ID { + rolesFilter = append(rolesFilter, r) + } + } + roles = rolesFilter + } + + // 查询系统岗位列表 + posts := s.sysPostService.SelectPostList(model.SysPost{}) + + // 新增用户时,用户ID为0 + if userId == "0" { + c.JSON(200, result.OkData(map[string]any{ + "user": map[string]any{}, + "roleIds": []string{}, + "postIds": []string{}, + "roles": roles, + "posts": posts, + })) + return + } + + // 检查用户是否存在 + user := s.sysUserService.SelectUserById(userId) + if user.UserID != userId { + c.JSON(200, result.ErrMsg("没有权限访问用户数据!")) + return + } + + // 角色ID组 + roleIds := make([]string, 0) + for _, r := range user.Roles { + roleIds = append(roleIds, r.RoleID) + } + + // 岗位ID组 + postIds := make([]string, 0) + userPosts := s.sysPostService.SelectPostListByUserId(userId) + for _, p := range userPosts { + postIds = append(postIds, p.PostID) + } + + c.JSON(200, result.OkData(map[string]any{ + "user": user, + "roleIds": roleIds, + "postIds": postIds, + "roles": roles, + "posts": posts, + })) +} + +// 用户信息新增 +// +// POST / +func (s *SysUserController) Add(c *gin.Context) { + var body model.SysUser + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.UserID != "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查用户登录账号是否唯一 + uniqueUserName := s.sysUserService.CheckUniqueUserName(body.UserName, "") + if !uniqueUserName { + msg := fmt.Sprintf("新增用户【%s】失败,登录账号已存在", body.UserName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查手机号码格式并判断是否唯一 + if body.PhoneNumber != "" { + if regular.ValidMobile(body.PhoneNumber) { + uniquePhone := s.sysUserService.CheckUniquePhone(body.PhoneNumber, "") + if !uniquePhone { + msg := fmt.Sprintf("新增用户【%s】失败,手机号码已存在", body.UserName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } else { + msg := fmt.Sprintf("新增用户【%s】失败,手机号码格式错误", body.UserName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + // 检查邮箱格式并判断是否唯一 + if body.Email != "" { + if regular.ValidEmail(body.Email) { + uniqueEmail := s.sysUserService.CheckUniqueEmail(body.Email, "") + if !uniqueEmail { + msg := fmt.Sprintf("新增用户【%s】失败,邮箱已存在", body.UserName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } else { + msg := fmt.Sprintf("新增用户【%s】失败,邮箱格式错误", body.UserName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysUserService.InsertUser(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 用户信息修改 +// +// POST / +func (s *SysUserController) Edit(c *gin.Context) { + var body model.SysUser + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.UserID == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否管理员用户 + if config.IsAdmin(body.UserID) { + c.JSON(200, result.ErrMsg("不允许操作管理员用户")) + return + } + + user := s.sysUserService.SelectUserById(body.UserID) + if user.UserID != body.UserID { + c.JSON(200, result.ErrMsg("没有权限访问用户数据!")) + return + } + + // 检查用户登录账号是否唯一 + uniqueUserName := s.sysUserService.CheckUniqueUserName(body.UserName, body.UserID) + if !uniqueUserName { + msg := fmt.Sprintf("修改用户【%s】失败,登录账号已存在", body.UserName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查手机号码格式并判断是否唯一 + if body.PhoneNumber != "" { + if regular.ValidMobile(body.PhoneNumber) { + uniquePhone := s.sysUserService.CheckUniquePhone(body.PhoneNumber, body.UserID) + if !uniquePhone { + msg := fmt.Sprintf("修改用户【%s】失败,手机号码已存在", body.UserName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } else { + msg := fmt.Sprintf("修改用户【%s】失败,手机号码格式错误", body.UserName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + // 检查邮箱格式并判断是否唯一 + if body.Email != "" { + if regular.ValidEmail(body.Email) { + uniqueEmail := s.sysUserService.CheckUniqueEmail(body.Email, body.UserID) + if !uniqueEmail { + msg := fmt.Sprintf("修改用户【%s】失败,邮箱已存在", body.UserName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } else { + msg := fmt.Sprintf("修改用户【%s】失败,邮箱格式错误", body.UserName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + body.UserName = "" // 忽略修改登录用户名称 + body.Password = "" // 忽略修改密码 + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysUserService.UpdateUserAndRolePost(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 用户信息删除 +// +// DELETE /:userIds +func (s *SysUserController) Remove(c *gin.Context) { + userIds := c.Param("userIds") + if userIds == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(userIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows, err := s.sysUserService.DeleteUserByIds(uniqueIDs) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) +} + +// 用户重置密码 +// +// PUT /resetPwd +func (s *SysUserController) ResetPwd(c *gin.Context) { + var body struct { + UserID string `json:"userId" binding:"required"` + Password string `json:"password" binding:"required"` + } + if err := c.ShouldBindBodyWith(&body, binding.JSON); err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否管理员用户 + if config.IsAdmin(body.UserID) { + c.JSON(200, result.ErrMsg("不允许操作管理员用户")) + return + } + + user := s.sysUserService.SelectUserById(body.UserID) + if user.UserID != body.UserID { + c.JSON(200, result.ErrMsg("没有权限访问用户数据!")) + return + } + if !regular.ValidPassword(body.Password) { + c.JSON(200, result.ErrMsg("登录密码至少包含大小写字母、数字、特殊符号,且不少于6位")) + return + } + + userName := ctx.LoginUserToUserName(c) + SysUserController := model.SysUser{ + UserID: body.UserID, + Password: body.Password, + UpdateBy: userName, + } + rows := s.sysUserService.UpdateUser(SysUserController) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 用户状态修改 +// +// PUT /changeStatus +func (s *SysUserController) Status(c *gin.Context) { + var body struct { + UserID string `json:"userId" binding:"required"` + Status string `json:"status" binding:"required"` + } + if err := c.ShouldBindBodyWith(&body, binding.JSON); err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否存在 + user := s.sysUserService.SelectUserById(body.UserID) + if user.UserID != body.UserID { + c.JSON(200, result.ErrMsg("没有权限访问用户数据!")) + return + } + + // 与旧值相等不变更 + if user.Status == body.Status { + c.JSON(200, result.ErrMsg("变更状态与旧值相等!")) + return + } + + userName := ctx.LoginUserToUserName(c) + SysUserController := model.SysUser{ + UserID: body.UserID, + Status: body.Status, + UpdateBy: userName, + } + rows := s.sysUserService.UpdateUser(SysUserController) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 用户信息列表导出 +// +// POST /export +func (s *SysUserController) Export(c *gin.Context) { + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "u") + data := s.sysUserService.SelectUserPage(querys, dataScopeSQL) + if data["total"].(int64) == 0 { + c.JSON(200, result.ErrMsg("导出数据记录为空")) + return + } + rows := data["rows"].([]model.SysUser) + + // 导出文件名称 + fileName := fmt.Sprintf("user_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "用户序号", + "B1": "登录名称", + "C1": "用户名称", + "D1": "用户邮箱", + "E1": "手机号码", + "F1": "用户性别", + "G1": "帐号状态", + "H1": "最后登录IP", + "I1": "最后登录时间", + "J1": "部门名称", + "K1": "部门负责人", + } + // 读取用户性别字典数据 + dictSysUserSex := s.sysDictDataService.SelectDictDataByType("sys_user_sex") + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 用户性别 + sysUserSex := "未知" + for _, v := range dictSysUserSex { + if row.Sex == v.DictValue { + sysUserSex = v.DictLabel + break + } + } + // 帐号状态 + statusValue := "停用" + if row.Status == "1" { + statusValue = "正常" + } + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.UserID, + "B" + idx: row.UserName, + "C" + idx: row.NickName, + "D" + idx: row.Email, + "E" + idx: row.PhoneNumber, + "F" + idx: sysUserSex, + "G" + idx: statusValue, + "H" + idx: row.LoginIP, + "I" + idx: date.ParseDateToStr(row.LoginDate, date.YYYY_MM_DD_HH_MM_SS), + "J" + idx: row.Dept.DeptName, + "K" + idx: row.Dept.Leader, + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} + +// 用户信息列表导入模板下载 +// +// GET /importTemplate +func (s *SysUserController) Template(c *gin.Context) { + fileName := fmt.Sprintf("user_import_template_%d.xlsx", time.Now().UnixMilli()) + asserPath := "assets/template/excel/user_import_template.xlsx" + c.FileAttachment(asserPath, fileName) +} + +// 用户信息列表导入 +// +// POST /importData +func (s *SysUserController) ImportData(c *gin.Context) { + // 允许进行更新 + updateSupport := c.PostForm("updateSupport") + // 上传的文件 + formFile, err := c.FormFile("file") + if err != nil || updateSupport == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 保存表格文件 + filePath, err := file.TransferExeclUploadFile(formFile) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + // 读取表格数据 + rows, err := file.ReadSheet(filePath, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + // 获取操作人名称 + operName := ctx.LoginUserToUserName(c) + isUpdateSupport := parse.Boolean(updateSupport) + message := s.sysUserService.ImportUser(rows, isUpdateSupport, operName) + c.JSON(200, result.OkMsg(message)) +} diff --git a/src/modules/system/model/sys_config.go b/src/modules/system/model/sys_config.go new file mode 100644 index 00000000..8949c67b --- /dev/null +++ b/src/modules/system/model/sys_config.go @@ -0,0 +1,25 @@ +package model + +// 参数配置对象 sys_config +type SysConfig struct { + // 参数主键 + ConfigID string `json:"configId"` + // 参数名称 + ConfigName string `json:"configName" binding:"required"` + // 参数键名 + ConfigKey string `json:"configKey" binding:"required"` + // 参数键值 + ConfigValue string `json:"configValue" binding:"required"` + // 系统内置(Y是 N否) + ConfigType string `json:"configType"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + // 备注 + Remark string `json:"remark"` +} diff --git a/src/modules/system/model/sys_dept.go b/src/modules/system/model/sys_dept.go new file mode 100644 index 00000000..b7a08f1e --- /dev/null +++ b/src/modules/system/model/sys_dept.go @@ -0,0 +1,41 @@ +package model + +// SysDept 部门对象 sys_dept +type SysDept struct { + // 部门ID + DeptID string `json:"deptId"` + // 父部门ID + ParentID string `json:"parentId" binding:"required"` + // 祖级列表 + Ancestors string `json:"ancestors"` + // 部门名称 + DeptName string `json:"deptName" binding:"required"` + // 显示顺序 + OrderNum int `json:"orderNum"` + // 负责人 + Leader string `json:"leader"` + // 联系电话 + Phone string `json:"phone"` + // 邮箱 + Email string `json:"email"` + // 部门状态(0正常 1停用) + Status string `json:"status"` + // 删除标志(0代表存在 1代表删除) + DelFlag string `json:"delFlag"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + + // ====== 非数据库字段属性 ====== + + // 子部门列表 + Children []SysDept `json:"children,omitempty"` + + // 父部门名称 + ParentName string `json:"parentName,omitempty"` +} diff --git a/src/modules/system/model/sys_dict_data.go b/src/modules/system/model/sys_dict_data.go new file mode 100644 index 00000000..7c8cd06a --- /dev/null +++ b/src/modules/system/model/sys_dict_data.go @@ -0,0 +1,31 @@ +package model + +// SysDictData 字典数据对象 sys_dict_data +type SysDictData struct { + // 字典编码 + DictCode string `json:"dictCode"` + // 字典排序 + DictSort int `json:"dictSort"` + // 字典标签 + DictLabel string `json:"dictLabel" binding:"required"` + // 字典键值 + DictValue string `json:"dictValue" binding:"required"` + // 字典类型 + DictType string `json:"dictType" binding:"required"` + // 样式属性(样式扩展) + TagClass string `json:"tagClass"` + // 标签类型(预设颜色) + TagType string `json:"tagType"` + // 状态(0停用 1正常) + Status string `json:"status"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + // 备注 + Remark string `json:"remark"` +} diff --git a/src/modules/system/model/sys_dict_type.go b/src/modules/system/model/sys_dict_type.go new file mode 100644 index 00000000..0ad0b1ab --- /dev/null +++ b/src/modules/system/model/sys_dict_type.go @@ -0,0 +1,23 @@ +package model + +// SysDictType 字典类型对象 sys_dict_type +type SysDictType struct { + // 字典主键 + DictID string `json:"dictId"` + // 字典名称 + DictName string `json:"dictName" binding:"required"` + // 字典类型 + DictType string `json:"dictType" binding:"required"` + // 状态(0停用 1正常) + Status string `json:"status"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + // 备注 + Remark string `json:"remark"` +} diff --git a/src/modules/system/model/sys_log_login.go b/src/modules/system/model/sys_log_login.go new file mode 100644 index 00000000..8548f8de --- /dev/null +++ b/src/modules/system/model/sys_log_login.go @@ -0,0 +1,23 @@ +package model + +// SysLogLogin 系统登录日志表 sys_log_login +type SysLogLogin struct { + // 登录ID + LoginID string `json:"loginId"` + // 用户账号 + UserName string `json:"userName"` + // 登录IP地址 + IPAddr string `json:"ipaddr"` + // 登录地点 + LoginLocation string `json:"loginLocation"` + // 浏览器类型 + Browser string `json:"browser"` + // 操作系统 + OS string `json:"os"` + // 登录状态(0失败 1成功) + Status string `json:"status"` + // 提示消息 + Msg string `json:"msg"` + // 访问时间 + LoginTime int64 `json:"loginTime"` +} diff --git a/src/modules/system/model/sys_log_operate.go b/src/modules/system/model/sys_log_operate.go new file mode 100644 index 00000000..3a0b6041 --- /dev/null +++ b/src/modules/system/model/sys_log_operate.go @@ -0,0 +1,37 @@ +package model + +// SysLogOperate 系统操作日志表 sys_log_operate +type SysLogOperate struct { + // 日志主键 + OperID string `json:"operId"` + // 模块标题 + Title string `json:"title"` + // 业务类型(0其它 1新增 2修改 3删除 4授权 5导出 6导入 7强退 8清空数据) + BusinessType string `json:"businessType"` + // 方法名称 + Method string `json:"method"` + // 请求方式 + RequestMethod string `json:"requestMethod"` + // 操作人员类别(0其它 1后台用户 2手机端用户) + OperatorType string `json:"operatorType"` + // 操作人员 + OperName string `json:"operName"` + // 部门名称 + DeptName string `json:"deptName"` + // 请求URL + OperURL string `json:"operUrl"` + // 主机地址 + OperIP string `json:"operIp"` + // 操作地点 + OperLocation string `json:"operLocation"` + // 请求参数 + OperParam string `json:"operParam"` + // 操作消息 + OperMsg string `json:"operMsg"` + // 操作状态(0异常 1正常) + Status string `json:"status"` + // 操作时间 + OperTime int64 `json:"operTime"` + // 消耗时间(毫秒) + CostTime int64 `json:"costTime"` +} diff --git a/src/modules/system/model/sys_menu.go b/src/modules/system/model/sys_menu.go new file mode 100644 index 00000000..397e30eb --- /dev/null +++ b/src/modules/system/model/sys_menu.go @@ -0,0 +1,46 @@ +package model + +// SysMenu 菜单权限对象 sys_menu +type SysMenu struct { + // 菜单ID + MenuID string `json:"menuId"` + // 菜单名称 + MenuName string `json:"menuName" binding:"required"` + // 父菜单ID 默认0 + ParentID string `json:"parentId" binding:"required"` + // 显示顺序 + MenuSort int `json:"menuSort"` + // 路由地址 + Path string `json:"path"` + // 组件路径 + Component string `json:"component"` + // 是否内部跳转(0否 1是) + IsFrame string `json:"isFrame"` + // 是否缓存(0不缓存 1缓存) + IsCache string `json:"isCache"` + // 菜单类型(D目录 M菜单 B按钮) + MenuType string `json:"menuType" binding:"required"` + // 是否显示(0隐藏 1显示) + Visible string `json:"visible"` + // 菜单状态(0停用 1正常) + Status string `json:"status"` + // 权限标识 + Perms string `json:"perms"` + // 菜单图标(#无图标) + Icon string `json:"icon"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + // 备注 + Remark string `json:"remark"` + + // ====== 非数据库字段属性 ====== + + // 子菜单 + Children []SysMenu `json:"children,omitempty"` +} diff --git a/src/modules/system/model/sys_notice.go b/src/modules/system/model/sys_notice.go new file mode 100644 index 00000000..dc33097a --- /dev/null +++ b/src/modules/system/model/sys_notice.go @@ -0,0 +1,27 @@ +package model + +// SysNotice 通知公告对象 sys_notice +type SysNotice struct { + // 公告ID + NoticeID string `json:"noticeId"` + // 公告标题 + NoticeTitle string `json:"noticeTitle" binding:"required"` + // 公告类型(1通知 2公告) + NoticeType string `json:"noticeType" binding:"required"` + // 公告内容 + NoticeContent string `json:"noticeContent" binding:"required"` + // 公告状态(0关闭 1正常) + Status string `json:"status"` + // 删除标志(0代表存在 1代表删除) + DelFlag string `json:"delFlag"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + // 备注 + Remark string `json:"remark"` +} diff --git a/src/modules/system/model/sys_post.go b/src/modules/system/model/sys_post.go new file mode 100644 index 00000000..7b651971 --- /dev/null +++ b/src/modules/system/model/sys_post.go @@ -0,0 +1,25 @@ +package model + +// SysPost 岗位对象 sys_post +type SysPost struct { + // 岗位ID + PostID string `json:"postId"` + // 岗位编码 + PostCode string `json:"postCode" binding:"required"` + // 岗位名称 + PostName string `json:"postName" binding:"required"` + // 显示顺序 + PostSort int `json:"postSort"` + // 状态(0停用 1正常) + Status string `json:"status"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + // 备注 + Remark string `json:"remark"` +} diff --git a/src/modules/system/model/sys_role.go b/src/modules/system/model/sys_role.go new file mode 100644 index 00000000..c4a8d9eb --- /dev/null +++ b/src/modules/system/model/sys_role.go @@ -0,0 +1,40 @@ +package model + +// SysRole 角色对象 sys_role +type SysRole struct { + // 角色ID + RoleID string `json:"roleId"` + // 角色名称 + RoleName string `json:"roleName" binding:"required"` + // 角色键值 + RoleKey string `json:"roleKey" binding:"required"` + // 显示顺序 + RoleSort int `json:"roleSort"` + // 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限) + DataScope string `json:"dataScope"` + // 菜单树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示) + MenuCheckStrictly string `json:"menuCheckStrictly"` + // 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示) + DeptCheckStrictly string `json:"deptCheckStrictly"` + // 角色状态(0停用 1正常) + Status string `json:"status"` + // 删除标志(0代表存在 1代表删除) + DelFlag string `json:"delFlag"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + // 备注 + Remark string `json:"remark"` + + // ====== 非数据库字段属性 ====== + + // 菜单组 + MenuIds []string `json:"menuIds,omitempty"` + // 部门组(数据权限) + DeptIds []string `json:"deptIds,omitempty"` +} diff --git a/src/modules/system/model/sys_role_dept.go b/src/modules/system/model/sys_role_dept.go new file mode 100644 index 00000000..29f98405 --- /dev/null +++ b/src/modules/system/model/sys_role_dept.go @@ -0,0 +1,15 @@ +package model + +// SysRoleDept 角色和部门关联对象 sys_role_dept +type SysRoleDept struct { + RoleID string `json:"roleId"` // 角色ID + DeptID string `json:"deptId"` // 部门ID +} + +// NewSysRoleDept 创建角色和部门关联对象的构造函数 +func NewSysRoleDept(roleID string, deptID string) SysRoleDept { + return SysRoleDept{ + RoleID: roleID, + DeptID: deptID, + } +} diff --git a/src/modules/system/model/sys_role_menu.go b/src/modules/system/model/sys_role_menu.go new file mode 100644 index 00000000..3d5f74df --- /dev/null +++ b/src/modules/system/model/sys_role_menu.go @@ -0,0 +1,15 @@ +package model + +// SysRoleMenu 角色和菜单关联对象 sys_role_menu +type SysRoleMenu struct { + RoleID string `json:"roleId"` // 角色ID + MenuID string `json:"menuId"` // 菜单ID +} + +// NewSysRoleMenu 创建角色和菜单关联对象的构造函数 +func NewSysRoleMenu(roleID string, menuID string) SysRoleMenu { + return SysRoleMenu{ + RoleID: roleID, + MenuID: menuID, + } +} diff --git a/src/modules/system/model/sys_user.go b/src/modules/system/model/sys_user.go new file mode 100644 index 00000000..c580784f --- /dev/null +++ b/src/modules/system/model/sys_user.go @@ -0,0 +1,56 @@ +package model + +// SysUser 用户对象 sys_user +type SysUser struct { + // 用户ID + UserID string `json:"userId"` + // 部门ID + DeptID string `json:"deptId"` + // 用户账号 + UserName string `json:"userName" binding:"required"` + // 用户昵称 + NickName string `json:"nickName" binding:"required"` + // 用户类型(sys系统用户) + UserType string `json:"userType"` + // 用户邮箱 + Email string `json:"email"` + // 手机号码 + PhoneNumber string `json:"phonenumber"` + // 用户性别(0未知 1男 2女) + Sex string `json:"sex"` + // 头像地址 + Avatar string `json:"avatar"` + // 密码 + Password string `json:"-"` + // 帐号状态(0停用 1正常) + Status string `json:"status"` + // 删除标志(0代表存在 1代表删除) + DelFlag string `json:"delFlag"` + // 最后登录IP + LoginIP string `json:"loginIp"` + // 最后登录时间 + LoginDate int64 `json:"loginDate"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + // 备注 + Remark string `json:"remark"` + + // ====== 非数据库字段属性 ====== + + // 部门对象 + Dept SysDept `json:"dept,omitempty" binding:"structonly"` + // 角色对象组 + Roles []SysRole `json:"roles"` + // 角色ID + RoleID string `json:"roleId,omitempty"` + // 角色组 + RoleIDs []string `json:"roleIds,omitempty"` + // 岗位组 + PostIDs []string `json:"postIds,omitempty"` +} diff --git a/src/modules/system/model/sys_user_post.go b/src/modules/system/model/sys_user_post.go new file mode 100644 index 00000000..557cd2e9 --- /dev/null +++ b/src/modules/system/model/sys_user_post.go @@ -0,0 +1,15 @@ +package model + +// SysUserPost 用户和岗位关联对象 sys_user_post +type SysUserPost struct { + UserID string `json:"userId"` // 用户ID + PostID string `json:"postId"` // 岗位ID +} + +// NewSysUserPost 创建用户和岗位关联对象的构造函数 +func NewSysUserPost(userID string, postID string) SysUserPost { + return SysUserPost{ + UserID: userID, + PostID: postID, + } +} diff --git a/src/modules/system/model/sys_user_role.go b/src/modules/system/model/sys_user_role.go new file mode 100644 index 00000000..e2c44a78 --- /dev/null +++ b/src/modules/system/model/sys_user_role.go @@ -0,0 +1,15 @@ +package model + +// SysUserRole 用户和角色关联对象 sys_user_role +type SysUserRole struct { + UserID string `json:"userId"` // 用户ID + RoleID string `json:"roleId"` // 角色ID +} + +// NewSysUserRole 创建用户和角色关联对象的构造函数 +func NewSysUserRole(userID string, roleID string) SysUserRole { + return SysUserRole{ + UserID: userID, + RoleID: roleID, + } +} diff --git a/src/modules/system/repository/sys_config.go b/src/modules/system/repository/sys_config.go new file mode 100644 index 00000000..ea7cb2bc --- /dev/null +++ b/src/modules/system/repository/sys_config.go @@ -0,0 +1,30 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysConfig 参数配置表 数据层接口 +type ISysConfig interface { + // SelectDictDataPage 分页查询参数配置列表数据 + SelectConfigPage(query map[string]any) map[string]any + + // SelectConfigList 查询参数配置列表 + SelectConfigList(sysConfig model.SysConfig) []model.SysConfig + + // SelectConfigValueByKey 通过参数键名查询参数键值 + SelectConfigValueByKey(configKey string) string + + // SelectConfigByIds 通过配置ID查询参数配置信息 + SelectConfigByIds(configIds []string) []model.SysConfig + + // CheckUniqueConfig 校验配置参数是否唯一 + CheckUniqueConfig(sysConfig model.SysConfig) string + + // InsertConfig 新增参数配置 + InsertConfig(sysConfig model.SysConfig) string + + // UpdateConfig 修改参数配置 + UpdateConfig(sysConfig model.SysConfig) int64 + + // DeleteConfigByIds 批量删除参数配置信息 + DeleteConfigByIds(configIds []string) int64 +} diff --git a/src/modules/system/repository/sys_config.impl.go b/src/modules/system/repository/sys_config.impl.go new file mode 100644 index 00000000..fa5543ee --- /dev/null +++ b/src/modules/system/repository/sys_config.impl.go @@ -0,0 +1,339 @@ +package repository + +import ( + "fmt" + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysConfigImpl 结构体 +var NewSysConfigImpl = &SysConfigImpl{ + selectSql: `select + config_id, config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark + from sys_config`, + + resultMap: map[string]string{ + "config_id": "ConfigID", + "config_name": "ConfigName", + "config_key": "ConfigKey", + "config_value": "ConfigValue", + "config_type": "ConfigType", + "remark": "Remark", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + }, +} + +// SysConfigImpl 参数配置表 数据层处理 +type SysConfigImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysConfigImpl) convertResultRows(rows []map[string]any) []model.SysConfig { + arr := make([]model.SysConfig, 0) + for _, row := range rows { + sysConfig := model.SysConfig{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysConfig, keyMapper, value) + } + } + arr = append(arr, sysConfig) + } + return arr +} + +// SelectDictDataPage 分页查询参数配置列表数据 +func (r *SysConfigImpl) SelectConfigPage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["configName"]; ok && v != "" { + conditions = append(conditions, "config_name like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["configType"]; ok && v != "" { + conditions = append(conditions, "config_type = ?") + params = append(params, v) + } + if v, ok := query["configKey"]; ok && v != "" { + conditions = append(conditions, "config_key like concat(?, '%')") + params = append(params, v) + } + beginTime, ok := query["beginTime"] + if !ok { + beginTime, ok = query["params[beginTime]"] + } + if ok && beginTime != "" { + conditions = append(conditions, "create_time >= ?") + beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD) + params = append(params, beginDate.UnixMilli()) + } + endTime, ok := query["endTime"] + if !ok { + endTime, ok = query["params[endTime]"] + } + if ok && endTime != "" { + conditions = append(conditions, "create_time <= ?") + endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD) + params = append(params, endDate.UnixMilli()) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysConfig{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_config" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := r.selectSql + whereSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectConfigList 查询参数配置列表 +func (r *SysConfigImpl) SelectConfigList(sysConfig model.SysConfig) []model.SysConfig { + // 查询条件拼接 + var conditions []string + var params []any + if sysConfig.ConfigName != "" { + conditions = append(conditions, "config_name like concat(?, '%')") + params = append(params, sysConfig.ConfigName) + } + if sysConfig.ConfigType != "" { + conditions = append(conditions, "config_type = ?") + params = append(params, sysConfig.ConfigType) + } + if sysConfig.ConfigKey != "" { + conditions = append(conditions, "config_key like concat(?, '%')") + params = append(params, sysConfig.ConfigKey) + } + if sysConfig.CreateTime > 0 { + conditions = append(conditions, "create_time >= ?") + params = append(params, sysConfig.CreateTime) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + querySql := r.selectSql + whereSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysConfig{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectConfigValueByKey 通过参数键名查询参数键值 +func (r *SysConfigImpl) SelectConfigValueByKey(configKey string) string { + querySql := "select config_value as 'str' from sys_config where config_key = ?" + results, err := datasource.RawDB("", querySql, []any{configKey}) + if err != nil { + logger.Errorf("query err => %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} + +// SelectConfigByIds 通过配置ID查询参数配置信息 +func (r *SysConfigImpl) SelectConfigByIds(configIds []string) []model.SysConfig { + placeholder := repo.KeyPlaceholderByQuery(len(configIds)) + querySql := r.selectSql + " where config_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(configIds) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysConfig{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// CheckUniqueConfig 校验配置参数是否唯一 +func (r *SysConfigImpl) CheckUniqueConfig(sysConfig model.SysConfig) string { + // 查询条件拼接 + var conditions []string + var params []any + if sysConfig.ConfigKey != "" { + conditions = append(conditions, "config_key = ?") + params = append(params, sysConfig.ConfigKey) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } else { + return "" + } + + // 查询数据 + querySql := "select config_id as 'str' from sys_config " + whereSql + " limit 1" + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} + +// InsertConfig 新增参数配置 +func (r *SysConfigImpl) InsertConfig(sysConfig model.SysConfig) string { + // 参数拼接 + params := make(map[string]any) + if sysConfig.ConfigName != "" { + params["config_name"] = sysConfig.ConfigName + } + if sysConfig.ConfigKey != "" { + params["config_key"] = sysConfig.ConfigKey + } + if sysConfig.ConfigValue != "" { + params["config_value"] = sysConfig.ConfigValue + } + if sysConfig.ConfigType != "" { + params["config_type"] = sysConfig.ConfigType + } + if sysConfig.Remark != "" { + params["remark"] = sysConfig.Remark + } + if sysConfig.CreateBy != "" { + params["create_by"] = sysConfig.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_config (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// UpdateConfig 修改参数配置 +func (r *SysConfigImpl) UpdateConfig(sysConfig model.SysConfig) int64 { + // 参数拼接 + params := make(map[string]any) + if sysConfig.ConfigName != "" { + params["config_name"] = sysConfig.ConfigName + } + if sysConfig.ConfigKey != "" { + params["config_key"] = sysConfig.ConfigKey + } + if sysConfig.ConfigValue != "" { + params["config_value"] = sysConfig.ConfigValue + } + if sysConfig.ConfigType != "" { + params["config_type"] = sysConfig.ConfigType + } + if sysConfig.Remark != "" { + params["remark"] = sysConfig.Remark + } + if sysConfig.UpdateBy != "" { + params["update_by"] = sysConfig.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_config set " + strings.Join(keys, ",") + " where config_id = ?" + + // 执行更新 + values = append(values, sysConfig.ConfigID) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// DeleteConfigByIds 批量删除参数配置信息 +func (r *SysConfigImpl) DeleteConfigByIds(configIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(configIds)) + sql := "delete from sys_config where config_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(configIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/system/repository/sys_dept.go b/src/modules/system/repository/sys_dept.go new file mode 100644 index 00000000..bc64d8fb --- /dev/null +++ b/src/modules/system/repository/sys_dept.go @@ -0,0 +1,42 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysDept 部门表 数据层接口 +type ISysDept interface { + // SelectDeptList 查询部门管理数据 + SelectDeptList(sysDept model.SysDept, dataScopeSQL string) []model.SysDept + + // SelectDeptListByRoleId 根据角色ID查询部门树信息 + SelectDeptListByRoleId(roleId string, deptCheckStrictly bool) []string + + // SelectDeptById 根据部门ID查询信息 + SelectDeptById(deptId string) model.SysDept + + // SelectChildrenDeptById 根据ID查询所有子部门 + SelectChildrenDeptById(deptId string) []model.SysDept + + // HasChildByDeptId 是否存在子节点 + HasChildByDeptId(deptId string) int64 + + // CheckDeptExistUser 查询部门是否存在用户 + CheckDeptExistUser(deptId string) int64 + + // CheckUniqueDept 校验部门是否唯一 + CheckUniqueDept(sysDept model.SysDept) string + + // InsertDept 新增部门信息 + InsertDept(sysDept model.SysDept) string + + // UpdateDept 修改部门信息 + UpdateDept(sysDept model.SysDept) int64 + + // UpdateDeptStatusNormal 修改所在部门正常状态 + UpdateDeptStatusNormal(deptIds []string) int64 + + // UpdateDeptChildren 修改子元素关系 + UpdateDeptChildren(sysDepts []model.SysDept) int64 + + // DeleteDeptById 删除部门管理信息 + DeleteDeptById(deptId string) int64 +} diff --git a/src/modules/system/repository/sys_dept.impl.go b/src/modules/system/repository/sys_dept.impl.go new file mode 100644 index 00000000..e5698bcd --- /dev/null +++ b/src/modules/system/repository/sys_dept.impl.go @@ -0,0 +1,393 @@ +package repository + +import ( + "fmt" + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysDeptImpl 结构体 +var NewSysDeptImpl = &SysDeptImpl{ + selectSql: `select + d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time + from sys_dept d`, + + resultMap: map[string]string{ + "dept_id": "DeptID", + "parent_id": "ParentID", + "ancestors": "Ancestors", + "dept_name": "DeptName", + "order_num": "OrderNum", + "leader": "Leader", + "phone": "Phone", + "email": "Email", + "status": "Status", + "del_flag": "DelFlag", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + "parent_name": "ParentName", + }, +} + +// SysDeptImpl 部门表 数据层处理 +type SysDeptImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysDeptImpl) convertResultRows(rows []map[string]any) []model.SysDept { + arr := make([]model.SysDept, 0) + for _, row := range rows { + sysDept := model.SysDept{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysDept, keyMapper, value) + } + } + arr = append(arr, sysDept) + } + return arr +} + +// SelectDeptList 查询部门管理数据 +func (r *SysDeptImpl) SelectDeptList(sysDept model.SysDept, dataScopeSQL string) []model.SysDept { + // 查询条件拼接 + var conditions []string + var params []any + if sysDept.DeptID != "" { + conditions = append(conditions, "dept_id = ?") + params = append(params, sysDept.DeptID) + } + if sysDept.ParentID != "" { + conditions = append(conditions, "parent_id = ?") + params = append(params, sysDept.ParentID) + } + if sysDept.DeptName != "" { + conditions = append(conditions, "dept_name like concat(?, '%')") + params = append(params, sysDept.DeptName) + } + if sysDept.Status != "" { + conditions = append(conditions, "status = ?") + params = append(params, sysDept.Status) + } + + // 构建查询条件语句 + whereSql := " where d.del_flag = '0' " + if len(conditions) > 0 { + whereSql += " and " + strings.Join(conditions, " and ") + } + + // 查询数据 + orderSql := " order by d.parent_id, d.order_num asc " + querySql := r.selectSql + whereSql + dataScopeSQL + orderSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysDept{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectDeptListByRoleId 根据角色ID查询部门树信息 +func (r *SysDeptImpl) SelectDeptListByRoleId(roleId string, deptCheckStrictly bool) []string { + querySql := `select d.dept_id as 'str' from sys_dept d + left join sys_role_dept rd on d.dept_id = rd.dept_id + where rd.role_id = ? ` + var params []any + params = append(params, roleId) + // 展开 + if deptCheckStrictly { + querySql += ` and d.dept_id not in + (select d.parent_id from sys_dept d + inner join sys_role_dept rd on d.dept_id = rd.dept_id + and rd.role_id = ?) ` + params = append(params, roleId) + } + querySql += "order by d.parent_id, d.order_num" + + // 查询结果 + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []string{} + } + + if len(results) > 0 { + ids := make([]string, 0) + for _, v := range results { + ids = append(ids, fmt.Sprintf("%v", v["str"])) + } + return ids + } + return []string{} +} + +// SelectDeptById 根据部门ID查询信息 +func (r *SysDeptImpl) SelectDeptById(deptId string) model.SysDept { + querySql := `select d.dept_id, d.parent_id, d.ancestors, + d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, + (select dept_name from sys_dept where dept_id = d.parent_id) parent_name + from sys_dept d where d.dept_id = ?` + results, err := datasource.RawDB("", querySql, []any{deptId}) + if err != nil { + logger.Errorf("query err => %v", err) + return model.SysDept{} + } + // 转换实体 + rows := r.convertResultRows(results) + if len(rows) > 0 { + return rows[0] + } + return model.SysDept{} +} + +// SelectChildrenDeptById 根据ID查询所有子部门 +func (r *SysDeptImpl) SelectChildrenDeptById(deptId string) []model.SysDept { + querySql := r.selectSql + " where find_in_set(?, d.ancestors)" + results, err := datasource.RawDB("", querySql, []any{deptId}) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysDept{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// HasChildByDeptId 是否存在子节点 +func (r *SysDeptImpl) HasChildByDeptId(deptId string) int64 { + querySql := "select count(1) as 'total' from sys_dept where status = '1' and parent_id = ? limit 1" + results, err := datasource.RawDB("", querySql, []any{deptId}) + if err != nil { + logger.Errorf("query err => %v", err) + return 0 + } + if len(results) > 0 { + return parse.Number(results[0]["total"]) + } + return 0 +} + +// CheckDeptExistUser 查询部门是否存在用户 +func (r *SysDeptImpl) CheckDeptExistUser(deptId string) int64 { + querySql := "select count(1) as 'total' from sys_user where dept_id = ? and del_flag = '0'" + results, err := datasource.RawDB("", querySql, []any{deptId}) + if err != nil { + logger.Errorf("query err => %v", err) + return 0 + } + if len(results) > 0 { + return parse.Number(results[0]["total"]) + } + return 0 +} + +// CheckUniqueDept 校验部门是否唯一 +func (r *SysDeptImpl) CheckUniqueDept(sysDept model.SysDept) string { + // 查询条件拼接 + var conditions []string + var params []any + if sysDept.DeptName != "" { + conditions = append(conditions, "dept_name = ?") + params = append(params, sysDept.DeptName) + } + if sysDept.ParentID != "" { + conditions = append(conditions, "parent_id = ?") + params = append(params, sysDept.ParentID) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + if whereSql == "" { + return "" + } + + // 查询数据 + querySql := "select dept_id as 'str' from sys_dept " + whereSql + " and del_flag = '0' limit 1" + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} + +// InsertDept 新增部门信息 +func (r *SysDeptImpl) InsertDept(sysDept model.SysDept) string { + // 参数拼接 + params := make(map[string]any) + if sysDept.DeptID != "" { + params["dept_id"] = sysDept.DeptID + } + if sysDept.ParentID != "" { + params["parent_id"] = sysDept.ParentID + } + if sysDept.DeptName != "" { + params["dept_name"] = sysDept.DeptName + } + if sysDept.Ancestors != "" { + params["ancestors"] = sysDept.Ancestors + } + if sysDept.OrderNum > 0 { + params["order_num"] = sysDept.OrderNum + } + if sysDept.Leader != "" { + params["leader"] = sysDept.Leader + } + if sysDept.Phone != "" { + params["phone"] = sysDept.Phone + } + if sysDept.Email != "" { + params["email"] = sysDept.Email + } + if sysDept.Status != "" { + params["status"] = sysDept.Status + } + if sysDept.CreateBy != "" { + params["create_by"] = sysDept.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_dept (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// UpdateDept 修改部门信息 +func (r *SysDeptImpl) UpdateDept(sysDept model.SysDept) int64 { + // 参数拼接 + params := make(map[string]any) + if sysDept.ParentID != "" { + params["parent_id"] = sysDept.ParentID + } + if sysDept.DeptName != "" { + params["dept_name"] = sysDept.DeptName + } + if sysDept.Ancestors != "" { + params["ancestors"] = sysDept.Ancestors + } + if sysDept.OrderNum > 0 { + params["order_num"] = sysDept.OrderNum + } + if sysDept.Leader != "" { + params["leader"] = sysDept.Leader + } + if sysDept.Phone != "" { + params["phone"] = sysDept.Phone + } + if sysDept.Email != "" { + params["email"] = sysDept.Email + } + if sysDept.Status != "" { + params["status"] = sysDept.Status + } + if sysDept.UpdateBy != "" { + params["update_by"] = sysDept.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_dept set " + strings.Join(keys, ",") + " where dept_id = ?" + + // 执行更新 + values = append(values, sysDept.DeptID) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// UpdateDeptStatusNormal 修改所在部门正常状态 +func (r *SysDeptImpl) UpdateDeptStatusNormal(deptIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(deptIds)) + sql := "update sys_dept set status = '1' where dept_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(deptIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("update err => %v", err) + return 0 + } + return results +} + +// UpdateDeptChildren 修改子元素关系 +func (r *SysDeptImpl) UpdateDeptChildren(sysDepts []model.SysDept) int64 { + // 无参数 + if len(sysDepts) == 0 { + return 0 + } + + // 更新条件拼接 + var conditions []string + var params []any + for _, dept := range sysDepts { + caseSql := fmt.Sprintf("WHEN dept_id = '%s' THEN '%s'", dept.DeptID, dept.Ancestors) + conditions = append(conditions, caseSql) + params = append(params, dept.DeptID) + } + + cases := strings.Join(conditions, " ") + placeholders := repo.KeyPlaceholderByQuery(len(params)) + sql := "update sys_dept set ancestors = CASE " + cases + " END where dept_id in (" + placeholders + ")" + results, err := datasource.ExecDB("", sql, params) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// DeleteDeptById 删除部门管理信息 +func (r *SysDeptImpl) DeleteDeptById(deptId string) int64 { + sql := "update sys_dept set status = '0', del_flag = '1' where dept_id = ?" + results, err := datasource.ExecDB("", sql, []any{deptId}) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/system/repository/sys_dict_data.go b/src/modules/system/repository/sys_dict_data.go new file mode 100644 index 00000000..050d6979 --- /dev/null +++ b/src/modules/system/repository/sys_dict_data.go @@ -0,0 +1,33 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysDictData 字典类型数据表 数据层接口 +type ISysDictData interface { + // SelectDictDataPage 根据条件分页查询字典数据 + SelectDictDataPage(query map[string]any) map[string]any + + // SelectDictDataList 根据条件查询字典数据 + SelectDictDataList(sysDictData model.SysDictData) []model.SysDictData + + // SelectDictDataByCodes 根据字典数据编码查询信息 + SelectDictDataByCodes(dictCodes []string) []model.SysDictData + + // CountDictDataByType 查询字典数据 + CountDictDataByType(dictType string) int64 + + // CheckUniqueDictData 校验字典数据是否唯一 + CheckUniqueDictData(sysDictData model.SysDictData) string + + // DeleteDictDataByCodes 批量删除字典数据信息 + DeleteDictDataByCodes(dictCodes []string) int64 + + // InsertDictData 新增字典数据信息 + InsertDictData(sysDictData model.SysDictData) string + + // UpdateDictData 修改字典数据信息 + UpdateDictData(sysDictData model.SysDictData) int64 + + // UpdateDictDataType 同步修改字典类型 + UpdateDictDataType(oldDictType string, newDictType string) int64 +} diff --git a/src/modules/system/repository/sys_dict_data.impl.go b/src/modules/system/repository/sys_dict_data.impl.go new file mode 100644 index 00000000..0664cb0a --- /dev/null +++ b/src/modules/system/repository/sys_dict_data.impl.go @@ -0,0 +1,368 @@ +package repository + +import ( + "fmt" + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysDictDataImpl 结构体 +var NewSysDictDataImpl = &SysDictDataImpl{ + selectSql: `select + dict_code, dict_sort, dict_label, dict_value, dict_type, tag_class, tag_type, status, create_by, create_time, remark + from sys_dict_data`, + + resultMap: map[string]string{ + "dict_code": "DictCode", + "dict_sort": "DictSort", + "dict_label": "DictLabel", + "dict_value": "DictValue", + "dict_type": "DictType", + "tag_class": "TagClass", + "tag_type": "TagType", + "status": "Status", + "remark": "Remark", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + }, +} + +// SysDictDataImpl 字典类型数据表 数据层处理 +type SysDictDataImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysDictDataImpl) convertResultRows(rows []map[string]any) []model.SysDictData { + arr := make([]model.SysDictData, 0) + for _, row := range rows { + sysDictData := model.SysDictData{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysDictData, keyMapper, value) + } + } + arr = append(arr, sysDictData) + } + return arr +} + +// SelectDictDataPage 根据条件分页查询字典数据 +func (r *SysDictDataImpl) SelectDictDataPage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["dictType"]; ok && v != "" { + conditions = append(conditions, "dict_type = ?") + params = append(params, v) + } + if v, ok := query["dictLabel"]; ok && v != "" { + conditions = append(conditions, "dict_label like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "status = ?") + params = append(params, v) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysDictData{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_dict_data" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " order by dict_sort asc limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := r.selectSql + whereSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectDictDataList 根据条件查询字典数据 +func (r *SysDictDataImpl) SelectDictDataList(sysDictData model.SysDictData) []model.SysDictData { + // 查询条件拼接 + var conditions []string + var params []any + if sysDictData.DictLabel != "" { + conditions = append(conditions, "dict_label like concat(?, '%')") + params = append(params, sysDictData.DictLabel) + } + if sysDictData.DictType != "" { + conditions = append(conditions, "dict_type = ?") + params = append(params, sysDictData.DictType) + } + if sysDictData.Status != "" { + conditions = append(conditions, "status = ?") + params = append(params, sysDictData.Status) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + orderSql := " order by dict_sort asc " + querySql := r.selectSql + whereSql + orderSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysDictData{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectDictDataByCodes 根据字典数据编码查询信息 +func (r *SysDictDataImpl) SelectDictDataByCodes(dictCodes []string) []model.SysDictData { + placeholder := repo.KeyPlaceholderByQuery(len(dictCodes)) + querySql := r.selectSql + " where dict_code in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(dictCodes) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysDictData{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// CountDictDataByType 查询字典数据 +func (r *SysDictDataImpl) CountDictDataByType(dictType string) int64 { + querySql := "select count(1) as 'total' from sys_dict_data where dict_type = ?" + results, err := datasource.RawDB("", querySql, []any{dictType}) + if err != nil { + logger.Errorf("query err => %v", err) + return 0 + } + if len(results) > 0 { + return parse.Number(results[0]["total"]) + } + return 0 +} + +// CheckUniqueDictData 校验字典数据是否唯一 +func (r *SysDictDataImpl) CheckUniqueDictData(sysDictData model.SysDictData) string { + // 查询条件拼接 + var conditions []string + var params []any + if sysDictData.DictType != "" { + conditions = append(conditions, "dict_type = ?") + params = append(params, sysDictData.DictType) + } + if sysDictData.DictLabel != "" { + conditions = append(conditions, "dict_label = ?") + params = append(params, sysDictData.DictLabel) + } + if sysDictData.DictValue != "" { + conditions = append(conditions, "dict_value = ?") + params = append(params, sysDictData.DictValue) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } else { + return "" + } + + // 查询数据 + querySql := "select dict_code as 'str' from sys_dict_data " + whereSql + " limit 1" + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} + +// DeleteDictDataByCodes 批量删除字典数据信息 +func (r *SysDictDataImpl) DeleteDictDataByCodes(dictCodes []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(dictCodes)) + sql := "delete from sys_dict_data where dict_code in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(dictCodes) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// InsertDictData 新增字典数据信息 +func (r *SysDictDataImpl) InsertDictData(sysDictData model.SysDictData) string { + // 参数拼接 + params := make(map[string]any) + if sysDictData.DictSort > 0 { + params["dict_sort"] = sysDictData.DictSort + } + if sysDictData.DictLabel != "" { + params["dict_label"] = sysDictData.DictLabel + } + if sysDictData.DictValue != "" { + params["dict_value"] = sysDictData.DictValue + } + if sysDictData.DictType != "" { + params["dict_type"] = sysDictData.DictType + } + if sysDictData.TagClass != "" { + params["tag_class"] = sysDictData.TagClass + } + if sysDictData.TagType != "" { + params["tag_type"] = sysDictData.TagType + } + if sysDictData.Status != "" { + params["status"] = sysDictData.Status + } + if sysDictData.Remark != "" { + params["remark"] = sysDictData.Remark + } + if sysDictData.CreateBy != "" { + params["create_by"] = sysDictData.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_dict_data (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// UpdateDictData 修改字典数据信息 +func (r *SysDictDataImpl) UpdateDictData(sysDictData model.SysDictData) int64 { + // 参数拼接 + params := make(map[string]any) + if sysDictData.DictSort > 0 { + params["dict_sort"] = sysDictData.DictSort + } + if sysDictData.DictLabel != "" { + params["dict_label"] = sysDictData.DictLabel + } + if sysDictData.DictValue != "" { + params["dict_value"] = sysDictData.DictValue + } + if sysDictData.DictType != "" { + params["dict_type"] = sysDictData.DictType + } + if sysDictData.TagClass != "" { + params["tag_class"] = sysDictData.TagClass + } + if sysDictData.TagType != "" { + params["tag_type"] = sysDictData.TagType + } + if sysDictData.Status != "" { + params["status"] = sysDictData.Status + } + if sysDictData.Remark != "" { + params["remark"] = sysDictData.Remark + } + if sysDictData.UpdateBy != "" { + params["update_by"] = sysDictData.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_dict_data set " + strings.Join(keys, ",") + " where dict_code = ?" + + // 执行更新 + values = append(values, sysDictData.DictCode) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// UpdateDictDataType 同步修改字典类型 +func (r *SysDictDataImpl) UpdateDictDataType(oldDictType string, newDictType string) int64 { + // 参数拼接 + params := make([]any, 0) + if oldDictType == "" || newDictType == "" { + return 0 + } + params = append(params, newDictType) + params = append(params, oldDictType) + + // 构建执行语句 + sql := "update sys_dict_data set dict_type = ? where dict_type = ?" + + // 执行更新 + rows, err := datasource.ExecDB("", sql, params) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} diff --git a/src/modules/system/repository/sys_dict_type.go b/src/modules/system/repository/sys_dict_type.go new file mode 100644 index 00000000..8a4f4ec2 --- /dev/null +++ b/src/modules/system/repository/sys_dict_type.go @@ -0,0 +1,30 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysDictType 字典类型表 数据层接口 +type ISysDictType interface { + // SelectDictTypePage 根据条件分页查询字典类型 + SelectDictTypePage(query map[string]any) map[string]any + + // SelectDictTypeList 根据条件查询字典类型 + SelectDictTypeList(sysDictType model.SysDictType) []model.SysDictType + + // SelectDictTypeByIDs 根据字典类型ID查询信息 + SelectDictTypeByIDs(dictIDs []string) []model.SysDictType + + // SelectDictTypeByType 根据字典类型查询信息 + SelectDictTypeByType(dictType string) model.SysDictType + + // CheckUniqueDictType 校验字典类型是否唯一 + CheckUniqueDictType(sysDictType model.SysDictType) string + + // InsertDictType 新增字典类型信息 + InsertDictType(sysDictType model.SysDictType) string + + // UpdateDictType 修改字典类型信息 + UpdateDictType(sysDictType model.SysDictType) int64 + + // DeleteDictTypeByIDs 批量删除字典类型信息 + DeleteDictTypeByIDs(dictIDs []string) int64 +} diff --git a/src/modules/system/repository/sys_dict_type.impl.go b/src/modules/system/repository/sys_dict_type.impl.go new file mode 100644 index 00000000..e34ab7ba --- /dev/null +++ b/src/modules/system/repository/sys_dict_type.impl.go @@ -0,0 +1,334 @@ +package repository + +import ( + "fmt" + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysDictTypeImpl 结构体 +var NewSysDictTypeImpl = &SysDictTypeImpl{ + selectSql: `select + dict_id, dict_name, dict_type, status, create_by, create_time, remark + from sys_dict_type`, + + resultMap: map[string]string{ + "dict_id": "DictID", + "dict_name": "DictName", + "dict_type": "DictType", + "remark": "Remark", + "status": "Status", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + }, +} + +// SysDictTypeImpl 字典类型表 数据层处理 +type SysDictTypeImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysDictTypeImpl) convertResultRows(rows []map[string]any) []model.SysDictType { + arr := make([]model.SysDictType, 0) + for _, row := range rows { + sysDictType := model.SysDictType{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysDictType, keyMapper, value) + } + } + arr = append(arr, sysDictType) + } + return arr +} + +// SelectDictTypePage 根据条件分页查询字典类型 +func (r *SysDictTypeImpl) SelectDictTypePage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["dictName"]; ok && v != "" { + conditions = append(conditions, "dict_name like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["dictType"]; ok && v != "" { + conditions = append(conditions, "dict_type like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "status = ?") + params = append(params, v) + } + beginTime, ok := query["beginTime"] + if !ok { + beginTime, ok = query["params[beginTime]"] + } + if ok && beginTime != "" { + conditions = append(conditions, "create_time >= ?") + beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD) + params = append(params, beginDate.UnixMilli()) + } + endTime, ok := query["endTime"] + if !ok { + endTime, ok = query["params[endTime]"] + } + if ok && endTime != "" { + conditions = append(conditions, "create_time <= ?") + endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD) + params = append(params, endDate.UnixMilli()) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysDictType{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_dict_type" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := r.selectSql + whereSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectDictTypeList 根据条件查询字典类型 +func (r *SysDictTypeImpl) SelectDictTypeList(sysDictType model.SysDictType) []model.SysDictType { + // 查询条件拼接 + var conditions []string + var params []any + if sysDictType.DictName != "" { + conditions = append(conditions, "dict_name like concat(?, '%')") + params = append(params, sysDictType.DictName) + } + if sysDictType.DictType != "" { + conditions = append(conditions, "dict_type like concat(?, '%')") + params = append(params, sysDictType.DictType) + } + if sysDictType.Status != "" { + conditions = append(conditions, "status = ?") + params = append(params, sysDictType.Status) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + querySql := r.selectSql + whereSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysDictType{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectDictTypeByIDs 根据字典类型ID查询信息 +func (r *SysDictTypeImpl) SelectDictTypeByIDs(dictIDs []string) []model.SysDictType { + placeholder := repo.KeyPlaceholderByQuery(len(dictIDs)) + querySql := r.selectSql + " where dict_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(dictIDs) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysDictType{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// SelectDictTypeByType 根据字典类型查询信息 +func (r *SysDictTypeImpl) SelectDictTypeByType(dictType string) model.SysDictType { + querySql := r.selectSql + " where dict_type = ?" + results, err := datasource.RawDB("", querySql, []any{dictType}) + if err != nil { + logger.Errorf("query err => %v", err) + return model.SysDictType{} + } + // 转换实体 + rows := r.convertResultRows(results) + if len(rows) > 0 { + return rows[0] + } + return model.SysDictType{} +} + +// CheckUniqueDictType 校验字典是否唯一 +func (r *SysDictTypeImpl) CheckUniqueDictType(sysDictType model.SysDictType) string { + // 查询条件拼接 + var conditions []string + var params []any + if sysDictType.DictName != "" { + conditions = append(conditions, "dict_name = ?") + params = append(params, sysDictType.DictName) + } + if sysDictType.DictType != "" { + conditions = append(conditions, "dict_type = ?") + params = append(params, sysDictType.DictType) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } else { + return "" + } + + // 查询数据 + querySql := "select dict_id as 'str' from sys_dict_type " + whereSql + " limit 1" + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} + +// InsertDictType 新增字典类型信息 +func (r *SysDictTypeImpl) InsertDictType(sysDictType model.SysDictType) string { + // 参数拼接 + params := make(map[string]any) + if sysDictType.DictName != "" { + params["dict_name"] = sysDictType.DictName + } + if sysDictType.DictType != "" { + params["dict_type"] = sysDictType.DictType + } + if sysDictType.Status != "" { + params["status"] = sysDictType.Status + } + if sysDictType.Remark != "" { + params["remark"] = sysDictType.Remark + } + if sysDictType.CreateBy != "" { + params["create_by"] = sysDictType.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_dict_type (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// UpdateDictType 修改字典类型信息 +func (r *SysDictTypeImpl) UpdateDictType(sysDictType model.SysDictType) int64 { + // 参数拼接 + params := make(map[string]any) + if sysDictType.DictName != "" { + params["dict_name"] = sysDictType.DictName + } + if sysDictType.DictType != "" { + params["dict_type"] = sysDictType.DictType + } + if sysDictType.Status != "" { + params["status"] = sysDictType.Status + } + if sysDictType.Remark != "" { + params["remark"] = sysDictType.Remark + } + if sysDictType.UpdateBy != "" { + params["update_by"] = sysDictType.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_dict_type set " + strings.Join(keys, ",") + " where dict_id = ?" + + // 执行更新 + values = append(values, sysDictType.DictID) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// DeleteDictTypeByIDs 批量删除字典类型信息 +func (r *SysDictTypeImpl) DeleteDictTypeByIDs(dictIDs []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(dictIDs)) + sql := "delete from sys_dict_type where dict_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(dictIDs) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/system/repository/sys_log_login.go b/src/modules/system/repository/sys_log_login.go new file mode 100644 index 00000000..662e9fdb --- /dev/null +++ b/src/modules/system/repository/sys_log_login.go @@ -0,0 +1,21 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysLogLogin 系统登录日志表 数据层接口 +type ISysLogLogin interface { + // SelectSysLogLoginPage 分页查询系统登录日志集合 + SelectSysLogLoginPage(query map[string]any) map[string]any + + // SelectSysLogLoginList 查询系统登录日志集合 + SelectSysLogLoginList(sysLogLogin model.SysLogLogin) []model.SysLogLogin + + // InsertSysLogLogin 新增系统登录日志 + InsertSysLogLogin(sysLogLogin model.SysLogLogin) string + + // DeleteSysLogLoginByIds 批量删除系统登录日志 + DeleteSysLogLoginByIds(loginIds []string) int64 + + // CleanSysLogLogin 清空系统登录日志 + CleanSysLogLogin() error +} diff --git a/src/modules/system/repository/sys_log_login.impl.go b/src/modules/system/repository/sys_log_login.impl.go new file mode 100644 index 00000000..601bc5b2 --- /dev/null +++ b/src/modules/system/repository/sys_log_login.impl.go @@ -0,0 +1,245 @@ +package repository + +import ( + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysLogLoginImpl 结构体 +var NewSysLogLoginImpl = &SysLogLoginImpl{ + selectSql: `select login_id, user_name, ipaddr, login_location, + browser, os, status, msg, login_time from sys_log_login`, + + resultMap: map[string]string{ + "login_id": "LoginID", + "user_name": "UserName", + "status": "Status", + "ipaddr": "IPAddr", + "login_location": "LoginLocation", + "browser": "Browser", + "os": "OS", + "msg": "Msg", + "login_time": "LoginTime", + }, +} + +// SysLogLoginImpl 系统登录访问表 数据层处理 +type SysLogLoginImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysLogLoginImpl) convertResultRows(rows []map[string]any) []model.SysLogLogin { + arr := make([]model.SysLogLogin, 0) + for _, row := range rows { + SysLogLogin := model.SysLogLogin{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&SysLogLogin, keyMapper, value) + } + } + arr = append(arr, SysLogLogin) + } + return arr +} + +// SelectSysLogLoginPage 分页查询系统登录日志集合 +func (r *SysLogLoginImpl) SelectSysLogLoginPage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["ipaddr"]; ok && v != "" { + conditions = append(conditions, "ipaddr like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["userName"]; ok && v != "" { + conditions = append(conditions, "user_name like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "status = ?") + params = append(params, v) + } + beginTime, ok := query["beginTime"] + if !ok { + beginTime, ok = query["params[beginTime]"] + } + if ok && beginTime != "" { + conditions = append(conditions, "login_time >= ?") + beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD) + params = append(params, beginDate.UnixMilli()) + } + endTime, ok := query["endTime"] + if !ok { + endTime, ok = query["params[endTime]"] + } + if ok && endTime != "" { + conditions = append(conditions, "login_time <= ?") + endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD) + params = append(params, endDate.UnixMilli()) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysLogLogin{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_log_login" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " order by login_id desc limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := r.selectSql + whereSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectSysLogLoginList 查询系统登录日志集合 +func (r *SysLogLoginImpl) SelectSysLogLoginList(SysLogLogin model.SysLogLogin) []model.SysLogLogin { + // 查询条件拼接 + var conditions []string + var params []any + if SysLogLogin.IPAddr != "" { + conditions = append(conditions, "title like concat(?, '%')") + params = append(params, SysLogLogin.IPAddr) + } + if SysLogLogin.UserName != "" { + conditions = append(conditions, "user_name like concat(?, '%')") + params = append(params, SysLogLogin.UserName) + } + if SysLogLogin.Status != "" { + conditions = append(conditions, "status = ?") + params = append(params, SysLogLogin.Status) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + querySql := r.selectSql + whereSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysLogLogin{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// InsertSysLogLogin 新增系统登录日志 +func (r *SysLogLoginImpl) InsertSysLogLogin(SysLogLogin model.SysLogLogin) string { + // 参数拼接 + params := make(map[string]any) + params["login_time"] = time.Now().UnixMilli() + if SysLogLogin.UserName != "" { + params["user_name"] = SysLogLogin.UserName + } + if SysLogLogin.Status != "" { + params["status"] = SysLogLogin.Status + } + if SysLogLogin.IPAddr != "" { + params["ipaddr"] = SysLogLogin.IPAddr + } + if SysLogLogin.LoginLocation != "" { + params["login_location"] = SysLogLogin.LoginLocation + } + if SysLogLogin.Browser != "" { + params["browser"] = SysLogLogin.Browser + } + if SysLogLogin.OS != "" { + params["os"] = SysLogLogin.OS + } + if SysLogLogin.Msg != "" { + params["msg"] = SysLogLogin.Msg + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_log_login (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// DeleteSysLogLoginByIds 批量删除系统登录日志 +func (r *SysLogLoginImpl) DeleteSysLogLoginByIds(loginIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(loginIds)) + sql := "delete from sys_log_login where login_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(loginIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// CleanSysLogLogin 清空系统登录日志 +func (r *SysLogLoginImpl) CleanSysLogLogin() error { + sql := "truncate table sys_log_login" + _, err := datasource.ExecDB("", sql, []any{}) + return err +} diff --git a/src/modules/system/repository/sys_log_operate.go b/src/modules/system/repository/sys_log_operate.go new file mode 100644 index 00000000..e3f7be20 --- /dev/null +++ b/src/modules/system/repository/sys_log_operate.go @@ -0,0 +1,24 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysLogOperate 操作日志表 数据层接口 +type ISysLogOperate interface { + // SelectSysLogOperatePage 分页查询系统操作日志集合 + SelectSysLogOperatePage(query map[string]any) map[string]any + + // SelectSysLogOperateList 查询系统操作日志集合 + SelectSysLogOperateList(sysLogOperate model.SysLogOperate) []model.SysLogOperate + + // SelectSysLogOperateById 查询操作日志详细 + SelectSysLogOperateById(operId string) model.SysLogOperate + + // InsertSysLogOperate 新增操作日志 + InsertSysLogOperate(sysLogOperate model.SysLogOperate) string + + // DeleteSysLogOperateByIds 批量删除系统操作日志 + DeleteSysLogOperateByIds(operIds []string) int64 + + // CleanSysLogOperate 清空操作日志 + CleanSysLogOperate() error +} diff --git a/src/modules/system/repository/sys_log_operate.impl.go b/src/modules/system/repository/sys_log_operate.impl.go new file mode 100644 index 00000000..10d1f3bd --- /dev/null +++ b/src/modules/system/repository/sys_log_operate.impl.go @@ -0,0 +1,299 @@ +package repository + +import ( + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysLogOperateImpl 结构体 +var NewSysLogOperateImpl = &SysLogOperateImpl{ + selectSql: `select + oper_id, title, business_type, method, request_method, operator_type, oper_name, dept_name, + oper_url, oper_ip, oper_location, oper_param, oper_msg, status, oper_time, cost_time + from sys_log_operate`, + + resultMap: map[string]string{ + "oper_id": "OperID", + "title": "Title", + "business_type": "BusinessType", + "method": "Method", + "request_method": "RequestMethod", + "operator_type": "OperatorType", + "oper_name": "OperName", + "dept_name": "DeptName", + "oper_url": "OperURL", + "oper_ip": "OperIP", + "oper_location": "OperLocation", + "oper_param": "OperParam", + "oper_msg": "OperMsg", + "status": "Status", + "oper_time": "OperTime", + "cost_time": "CostTime", + }, +} + +// SysLogOperateImpl 操作日志表 数据层处理 +type SysLogOperateImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysLogOperateImpl) convertResultRows(rows []map[string]any) []model.SysLogOperate { + arr := make([]model.SysLogOperate, 0) + for _, row := range rows { + SysLogOperate := model.SysLogOperate{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&SysLogOperate, keyMapper, value) + } + } + arr = append(arr, SysLogOperate) + } + return arr +} + +// SelectSysLogOperatePage 分页查询系统操作日志集合 +func (r *SysLogOperateImpl) SelectSysLogOperatePage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["title"]; ok && v != "" { + conditions = append(conditions, "title like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["businessType"]; ok && v != "" { + conditions = append(conditions, "business_type = ?") + params = append(params, v) + } + if v, ok := query["operName"]; ok && v != "" { + conditions = append(conditions, "oper_name like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "status = ?") + params = append(params, v) + } + beginTime, ok := query["beginTime"] + if !ok { + beginTime, ok = query["params[beginTime]"] + } + if ok && beginTime != "" { + conditions = append(conditions, "oper_time >= ?") + beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD) + params = append(params, beginDate.UnixMilli()) + } + endTime, ok := query["endTime"] + if !ok { + endTime, ok = query["params[endTime]"] + } + if ok && endTime != "" { + conditions = append(conditions, "oper_time <= ?") + endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD) + params = append(params, endDate.UnixMilli()) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysLogOperate{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_log_operate" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " order by oper_id desc limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := r.selectSql + whereSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectSysLogOperateList 查询系统操作日志集合 +func (r *SysLogOperateImpl) SelectSysLogOperateList(SysLogOperate model.SysLogOperate) []model.SysLogOperate { + // 查询条件拼接 + var conditions []string + var params []any + if SysLogOperate.Title != "" { + conditions = append(conditions, "title like concat(?, '%')") + params = append(params, SysLogOperate.Title) + } + if SysLogOperate.BusinessType != "" { + conditions = append(conditions, "business_type = ?") + params = append(params, SysLogOperate.BusinessType) + } + if SysLogOperate.OperName != "" { + conditions = append(conditions, "oper_name like concat(?, '%')") + params = append(params, SysLogOperate.OperName) + } + if SysLogOperate.Status != "" { + conditions = append(conditions, "status = ?") + params = append(params, SysLogOperate.Status) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + querySql := r.selectSql + whereSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysLogOperate{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectSysLogOperateById 查询操作日志详细 +func (r *SysLogOperateImpl) SelectSysLogOperateById(operId string) model.SysLogOperate { + querySql := r.selectSql + " where oper_id = ?" + results, err := datasource.RawDB("", querySql, []any{operId}) + if err != nil { + logger.Errorf("query err => %v", err) + return model.SysLogOperate{} + } + // 转换实体 + rows := r.convertResultRows(results) + if len(rows) > 0 { + return rows[0] + } + return model.SysLogOperate{} +} + +// InsertSysLogOperate 新增操作日志 +func (r *SysLogOperateImpl) InsertSysLogOperate(SysLogOperate model.SysLogOperate) string { + // 参数拼接 + params := make(map[string]any) + params["oper_time"] = time.Now().UnixMilli() + if SysLogOperate.Title != "" { + params["title"] = SysLogOperate.Title + } + if SysLogOperate.BusinessType != "" { + params["business_type"] = SysLogOperate.BusinessType + } + if SysLogOperate.Method != "" { + params["method"] = SysLogOperate.Method + } + if SysLogOperate.RequestMethod != "" { + params["request_method"] = SysLogOperate.RequestMethod + } + if SysLogOperate.OperatorType != "" { + params["operator_type"] = SysLogOperate.OperatorType + } + if SysLogOperate.DeptName != "" { + params["dept_name"] = SysLogOperate.DeptName + } + if SysLogOperate.OperName != "" { + params["oper_name"] = SysLogOperate.OperName + } + if SysLogOperate.OperURL != "" { + params["oper_url"] = SysLogOperate.OperURL + } + if SysLogOperate.OperIP != "" { + params["oper_ip"] = SysLogOperate.OperIP + } + if SysLogOperate.OperLocation != "" { + params["oper_location"] = SysLogOperate.OperLocation + } + if SysLogOperate.OperParam != "" { + params["oper_param"] = SysLogOperate.OperParam + } + if SysLogOperate.OperMsg != "" { + params["oper_msg"] = SysLogOperate.OperMsg + } + if SysLogOperate.Status != "" { + params["status"] = SysLogOperate.Status + } + if SysLogOperate.CostTime > 0 { + params["cost_time"] = SysLogOperate.CostTime + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_log_operate (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// DeleteSysLogOperateByIds 批量删除系统操作日志 +func (r *SysLogOperateImpl) DeleteSysLogOperateByIds(operIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(operIds)) + sql := "delete from sys_log_operate where oper_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(operIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// CleanSysLogOperate 清空操作日志 +func (r *SysLogOperateImpl) CleanSysLogOperate() error { + sql := "truncate table sys_log_operate" + _, err := datasource.ExecDB("", sql, []any{}) + return err +} diff --git a/src/modules/system/repository/sys_menu.go b/src/modules/system/repository/sys_menu.go new file mode 100644 index 00000000..6b5f4ea3 --- /dev/null +++ b/src/modules/system/repository/sys_menu.go @@ -0,0 +1,36 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysMenu 菜单表 数据层接口 +type ISysMenu interface { + // SelectMenuList 查询系统菜单列表 + SelectMenuList(sysMenu model.SysMenu, userId string) []model.SysMenu + + // SelectMenuPermsByUserId 根据用户ID查询权限 + SelectMenuPermsByUserId(userId string) []string + + // SelectMenuTreeByUserId 根据用户ID查询菜单 + SelectMenuTreeByUserId(userId string) []model.SysMenu + + // SelectMenuListByRoleId 根据角色ID查询菜单树信息 + SelectMenuListByRoleId(roleId string, menuCheckStrictly bool) []string + + // SelectMenuByIds 根据菜单ID查询信息 + SelectMenuByIds(menuIds []string) []model.SysMenu + + // HasChildByMenuIdAndStatus 存在菜单子节点数量与状态 + HasChildByMenuIdAndStatus(menuId, status string) int64 + + // InsertMenu 新增菜单信息 + InsertMenu(sysMenu model.SysMenu) string + + // UpdateMenu 修改菜单信息 + UpdateMenu(sysMenu model.SysMenu) int64 + + // DeleteMenuById 删除菜单管理信息 + DeleteMenuById(menuId string) int64 + + // CheckUniqueMenu 校验菜单是否唯一 + CheckUniqueMenu(sysMenu model.SysMenu) string +} diff --git a/src/modules/system/repository/sys_menu.impl.go b/src/modules/system/repository/sys_menu.impl.go new file mode 100644 index 00000000..4127c90f --- /dev/null +++ b/src/modules/system/repository/sys_menu.impl.go @@ -0,0 +1,476 @@ +package repository + +import ( + "fmt" + "strings" + "time" + + "ems.agt/src/framework/constants/menu" + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysMenuImpl 结构体 +var NewSysMenuImpl = &SysMenuImpl{ + selectSql: `select + m.menu_id, m.menu_name, m.parent_id, m.menu_sort, m.path, m.component, m.is_frame, m.is_cache, m.menu_type, m.visible, m.status, ifnull(m.perms,'') as perms, m.icon, m.create_time, m.remark + from sys_menu m`, + + selectSqlByUser: `select distinct + m.menu_id, m.menu_name, m.parent_id, m.menu_sort, m.path, m.component, m.is_frame, m.is_cache, m.menu_type, m.visible, m.status, ifnull(m.perms,'') as perms, m.icon, m.create_time, m.remark + from sys_menu m + left join sys_role_menu rm on m.menu_id = rm.menu_id + left join sys_user_role ur on rm.role_id = ur.role_id + left join sys_role ro on ur.role_id = ro.role_id`, + + resultMap: map[string]string{ + "menu_id": "MenuID", + "menu_name": "MenuName", + "parent_name": "ParentName", + "parent_id": "ParentID", + "path": "Path", + "menu_sort": "MenuSort", + "component": "Component", + "is_frame": "IsFrame", + "is_cache": "IsCache", + "menu_type": "MenuType", + "visible": "Visible", + "status": "Status", + "perms": "Perms", + "icon": "Icon", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + "remark": "Remark", + }, +} + +// SysMenuImpl 菜单表 数据层处理 +type SysMenuImpl struct { + // 查询视图对象SQL + selectSql string + // 查询视图用户对象SQL + selectSqlByUser string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysMenuImpl) convertResultRows(rows []map[string]any) []model.SysMenu { + arr := make([]model.SysMenu, 0) + for _, row := range rows { + sysMenu := model.SysMenu{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysMenu, keyMapper, value) + } + } + arr = append(arr, sysMenu) + } + return arr +} + +// SelectMenuList 查询系统菜单列表 +func (r *SysMenuImpl) SelectMenuList(sysMenu model.SysMenu, userId string) []model.SysMenu { + // 查询条件拼接 + var conditions []string + var params []any + if sysMenu.MenuName != "" { + conditions = append(conditions, "m.menu_name like concat(?, '%')") + params = append(params, sysMenu.MenuName) + } + if sysMenu.Visible != "" { + conditions = append(conditions, "m.visible = ?") + params = append(params, sysMenu.Visible) + } + if sysMenu.Status != "" { + conditions = append(conditions, "m.status = ?") + params = append(params, sysMenu.Status) + } + + fromSql := r.selectSql + + // 个人菜单 + if userId != "*" { + fromSql = r.selectSqlByUser + conditions = append(conditions, "ur.user_id = ?") + params = append(params, userId) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + orderSql := " order by m.parent_id, m.menu_sort" + querySql := fromSql + whereSql + orderSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysMenu{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectMenuPermsByUserId 根据用户ID查询权限 +func (r *SysMenuImpl) SelectMenuPermsByUserId(userId string) []string { + querySql := `select distinct m.perms as 'str' from sys_menu m + left join sys_role_menu rm on m.menu_id = rm.menu_id + left join sys_user_role ur on rm.role_id = ur.role_id + left join sys_role r on r.role_id = ur.role_id + where m.status = '1' and m.perms != '' and r.status = '1' and ur.user_id = ? ` + + // 查询结果 + results, err := datasource.RawDB("", querySql, []any{userId}) + if err != nil { + logger.Errorf("query err => %v", err) + return []string{} + } + + // 读取结果 + rows := make([]string, 0) + for _, m := range results { + rows = append(rows, fmt.Sprintf("%v", m["str"])) + } + return rows +} + +// SelectMenuTreeByUserId 根据用户ID查询菜单 +func (r *SysMenuImpl) SelectMenuTreeByUserId(userId string) []model.SysMenu { + var params []any + var querySql string + + if userId == "*" { + // 管理员全部菜单 + querySql = r.selectSql + ` where + m.menu_type in (?,?) and m.status = '1' + order by m.parent_id, m.menu_sort` + params = append(params, menu.TYPE_DIR) + params = append(params, menu.TYPE_MENU) + } else { + // 用户ID权限 + querySql = r.selectSqlByUser + ` where + m.menu_type in (?, ?) and m.status = '1' + and ur.user_id = ? and ro.status = '1' + order by m.parent_id, m.menu_sort` + params = append(params, menu.TYPE_DIR) + params = append(params, menu.TYPE_MENU) + params = append(params, userId) + } + + // 查询结果 + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysMenu{} + } + + return r.convertResultRows(results) +} + +// SelectMenuListByRoleId 根据角色ID查询菜单树信息 +func (r *SysMenuImpl) SelectMenuListByRoleId(roleId string, menuCheckStrictly bool) []string { + querySql := `select m.menu_id as 'str' from sys_menu m + left join sys_role_menu rm on m.menu_id = rm.menu_id + where rm.role_id = ? ` + var params []any + params = append(params, roleId) + // 展开 + if menuCheckStrictly { + querySql += ` and m.menu_id not in + (select m.parent_id from sys_menu m + inner join sys_role_menu rm on m.menu_id = rm.menu_id + and rm.role_id = ?) ` + params = append(params, roleId) + } + + // 查询结果 + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []string{} + } + + if len(results) > 0 { + ids := make([]string, 0) + for _, v := range results { + ids = append(ids, fmt.Sprintf("%v", v["str"])) + } + return ids + } + return []string{} +} + +// SelectMenuByIds 根据菜单ID查询信息 +func (r *SysMenuImpl) SelectMenuByIds(menuIds []string) []model.SysMenu { + placeholder := repo.KeyPlaceholderByQuery(len(menuIds)) + querySql := r.selectSql + " where m.menu_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(menuIds) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysMenu{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// HasChildByMenuIdAndStatus 存在菜单子节点数量与状态 +func (r *SysMenuImpl) HasChildByMenuIdAndStatus(menuId, status string) int64 { + querySql := "select count(1) as 'total' from sys_menu where parent_id = ?" + params := []any{menuId} + + // 菜单状态 + if status != "" { + querySql += " and status = ? and menu_type in (?, ?) " + params = append(params, status) + params = append(params, menu.TYPE_DIR) + params = append(params, menu.TYPE_MENU) + } + + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return 0 + } + if len(results) > 0 { + return parse.Number(results[0]["total"]) + } + return 0 +} + +// InsertMenu 新增菜单信息 +func (r *SysMenuImpl) InsertMenu(sysMenu model.SysMenu) string { + // 参数拼接 + params := make(map[string]any) + if sysMenu.MenuID != "" { + params["menu_id"] = sysMenu.MenuID + } + if sysMenu.ParentID != "" { + params["parent_id"] = sysMenu.ParentID + } + if sysMenu.MenuName != "" { + params["menu_name"] = sysMenu.MenuName + } + if sysMenu.MenuSort > 0 { + params["menu_sort"] = sysMenu.MenuSort + } + if sysMenu.Path != "" { + params["path"] = sysMenu.Path + } + if sysMenu.Component != "" { + params["component"] = sysMenu.Component + } + if sysMenu.IsFrame != "" { + params["is_frame"] = sysMenu.IsFrame + } + if sysMenu.IsCache != "" { + params["is_cache"] = sysMenu.IsCache + } + if sysMenu.MenuType != "" { + params["menu_type"] = sysMenu.MenuType + } + if sysMenu.Visible != "" { + params["visible"] = sysMenu.Visible + } + if sysMenu.Status != "" { + params["status"] = sysMenu.Status + } + if sysMenu.Perms != "" { + params["perms"] = sysMenu.Perms + } + if sysMenu.Icon != "" { + params["icon"] = sysMenu.Icon + } else { + params["icon"] = "#" + } + if sysMenu.Remark != "" { + params["remark"] = sysMenu.Remark + } + if sysMenu.CreateBy != "" { + params["create_by"] = sysMenu.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 根据菜单类型重置参数 + if sysMenu.MenuType == menu.TYPE_BUTTON { + params["component"] = "" + params["path"] = "" + params["icon"] = "#" + params["is_cache"] = "1" + params["is_frame"] = "1" + params["visible"] = "1" + params["status"] = "1" + } + if sysMenu.MenuType == menu.TYPE_DIR { + params["component"] = "" + params["perms"] = "" + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_menu (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// UpdateMenu 修改菜单信息 +func (r *SysMenuImpl) UpdateMenu(sysMenu model.SysMenu) int64 { + // 参数拼接 + params := make(map[string]any) + if sysMenu.MenuID != "" { + params["menu_id"] = sysMenu.MenuID + } + if sysMenu.ParentID != "" { + params["parent_id"] = sysMenu.ParentID + } + if sysMenu.MenuName != "" { + params["menu_name"] = sysMenu.MenuName + } + if sysMenu.MenuSort > 0 { + params["menu_sort"] = sysMenu.MenuSort + } + if sysMenu.Path != "" { + params["path"] = sysMenu.Path + } + if sysMenu.Component != "" { + params["component"] = sysMenu.Component + } + if sysMenu.IsFrame != "" { + params["is_frame"] = sysMenu.IsFrame + } + if sysMenu.IsCache != "" { + params["is_cache"] = sysMenu.IsCache + } + if sysMenu.MenuType != "" { + params["menu_type"] = sysMenu.MenuType + } + if sysMenu.Visible != "" { + params["visible"] = sysMenu.Visible + } + if sysMenu.Status != "" { + params["status"] = sysMenu.Status + } + if sysMenu.Perms != "" { + params["perms"] = sysMenu.Perms + } + if sysMenu.Icon != "" { + params["icon"] = sysMenu.Icon + } else { + params["icon"] = "#" + } + if sysMenu.Remark != "" { + params["remark"] = sysMenu.Remark + } + if sysMenu.UpdateBy != "" { + params["update_by"] = sysMenu.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + + // 根据菜单类型重置参数 + if sysMenu.MenuType == menu.TYPE_BUTTON { + params["component"] = "" + params["path"] = "" + params["icon"] = "#" + params["is_cache"] = "1" + params["is_frame"] = "1" + params["visible"] = "1" + params["status"] = "1" + } + if sysMenu.MenuType == menu.TYPE_DIR { + params["component"] = "" + params["perms"] = "" + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_menu set " + strings.Join(keys, ",") + " where menu_id = ?" + + // 执行更新 + values = append(values, sysMenu.MenuID) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// DeleteMenuById 删除菜单管理信息 +func (r *SysMenuImpl) DeleteMenuById(menuId string) int64 { + sql := "delete from sys_menu where menu_id = ?" + results, err := datasource.ExecDB("", sql, []any{menuId}) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// CheckUniqueMenu 校验菜单是否唯一 +func (r *SysMenuImpl) CheckUniqueMenu(sysMenu model.SysMenu) string { + // 查询条件拼接 + var conditions []string + var params []any + if sysMenu.MenuName != "" { + conditions = append(conditions, "menu_name = ?") + params = append(params, sysMenu.MenuName) + } + if sysMenu.ParentID != "" { + conditions = append(conditions, "parent_id = ?") + params = append(params, sysMenu.ParentID) + } + if sysMenu.Path != "" { + conditions = append(conditions, "path = ?") + params = append(params, sysMenu.Path) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + if whereSql == "" { + return "" + } + + // 查询数据 + querySql := "select menu_id as 'str' from sys_menu " + whereSql + " limit 1" + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} diff --git a/src/modules/system/repository/sys_notice.go b/src/modules/system/repository/sys_notice.go new file mode 100644 index 00000000..cacb311c --- /dev/null +++ b/src/modules/system/repository/sys_notice.go @@ -0,0 +1,24 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysNotice 通知公告表 数据层接口 +type ISysNotice interface { + // SelectNoticePage 分页查询公告列表 + SelectNoticePage(query map[string]any) map[string]any + + // SelectNoticeList 查询公告列表 + SelectNoticeList(sysNotice model.SysNotice) []model.SysNotice + + // SelectNoticeById 查询公告信息 + SelectNoticeByIds(noticeIds []string) []model.SysNotice + + // InsertNotice 新增公告 + InsertNotice(sysNotice model.SysNotice) string + + // UpdateNotice 修改公告 + UpdateNotice(sysNotice model.SysNotice) int64 + + // DeleteNoticeByIds 批量删除公告信息 + DeleteNoticeByIds(noticeIds []string) int64 +} diff --git a/src/modules/system/repository/sys_notice.impl.go b/src/modules/system/repository/sys_notice.impl.go new file mode 100644 index 00000000..f565ddb8 --- /dev/null +++ b/src/modules/system/repository/sys_notice.impl.go @@ -0,0 +1,297 @@ +package repository + +import ( + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysNoticeImpl 结构体 +var NewSysNoticeImpl = &SysNoticeImpl{ + selectSql: `select + notice_id, notice_title, notice_type, notice_content, status, del_flag, + create_by, create_time, update_by, update_time, remark from sys_notice`, + + resultMap: map[string]string{ + "notice_id": "NoticeID", + "notice_title": "NoticeTitle", + "notice_type": "NoticeType", + "notice_content": "NoticeContent", + "status": "Status", + "del_flag": "DelFlag", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + "remark": "Remark", + }, +} + +// SysNoticeImpl 通知公告表 数据层处理 +type SysNoticeImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysNoticeImpl) convertResultRows(rows []map[string]any) []model.SysNotice { + arr := make([]model.SysNotice, 0) + for _, row := range rows { + sysNotice := model.SysNotice{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysNotice, keyMapper, value) + } + } + arr = append(arr, sysNotice) + } + return arr +} + +// SelectNoticePage 分页查询公告列表 +func (r *SysNoticeImpl) SelectNoticePage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["noticeTitle"]; ok && v != "" { + conditions = append(conditions, "notice_title like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["noticeType"]; ok && v != "" { + conditions = append(conditions, "notice_type = ?") + params = append(params, v) + } + if v, ok := query["createBy"]; ok && v != "" { + conditions = append(conditions, "create_by like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "status = ?") + params = append(params, v) + } + beginTime, ok := query["beginTime"] + if !ok { + beginTime, ok = query["params[beginTime]"] + } + if ok && beginTime != "" { + conditions = append(conditions, "create_time >= ?") + beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD) + params = append(params, beginDate.UnixMilli()) + } + endTime, ok := query["endTime"] + if !ok { + endTime, ok = query["params[endTime]"] + } + if ok && endTime != "" { + conditions = append(conditions, "create_time <= ?") + endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD) + params = append(params, endDate.UnixMilli()) + } + + // 构建查询条件语句 + whereSql := " where del_flag = '0' " + if len(conditions) > 0 { + whereSql += " and " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysNotice{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_notice" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := r.selectSql + whereSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectNoticeList 查询公告列表 +func (r *SysNoticeImpl) SelectNoticeList(sysNotice model.SysNotice) []model.SysNotice { + // 查询条件拼接 + var conditions []string + var params []any + if sysNotice.NoticeTitle != "" { + conditions = append(conditions, "notice_title like concat(?, '%')") + params = append(params, sysNotice.NoticeTitle) + } + if sysNotice.NoticeType != "" { + conditions = append(conditions, "notice_type = ?") + params = append(params, sysNotice.NoticeType) + } + if sysNotice.CreateBy != "" { + conditions = append(conditions, "create_by like concat(?, '%')") + params = append(params, sysNotice.CreateBy) + } + if sysNotice.Status != "" { + conditions = append(conditions, "status = ?") + params = append(params, sysNotice.Status) + } + + // 构建查询条件语句 + whereSql := " where del_flag = '0' " + if len(conditions) > 0 { + whereSql += " and " + strings.Join(conditions, " and ") + } + + // 查询数据 + querySql := r.selectSql + whereSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysNotice{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectNoticeByIds 查询公告信息 +func (r *SysNoticeImpl) SelectNoticeByIds(noticeIds []string) []model.SysNotice { + placeholder := repo.KeyPlaceholderByQuery(len(noticeIds)) + querySql := r.selectSql + " where notice_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(noticeIds) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysNotice{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// InsertNotice 新增公告 +func (r *SysNoticeImpl) InsertNotice(sysNotice model.SysNotice) string { + // 参数拼接 + params := make(map[string]any) + if sysNotice.NoticeTitle != "" { + params["notice_title"] = sysNotice.NoticeTitle + } + if sysNotice.NoticeType != "" { + params["notice_type"] = sysNotice.NoticeType + } + if sysNotice.NoticeContent != "" { + params["notice_content"] = sysNotice.NoticeContent + } + if sysNotice.Status != "" { + params["status"] = sysNotice.Status + } + if sysNotice.Remark != "" { + params["remark"] = sysNotice.Remark + } + if sysNotice.CreateBy != "" { + params["create_by"] = sysNotice.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_notice (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// UpdateNotice 修改公告 +func (r *SysNoticeImpl) UpdateNotice(sysNotice model.SysNotice) int64 { + // 参数拼接 + params := make(map[string]any) + if sysNotice.NoticeTitle != "" { + params["notice_title"] = sysNotice.NoticeTitle + } + if sysNotice.NoticeType != "" { + params["notice_type"] = sysNotice.NoticeType + } + if sysNotice.NoticeContent != "" { + params["notice_content"] = sysNotice.NoticeContent + } + if sysNotice.Status != "" { + params["status"] = sysNotice.Status + } + if sysNotice.Remark != "" { + params["remark"] = sysNotice.Remark + } + if sysNotice.UpdateBy != "" { + params["update_by"] = sysNotice.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_notice set " + strings.Join(keys, ",") + " where notice_id = ?" + + // 执行更新 + values = append(values, sysNotice.NoticeID) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// DeleteNoticeByIds 批量删除公告信息 +func (r *SysNoticeImpl) DeleteNoticeByIds(noticeIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(noticeIds)) + sql := "update sys_notice set del_flag = '1' where notice_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(noticeIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("update err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/system/repository/sys_post.go b/src/modules/system/repository/sys_post.go new file mode 100644 index 00000000..98bf3e48 --- /dev/null +++ b/src/modules/system/repository/sys_post.go @@ -0,0 +1,30 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysPost 岗位表 数据层接口 +type ISysPost interface { + // SelectPostPage 查询岗位分页数据集合 + SelectPostPage(query map[string]any) map[string]any + + // SelectPostList 查询岗位数据集合 + SelectPostList(sysPost model.SysPost) []model.SysPost + + // SelectPostByIds 通过岗位ID查询岗位信息 + SelectPostByIds(postIds []string) []model.SysPost + + // SelectPostListByUserId 根据用户ID获取岗位选择框列表 + SelectPostListByUserId(userId string) []model.SysPost + + // DeletePostByIds 批量删除岗位信息 + DeletePostByIds(postIds []string) int64 + + // UpdatePost 修改岗位信息 + UpdatePost(sysPost model.SysPost) int64 + + // InsertPost 新增岗位信息 + InsertPost(sysPost model.SysPost) string + + // CheckUniquePost 校验岗位唯一 + CheckUniquePost(sysPost model.SysPost) string +} diff --git a/src/modules/system/repository/sys_post.impl.go b/src/modules/system/repository/sys_post.impl.go new file mode 100644 index 00000000..d1dcda5e --- /dev/null +++ b/src/modules/system/repository/sys_post.impl.go @@ -0,0 +1,323 @@ +package repository + +import ( + "fmt" + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysPostImpl 结构体 +var NewSysPostImpl = &SysPostImpl{ + selectSql: `select + post_id, post_code, post_name, post_sort, status, create_by, create_time, remark + from sys_post`, + + resultMap: map[string]string{ + "post_id": "PostID", + "post_code": "PostCode", + "post_name": "PostName", + "post_sort": "PostSort", + "status": "Status", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + "remark": "Remark", + }, +} + +// SysPostImpl 岗位表 数据层处理 +type SysPostImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysPostImpl) convertResultRows(rows []map[string]any) []model.SysPost { + arr := make([]model.SysPost, 0) + for _, row := range rows { + sysPost := model.SysPost{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysPost, keyMapper, value) + } + } + arr = append(arr, sysPost) + } + return arr +} + +// SelectPostPage 查询岗位分页数据集合 +func (r *SysPostImpl) SelectPostPage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["postCode"]; ok && v != "" { + conditions = append(conditions, "post_code like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["postName"]; ok && v != "" { + conditions = append(conditions, "post_name like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "status = ?") + params = append(params, v) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysPost{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_post" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " order by post_sort limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := r.selectSql + whereSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectPostList 查询岗位数据集合 +func (r *SysPostImpl) SelectPostList(sysPost model.SysPost) []model.SysPost { + // 查询条件拼接 + var conditions []string + var params []any + if sysPost.PostCode != "" { + conditions = append(conditions, "post_code like concat(?, '%')") + params = append(params, sysPost.PostCode) + } + if sysPost.PostName != "" { + conditions = append(conditions, "post_name like concat(?, '%')") + params = append(params, sysPost.PostName) + } + if sysPost.Status != "" { + conditions = append(conditions, "status = ?") + params = append(params, sysPost.Status) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + orderSql := " order by post_sort" + querySql := r.selectSql + whereSql + orderSql + rows, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysPost{} + } + return r.convertResultRows(rows) +} + +// SelectPostByIds 通过岗位ID查询岗位信息 +func (r *SysPostImpl) SelectPostByIds(postIds []string) []model.SysPost { + placeholder := repo.KeyPlaceholderByQuery(len(postIds)) + querySql := r.selectSql + " where post_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(postIds) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysPost{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// SelectPostListByUserId 根据用户ID获取岗位选择框列表 +func (r *SysPostImpl) SelectPostListByUserId(userId string) []model.SysPost { + // 查询数据 + querySql := `select distinct + p.post_id, p.post_name, p.post_code + from sys_post p + left join sys_user_post up on up.post_id = p.post_id + left join sys_user u on u.user_id = up.user_id + where u.user_id = ? order by p.post_id` + rows, err := datasource.RawDB("", querySql, []any{userId}) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysPost{} + } + return r.convertResultRows(rows) +} + +// DeletePostByIds 批量删除岗位信息 +func (r *SysPostImpl) DeletePostByIds(postIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(postIds)) + sql := "delete from sys_post where post_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(postIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// UpdatePost 修改岗位信息 +func (r *SysPostImpl) UpdatePost(sysPost model.SysPost) int64 { + // 参数拼接 + params := make(map[string]any) + if sysPost.PostCode != "" { + params["post_code"] = sysPost.PostCode + } + if sysPost.PostName != "" { + params["post_name"] = sysPost.PostName + } + if sysPost.PostSort > 0 { + params["post_sort"] = sysPost.PostSort + } + if sysPost.Status != "" { + params["status"] = sysPost.Status + } + if sysPost.Remark != "" { + params["remark"] = sysPost.Remark + } + if sysPost.UpdateBy != "" { + params["update_by"] = sysPost.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_post set " + strings.Join(keys, ",") + " where post_id = ?" + + // 执行更新 + values = append(values, sysPost.PostID) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// InsertPost 新增岗位信息 +func (r *SysPostImpl) InsertPost(sysPost model.SysPost) string { + // 参数拼接 + params := make(map[string]any) + if sysPost.PostID != "" { + params["post_id"] = sysPost.PostID + } + if sysPost.PostCode != "" { + params["post_code"] = sysPost.PostCode + } + if sysPost.PostName != "" { + params["post_name"] = sysPost.PostName + } + if sysPost.PostSort > 0 { + params["post_sort"] = sysPost.PostSort + } + if sysPost.Status != "" { + params["status"] = sysPost.Status + } + if sysPost.Remark != "" { + params["remark"] = sysPost.Remark + } + if sysPost.CreateBy != "" { + params["create_by"] = sysPost.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_post (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// CheckUniquePost 校验岗位唯一 +func (r *SysPostImpl) CheckUniquePost(sysPost model.SysPost) string { + // 查询条件拼接 + var conditions []string + var params []any + if sysPost.PostName != "" { + conditions = append(conditions, "post_name= ?") + params = append(params, sysPost.PostName) + } + if sysPost.PostCode != "" { + conditions = append(conditions, "post_code = ?") + params = append(params, sysPost.PostCode) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } else { + return "" + } + + // 查询数据 + querySql := "select post_id as 'str' from sys_post " + whereSql + " limit 1" + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err %v", err) + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} diff --git a/src/modules/system/repository/sys_role.go b/src/modules/system/repository/sys_role.go new file mode 100644 index 00000000..cf259b54 --- /dev/null +++ b/src/modules/system/repository/sys_role.go @@ -0,0 +1,30 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysRole 角色表 数据层接口 +type ISysRole interface { + // SelectRolePage 根据条件分页查询角色数据 + SelectRolePage(query map[string]any, dataScopeSQL string) map[string]any + + // SelectRoleList 根据条件查询角色数据 + SelectRoleList(sysRole model.SysRole, dataScopeSQL string) []model.SysRole + + // SelectRoleListByUserId 根据用户ID获取角色选择框列表 + SelectRoleListByUserId(userId string) []model.SysRole + + // SelectRoleByIds 通过角色ID查询角色 + SelectRoleByIds(roleIds []string) []model.SysRole + + // UpdateRole 修改角色信息 + UpdateRole(sysRole model.SysRole) int64 + + // InsertRole 新增角色信息 + InsertRole(sysRole model.SysRole) string + + // DeleteRoleByIds 批量删除角色信息 + DeleteRoleByIds(roleIds []string) int64 + + // CheckUniqueRole 校验角色是否唯一 + CheckUniqueRole(sysRole model.SysRole) string +} diff --git a/src/modules/system/repository/sys_role.impl.go b/src/modules/system/repository/sys_role.impl.go new file mode 100644 index 00000000..ee4a2ae0 --- /dev/null +++ b/src/modules/system/repository/sys_role.impl.go @@ -0,0 +1,382 @@ +package repository + +import ( + "fmt" + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysRoleImpl 结构体 +var NewSysRoleImpl = &SysRoleImpl{ + selectSql: `select distinct + r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.menu_check_strictly, + r.dept_check_strictly, r.status, r.del_flag, r.create_time, r.remark + from sys_role r + left join sys_user_role ur on ur.role_id = r.role_id + left join sys_user u on u.user_id = ur.user_id + left join sys_dept d on u.dept_id = d.dept_id`, + + resultMap: map[string]string{ + "role_id": "RoleID", + "role_name": "RoleName", + "role_key": "RoleKey", + "role_sort": "RoleSort", + "data_scope": "DataScope", + "menu_check_strictly": "MenuCheckStrictly", + "dept_check_strictly": "DeptCheckStrictly", + "status": "Status", + "del_flag": "DelFlag", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + "remark": "Remark", + }, +} + +// SysRoleImpl 角色表 数据层处理 +type SysRoleImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysRoleImpl) convertResultRows(rows []map[string]any) []model.SysRole { + arr := make([]model.SysRole, 0) + for _, row := range rows { + sysRole := model.SysRole{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysRole, keyMapper, value) + } + } + arr = append(arr, sysRole) + } + return arr +} + +// SelectRolePage 根据条件分页查询角色数据 +func (r *SysRoleImpl) SelectRolePage(query map[string]any, dataScopeSQL string) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["roleId"]; ok && v != "" { + conditions = append(conditions, "r.role_id = ?") + params = append(params, v) + } + if v, ok := query["roleName"]; ok && v != "" { + conditions = append(conditions, "r.role_name like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["roleKey"]; ok && v != "" { + conditions = append(conditions, "r.role_key like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "r.status = ?") + params = append(params, v) + } + beginTime, ok := query["beginTime"] + if !ok { + beginTime, ok = query["params[beginTime]"] + } + if ok && beginTime != "" { + conditions = append(conditions, "r.create_time >= ?") + beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD) + params = append(params, beginDate.UnixMilli()) + } + endTime, ok := query["endTime"] + if !ok { + endTime, ok = query["params[endTime]"] + } + if ok && endTime != "" { + conditions = append(conditions, "r.create_time <= ?") + endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD) + params = append(params, endDate.UnixMilli()) + } + if v, ok := query["deptId"]; ok && v != "" { + conditions = append(conditions, `(u.dept_id = ? or u.dept_id in ( + select t.dept_id from sys_dept t where find_in_set(?, ancestors) + ))`) + params = append(params, v) + params = append(params, v) + } + + // 构建查询条件语句 + whereSql := " where r.del_flag = '0' " + if len(conditions) > 0 { + whereSql += " and " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysRole{}, + } + + // 查询数量 长度为0直接返回 + totalSql := `select count(distinct r.role_id) as 'total' from sys_role r + left join sys_user_role ur on ur.role_id = r.role_id + left join sys_user u on u.user_id = ur.user_id + left join sys_dept d on u.dept_id = d.dept_id` + totalRows, err := datasource.RawDB("", totalSql+whereSql+dataScopeSQL, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " order by r.role_sort asc limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := r.selectSql + whereSql + dataScopeSQL + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectRoleList 根据条件查询角色数据 +func (r *SysRoleImpl) SelectRoleList(sysRole model.SysRole, dataScopeSQL string) []model.SysRole { + // 查询条件拼接 + var conditions []string + var params []any + if sysRole.RoleID != "" { + conditions = append(conditions, "r.role_id = ?") + params = append(params, sysRole.RoleID) + } + if sysRole.RoleKey != "" { + conditions = append(conditions, "r.role_key like concat(?, '%')") + params = append(params, sysRole.RoleKey) + } + if sysRole.RoleName != "" { + conditions = append(conditions, "r.role_name like concat(?, '%')") + params = append(params, sysRole.RoleName) + } + if sysRole.Status != "" { + conditions = append(conditions, "r.status = ?") + params = append(params, sysRole.Status) + } + + // 构建查询条件语句 + whereSql := " where r.del_flag = '0' " + if len(conditions) > 0 { + whereSql += " and " + strings.Join(conditions, " and ") + } + + // 查询数据 + orderSql := " order by r.role_sort" + querySql := r.selectSql + whereSql + dataScopeSQL + orderSql + rows, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysRole{} + } + return r.convertResultRows(rows) +} + +// SelectRoleListByUserId 根据用户ID获取角色选择框列表 +func (r *SysRoleImpl) SelectRoleListByUserId(userId string) []model.SysRole { + querySql := r.selectSql + " where r.del_flag = '0' and ur.user_id = ?" + results, err := datasource.RawDB("", querySql, []any{userId}) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysRole{} + } + return r.convertResultRows(results) +} + +// SelectRoleByIds 通过角色ID查询角色 +func (r *SysRoleImpl) SelectRoleByIds(roleIds []string) []model.SysRole { + placeholder := repo.KeyPlaceholderByQuery(len(roleIds)) + querySql := r.selectSql + " where r.role_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(roleIds) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysRole{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// UpdateRole 修改角色信息 +func (r *SysRoleImpl) UpdateRole(sysRole model.SysRole) int64 { + // 参数拼接 + params := make(map[string]any) + if sysRole.RoleName != "" { + params["role_name"] = sysRole.RoleName + } + if sysRole.RoleKey != "" { + params["role_key"] = sysRole.RoleKey + } + if sysRole.RoleSort > 0 { + params["role_sort"] = sysRole.RoleSort + } + if sysRole.DataScope != "" { + params["data_scope"] = sysRole.DataScope + } + if sysRole.MenuCheckStrictly != "" { + params["menu_check_strictly"] = sysRole.MenuCheckStrictly + } + if sysRole.DeptCheckStrictly != "" { + params["dept_check_strictly"] = sysRole.DeptCheckStrictly + } + if sysRole.Status != "" { + params["status"] = sysRole.Status + } + if sysRole.Remark != "" { + params["remark"] = sysRole.Remark + } + if sysRole.UpdateBy != "" { + params["update_by"] = sysRole.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_role set " + strings.Join(keys, ",") + " where role_id = ?" + + // 执行更新 + values = append(values, sysRole.RoleID) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// InsertRole 新增角色信息 +func (r *SysRoleImpl) InsertRole(sysRole model.SysRole) string { + // 参数拼接 + params := make(map[string]any) + if sysRole.RoleID != "" { + params["role_id"] = sysRole.RoleID + } + if sysRole.RoleName != "" { + params["role_name"] = sysRole.RoleName + } + if sysRole.RoleKey != "" { + params["role_key"] = sysRole.RoleKey + } + if sysRole.RoleSort > 0 { + params["role_sort"] = sysRole.RoleSort + } + if sysRole.DataScope != "" { + params["data_scope"] = sysRole.DataScope + } + if sysRole.MenuCheckStrictly != "" { + params["menu_check_strictly"] = sysRole.MenuCheckStrictly + } + if sysRole.DeptCheckStrictly != "" { + params["dept_check_strictly"] = sysRole.DeptCheckStrictly + } + if sysRole.Status != "" { + params["status"] = sysRole.Status + } + if sysRole.Remark != "" { + params["remark"] = sysRole.Remark + } + if sysRole.CreateBy != "" { + params["create_by"] = sysRole.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_role (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// DeleteRoleByIds 批量删除角色信息 +func (r *SysRoleImpl) DeleteRoleByIds(roleIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(roleIds)) + sql := "update sys_role set del_flag = '1' where role_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(roleIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// CheckUniqueRole 校验角色是否唯一 +func (r *SysRoleImpl) CheckUniqueRole(sysRole model.SysRole) string { + // 查询条件拼接 + var conditions []string + var params []any + if sysRole.RoleName != "" { + conditions = append(conditions, "r.role_name = ?") + params = append(params, sysRole.RoleName) + } + if sysRole.RoleKey != "" { + conditions = append(conditions, "r.role_key = ?") + params = append(params, sysRole.RoleKey) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } else { + return "" + } + + // 查询数据 + querySql := "select role_id as 'str' from sys_role r " + whereSql + " and r.del_flag = '0' limit 1" + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} diff --git a/src/modules/system/repository/sys_role_dept.go b/src/modules/system/repository/sys_role_dept.go new file mode 100644 index 00000000..28c034f7 --- /dev/null +++ b/src/modules/system/repository/sys_role_dept.go @@ -0,0 +1,15 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysRoleDept 角色与部门关联表 数据层接口 +type ISysRoleDept interface { + // DeleteRoleDept 批量删除角色部门关联信息 + DeleteRoleDept(roleIds []string) int64 + + // DeleteDeptRole 批量删除部门角色关联信息 + DeleteDeptRole(deptIds []string) int64 + + // BatchRoleDept 批量新增角色部门信息 + BatchRoleDept(sysRoleDepts []model.SysRoleDept) int64 +} diff --git a/src/modules/system/repository/sys_role_dept.impl.go b/src/modules/system/repository/sys_role_dept.impl.go new file mode 100644 index 00000000..c6e3ce92 --- /dev/null +++ b/src/modules/system/repository/sys_role_dept.impl.go @@ -0,0 +1,58 @@ +package repository + +import ( + "fmt" + "strings" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysRoleDeptImpl 结构体 +var NewSysRoleDeptImpl = &SysRoleDeptImpl{} + +// SysRoleDeptImpl 角色与部门关联表 数据层处理 +type SysRoleDeptImpl struct{} + +// DeleteRoleDept 批量删除角色部门关联信息 +func (r *SysRoleDeptImpl) DeleteRoleDept(roleIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(roleIds)) + sql := "delete from sys_role_dept where role_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(roleIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// DeleteDeptRole 批量删除部门角色关联信息 +func (r *SysRoleDeptImpl) DeleteDeptRole(deptIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(deptIds)) + sql := "delete from sys_role_dept where dept_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(deptIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// BatchRoleDept 批量新增角色部门信息 +func (r *SysRoleDeptImpl) BatchRoleDept(sysRoleDepts []model.SysRoleDept) int64 { + keyValues := make([]string, 0) + for _, item := range sysRoleDepts { + keyValues = append(keyValues, fmt.Sprintf("(%s,%s)", item.RoleID, item.DeptID)) + } + sql := "insert into sys_role_dept(role_id, dept_id) values " + strings.Join(keyValues, ",") + results, err := datasource.ExecDB("", sql, nil) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/system/repository/sys_role_menu.go b/src/modules/system/repository/sys_role_menu.go new file mode 100644 index 00000000..5a2def7d --- /dev/null +++ b/src/modules/system/repository/sys_role_menu.go @@ -0,0 +1,18 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysRoleMenu 角色与菜单关联表 数据层接口 +type ISysRoleMenu interface { + // CheckMenuExistRole 查询菜单分配给角色使用数量 + CheckMenuExistRole(menuId string) int64 + + // DeleteRoleMenu 批量删除角色和菜单关联 + DeleteRoleMenu(roleIds []string) int64 + + // DeleteMenuRole 批量删除菜单和角色关联 + DeleteMenuRole(menuIds []string) int64 + + // BatchRoleMenu 批量新增角色菜单信息 + BatchRoleMenu(sysRoleMenus []model.SysRoleMenu) int64 +} diff --git a/src/modules/system/repository/sys_role_menu.impl.go b/src/modules/system/repository/sys_role_menu.impl.go new file mode 100644 index 00000000..7c7ecf15 --- /dev/null +++ b/src/modules/system/repository/sys_role_menu.impl.go @@ -0,0 +1,73 @@ +package repository + +import ( + "fmt" + "strings" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysRoleMenuImpl 结构体 +var NewSysRoleMenuImpl = &SysRoleMenuImpl{} + +// SysRoleMenuImpl 角色与菜单关联表 数据层处理 +type SysRoleMenuImpl struct{} + +// CheckMenuExistRole 查询菜单分配给角色使用数量 +func (r *SysRoleMenuImpl) CheckMenuExistRole(menuId string) int64 { + querySql := "select count(1) as 'total' from sys_role_menu where menu_id = ?" + results, err := datasource.RawDB("", querySql, []any{menuId}) + if err != nil { + logger.Errorf("query err => %v", err) + return 0 + } + if len(results) > 0 { + return parse.Number(results[0]["total"]) + } + return 0 +} + +// DeleteRoleMenu 批量删除角色和菜单关联 +func (r *SysRoleMenuImpl) DeleteRoleMenu(roleIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(roleIds)) + sql := "delete from sys_role_menu where role_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(roleIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// DeleteMenuRole 批量删除菜单和角色关联 +func (r *SysRoleMenuImpl) DeleteMenuRole(menuIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(menuIds)) + sql := "delete from sys_role_menu where menu_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(menuIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// BatchRoleMenu 批量新增角色菜单信息 +func (r *SysRoleMenuImpl) BatchRoleMenu(sysRoleMenus []model.SysRoleMenu) int64 { + keyValues := make([]string, 0) + for _, item := range sysRoleMenus { + keyValues = append(keyValues, fmt.Sprintf("(%s,%s)", item.RoleID, item.MenuID)) + } + sql := "insert into sys_role_menu(role_id, menu_id) values " + strings.Join(keyValues, ",") + results, err := datasource.ExecDB("", sql, nil) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/system/repository/sys_user.go b/src/modules/system/repository/sys_user.go new file mode 100644 index 00000000..b79ab0e6 --- /dev/null +++ b/src/modules/system/repository/sys_user.go @@ -0,0 +1,33 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysUser 用户表 数据层接口 +type ISysUser interface { + // SelectUserPage 根据条件分页查询用户列表 + SelectUserPage(query map[string]any, dataScopeSQL string) map[string]any + + // SelectAllocatedPage 根据条件分页查询分配用户角色列表 + SelectAllocatedPage(query map[string]any, dataScopeSQL string) map[string]any + + // SelectUserList 根据条件查询用户列表 + SelectUserList(sysUser model.SysUser, dataScopeSQL string) []model.SysUser + + // SelectUserByIds 通过用户ID查询用户 + SelectUserByIds(userIds []string) []model.SysUser + + // SelectUserByUserName 通过用户登录账号查询用户 + SelectUserByUserName(userName string) model.SysUser + + // InsertUser 新增用户信息 + InsertUser(sysUser model.SysUser) string + + // UpdateUser 修改用户信息 + UpdateUser(sysUser model.SysUser) int64 + + // DeleteUserByIds 批量删除用户信息 + DeleteUserByIds(userIds []string) int64 + + // CheckUniqueUser 校验用户信息是否唯一 + CheckUniqueUser(sysUser model.SysUser) string +} diff --git a/src/modules/system/repository/sys_user.impl.go b/src/modules/system/repository/sys_user.impl.go new file mode 100644 index 00000000..7116632c --- /dev/null +++ b/src/modules/system/repository/sys_user.impl.go @@ -0,0 +1,580 @@ +package repository + +import ( + "fmt" + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/crypto" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysUserImpl 结构体 +var NewSysUserImpl = &SysUserImpl{ + selectSql: `select + u.user_id, u.dept_id, u.user_name, u.nick_name, u.user_type, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, + d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status, + r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status + from sys_user u + left join sys_dept d on u.dept_id = d.dept_id + left join sys_user_role ur on u.user_id = ur.user_id + left join sys_role r on r.role_id = ur.role_id`, + + sysUserMap: map[string]string{ + "user_id": "UserID", + "dept_id": "DeptID", + "user_name": "UserName", + "nick_name": "NickName", + "user_type": "UserType", + "email": "Email", + "phonenumber": "PhoneNumber", + "sex": "Sex", + "avatar": "Avatar", + "password": "Password", + "status": "Status", + "del_flag": "DelFlag", + "login_ip": "LoginIP", + "login_date": "LoginDate", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + "remark": "Remark", + }, + + sysDeptMap: map[string]string{ + "dept_id": "DeptID", + "parent_id": "ParentID", + "dept_name": "DeptName", + "ancestors": "Ancestors", + "order_num": "OrderNum", + "leader": "Leader", + "dept_status": "Status", + }, + + sysRoleMap: map[string]string{ + "role_id": "RoleID", + "role_name": "RoleName", + "role_key": "RoleKey", + "role_sort": "RoleSort", + "data_scope": "DataScope", + "role_status": "Status", + }, +} + +// SysUserImpl 用户表 数据层处理 +type SysUserImpl struct { + // 查询视图对象SQL + selectSql string + // 用户信息实体映射 + sysUserMap map[string]string + // 用户部门实体映射 一对一 + sysDeptMap map[string]string + // 用户角色实体映射 一对多 + sysRoleMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysUserImpl) convertResultRows(rows []map[string]any) []model.SysUser { + arr := make([]model.SysUser, 0) + + for _, row := range rows { + sysUser := model.SysUser{} + sysDept := model.SysDept{} + sysRole := model.SysRole{} + sysUser.Roles = []model.SysRole{} + + for key, value := range row { + if keyMapper, ok := r.sysUserMap[key]; ok { + repo.SetFieldValue(&sysUser, keyMapper, value) + } + if keyMapper, ok := r.sysDeptMap[key]; ok { + repo.SetFieldValue(&sysDept, keyMapper, value) + } + if keyMapper, ok := r.sysRoleMap[key]; ok { + repo.SetFieldValue(&sysRole, keyMapper, value) + } + } + + sysUser.Dept = sysDept + if sysRole.RoleKey != "" { + sysUser.Roles = append(sysUser.Roles, sysRole) + } + + one := true + for i, a := range arr { + if a.UserID == sysUser.UserID { + arrUser := &arr[i] + arrUser.Roles = append(arrUser.Roles, sysUser.Roles...) + one = false + break + } + } + if one { + arr = append(arr, sysUser) + } + } + + return arr +} + +// SelectUserPage 根据条件分页查询用户列表 +func (r *SysUserImpl) SelectUserPage(query map[string]any, dataScopeSQL string) map[string]any { + selectUserSql := `select + u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader + from sys_user u + left join sys_dept d on u.dept_id = d.dept_id` + selectUserTotalSql := `select count(distinct u.user_id) as 'total' + from sys_user u left join sys_dept d on u.dept_id = d.dept_id` + + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["userId"]; ok && v != "" { + conditions = append(conditions, "u.user_id = ?") + params = append(params, v) + } + if v, ok := query["userName"]; ok && v != "" { + conditions = append(conditions, "u.user_name like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "u.status = ?") + params = append(params, v) + } + if v, ok := query["phonenumber"]; ok && v != "" { + conditions = append(conditions, "u.phonenumber like concat(?, '%')") + params = append(params, v) + } + beginTime, ok := query["beginTime"] + if !ok { + beginTime, ok = query["params[beginTime]"] + } + if ok && beginTime != "" { + conditions = append(conditions, "u.login_date >= ?") + beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD) + params = append(params, beginDate.UnixMilli()) + } + endTime, ok := query["endTime"] + if !ok { + endTime, ok = query["params[endTime]"] + } + if ok && endTime != "" { + conditions = append(conditions, "u.login_date <= ?") + endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD) + params = append(params, endDate.UnixMilli()) + } + if v, ok := query["deptId"]; ok && v != "" { + conditions = append(conditions, "(u.dept_id = ? or u.dept_id in ( select t.dept_id from sys_dept t where find_in_set(?, ancestors) ))") + params = append(params, v) + params = append(params, v) + } + + // 构建查询条件语句 + whereSql := " where u.del_flag = '0' " + if len(conditions) > 0 { + whereSql += " and " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysUser{}, + } + + // 查询数量 长度为0直接返回 + totalSql := selectUserTotalSql + whereSql + dataScopeSQL + totalRows, err := datasource.RawDB("", totalSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := selectUserSql + whereSql + dataScopeSQL + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectAllocatedPage 根据条件分页查询分配用户角色列表 +func (r *SysUserImpl) SelectAllocatedPage(query map[string]any, dataScopeSQL string) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["userName"]; ok && v != "" { + conditions = append(conditions, "u.user_name like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["phonenumber"]; ok && v != "" { + conditions = append(conditions, "u.phonenumber like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "u.status = ?") + params = append(params, v) + } + // 分配角色用户 + if allocated, ok := query["allocated"]; ok && allocated != "" { + if roleId, ok := query["roleId"]; ok && roleId != "" { + if parse.Boolean(allocated) { + conditions = append(conditions, "r.role_id = ?") + params = append(params, roleId) + } else { + conditions = append(conditions, `(r.role_id != ? or r.role_id IS NULL) + and u.user_id not in ( + select u.user_id from sys_user u + inner join sys_user_role ur on u.user_id = ur.user_id + and ur.role_id = ? + )`) + params = append(params, roleId) + params = append(params, roleId) + } + + } + } + + // 构建查询条件语句 + whereSql := " where u.del_flag = '0' " + if len(conditions) > 0 { + whereSql += " and " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysUser{}, + } + + // 查询数量 长度为0直接返回 + totalSql := `select count(distinct u.user_id) as 'total' from sys_user u + left join sys_dept d on u.dept_id = d.dept_id + left join sys_user_role ur on u.user_id = ur.user_id + left join sys_role r on r.role_id = ur.role_id` + totalRows, err := datasource.RawDB("", totalSql+whereSql+dataScopeSQL, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := `select distinct + u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, + u.phonenumber, u.status, u.create_time, d.dept_name + from sys_user u + left join sys_dept d on u.dept_id = d.dept_id + left join sys_user_role ur on u.user_id = ur.user_id + left join sys_role r on r.role_id = ur.role_id` + querySql = querySql + whereSql + dataScopeSQL + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectUserList 根据条件查询用户列表 +func (r *SysUserImpl) SelectUserList(sysUser model.SysUser, dataScopeSQL string) []model.SysUser { + selectUserSql := `select + u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader + from sys_user u + left join sys_dept d on u.dept_id = d.dept_id` + + // 查询条件拼接 + var conditions []string + var params []any + if sysUser.UserID != "" { + conditions = append(conditions, "u.user_id = ?") + params = append(params, sysUser.UserID) + } + if sysUser.UserName != "" { + conditions = append(conditions, "u.user_name like concat(?, '%')") + params = append(params, sysUser.UserName) + } + if sysUser.Status != "" { + conditions = append(conditions, "u.status = ?") + params = append(params, sysUser.Status) + } + if sysUser.PhoneNumber != "" { + conditions = append(conditions, "u.phonenumber like concat(?, '%')") + params = append(params, sysUser.PhoneNumber) + } + + // 构建查询条件语句 + whereSql := " where u.del_flag = '0' " + if len(conditions) > 0 { + whereSql += " and " + strings.Join(conditions, " and ") + } + + // 查询数据 + querySql := selectUserSql + whereSql + dataScopeSQL + rows, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysUser{} + } + return r.convertResultRows(rows) +} + +// SelectUserByIds 通过用户ID查询用户 +func (r *SysUserImpl) SelectUserByIds(userIds []string) []model.SysUser { + placeholder := repo.KeyPlaceholderByQuery(len(userIds)) + querySql := r.selectSql + " where u.del_flag = '0' and u.user_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(userIds) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysUser{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// SelectUserByUserName 通过用户登录账号查询用户 +func (r *SysUserImpl) SelectUserByUserName(userName string) model.SysUser { + querySql := r.selectSql + " where u.del_flag = '0' and u.user_name = ?" + results, err := datasource.RawDB("", querySql, []any{userName}) + if err != nil { + logger.Errorf("query err => %v", err) + return model.SysUser{} + } + // 转换实体 + rows := r.convertResultRows(results) + if len(rows) > 0 { + return rows[0] + } + return model.SysUser{} +} + +// InsertUser 新增用户信息 +func (r *SysUserImpl) InsertUser(sysUser model.SysUser) string { + // 参数拼接 + params := make(map[string]any) + if sysUser.UserID != "" { + params["user_id"] = sysUser.UserID + } + if sysUser.DeptID != "" { + params["dept_id"] = sysUser.DeptID + } + if sysUser.UserName != "" { + params["user_name"] = sysUser.UserName + } + if sysUser.NickName != "" { + params["nick_name"] = sysUser.NickName + } + if sysUser.UserType != "" { + params["user_type"] = sysUser.UserType + } + if sysUser.Avatar != "" { + params["avatar"] = sysUser.Avatar + } + if sysUser.Email != "" { + params["email"] = sysUser.Email + } + if sysUser.PhoneNumber != "" { + params["phonenumber"] = sysUser.PhoneNumber + } + if sysUser.Sex != "" { + params["sex"] = sysUser.Sex + } + if sysUser.Password != "" { + password := crypto.BcryptHash(sysUser.Password) + params["password"] = password + } + if sysUser.Status != "" { + params["status"] = sysUser.Status + } + if sysUser.Remark != "" { + params["remark"] = sysUser.Remark + } + if sysUser.CreateBy != "" { + params["create_by"] = sysUser.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_user (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// UpdateUser 修改用户信息 +func (r *SysUserImpl) UpdateUser(sysUser model.SysUser) int64 { + // 参数拼接 + params := make(map[string]any) + if sysUser.DeptID != "" { + params["dept_id"] = sysUser.DeptID + } + if sysUser.UserName != "" { + params["user_name"] = sysUser.UserName + } + if sysUser.NickName != "" { + params["nick_name"] = sysUser.NickName + } + if sysUser.UserType != "" { + params["user_type"] = sysUser.UserType + } + if sysUser.Avatar != "" { + params["avatar"] = sysUser.Avatar + } + if sysUser.Email != "" { + if sysUser.Email == "nil" { + params["email"] = "" + } else { + params["email"] = sysUser.Email + } + } + if sysUser.PhoneNumber != "" { + if sysUser.PhoneNumber == "nil" { + params["phonenumber"] = "" + } else { + params["phonenumber"] = sysUser.PhoneNumber + } + } + if sysUser.Sex != "" { + params["sex"] = sysUser.Sex + } + if sysUser.Password != "" { + password := crypto.BcryptHash(sysUser.Password) + params["password"] = password + } + if sysUser.Status != "" { + params["status"] = sysUser.Status + } + if sysUser.Remark != "" { + params["remark"] = sysUser.Remark + } + if sysUser.UpdateBy != "" { + params["update_by"] = sysUser.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + if sysUser.LoginIP != "" { + params["login_ip"] = sysUser.LoginIP + } + if sysUser.LoginDate > 0 { + params["login_date"] = sysUser.LoginDate + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_user set " + strings.Join(keys, ",") + " where user_id = ?" + + // 执行更新 + values = append(values, sysUser.UserID) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// DeleteUserByIds 批量删除用户信息 +func (r *SysUserImpl) DeleteUserByIds(userIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(userIds)) + sql := "update sys_user set del_flag = '1' where user_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(userIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("update err => %v", err) + return 0 + } + return results +} + +// CheckUniqueUser 校验用户信息是否唯一 +func (r *SysUserImpl) CheckUniqueUser(sysUser model.SysUser) string { + // 查询条件拼接 + var conditions []string + var params []any + if sysUser.UserName != "" { + conditions = append(conditions, "user_name = ?") + params = append(params, sysUser.UserName) + } + if sysUser.PhoneNumber != "" { + conditions = append(conditions, "phonenumber = ?") + params = append(params, sysUser.PhoneNumber) + } + if sysUser.Email != "" { + conditions = append(conditions, "email = ?") + params = append(params, sysUser.Email) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } else { + return "" + } + + // 查询数据 + querySql := "select user_id as 'str' from sys_user " + whereSql + " limit 1" + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err %v", err) + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} diff --git a/src/modules/system/repository/sys_user_post.go b/src/modules/system/repository/sys_user_post.go new file mode 100644 index 00000000..2d2060b0 --- /dev/null +++ b/src/modules/system/repository/sys_user_post.go @@ -0,0 +1,15 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysUserPost 用户与岗位关联表 数据层接口 +type ISysUserPost interface { + // CountUserPostByPostId 通过岗位ID查询岗位使用数量 + CountUserPostByPostId(postId string) int64 + + // BatchUserPost 批量新增用户岗位信息 + BatchUserPost(sysUserPosts []model.SysUserPost) int64 + + // DeleteUserPost 批量删除用户和岗位关联 + DeleteUserPost(userIds []string) int64 +} diff --git a/src/modules/system/repository/sys_user_post.impl.go b/src/modules/system/repository/sys_user_post.impl.go new file mode 100644 index 00000000..cd6b9374 --- /dev/null +++ b/src/modules/system/repository/sys_user_post.impl.go @@ -0,0 +1,60 @@ +package repository + +import ( + "fmt" + "strings" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysUserPostImpl 结构体 +var NewSysUserPostImpl = &SysUserPostImpl{} + +// SysUserPostImpl 用户与岗位关联表 数据层处理 +type SysUserPostImpl struct{} + +// CountUserPostByPostId 通过岗位ID查询岗位使用数量 +func (r *SysUserPostImpl) CountUserPostByPostId(postId string) int64 { + querySql := "select count(1) as total from sys_user_role where role_id = ?" + results, err := datasource.RawDB("", querySql, []any{postId}) + if err != nil { + logger.Errorf("query err => %v", err) + return 0 + } + if len(results) > 0 { + return parse.Number(results[0]["total"]) + } + return 0 +} + +// DeleteUserPost 批量删除用户和岗位关联 +func (r *SysUserPostImpl) DeleteUserPost(userIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(userIds)) + sql := "delete from sys_user_post where user_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(userIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// BatchUserPost 批量新增用户岗位信息 +func (r *SysUserPostImpl) BatchUserPost(sysUserPosts []model.SysUserPost) int64 { + keyValues := make([]string, 0) + for _, item := range sysUserPosts { + keyValues = append(keyValues, fmt.Sprintf("(%s,%s)", item.UserID, item.PostID)) + } + sql := "insert into sys_user_post(user_id, post_id) values " + strings.Join(keyValues, ",") + results, err := datasource.ExecDB("", sql, nil) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/system/repository/sys_user_role.go b/src/modules/system/repository/sys_user_role.go new file mode 100644 index 00000000..b6af57c3 --- /dev/null +++ b/src/modules/system/repository/sys_user_role.go @@ -0,0 +1,18 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysUserRole 用户与角色关联表 数据层接口 +type ISysUserRole interface { + // CountUserRoleByRoleId 通过角色ID查询角色使用数量 + CountUserRoleByRoleId(roleId string) int64 + + // BatchUserRole 批量新增用户角色信息 + BatchUserRole(sysUserRoles []model.SysUserRole) int64 + + // DeleteUserRole 批量删除用户和角色关联 + DeleteUserRole(userIds []string) int64 + + // DeleteUserRoleByRoleId 批量取消授权用户角色 + DeleteUserRoleByRoleId(roleId string, userIds []string) int64 +} diff --git a/src/modules/system/repository/sys_user_role.impl.go b/src/modules/system/repository/sys_user_role.impl.go new file mode 100644 index 00000000..f44a2191 --- /dev/null +++ b/src/modules/system/repository/sys_user_role.impl.go @@ -0,0 +1,74 @@ +package repository + +import ( + "fmt" + "strings" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysUserRoleImpl 结构体 +var NewSysUserRoleImpl = &SysUserRoleImpl{} + +// SysUserRoleImpl 用户与角色关联表 数据层处理 +type SysUserRoleImpl struct{} + +// CountUserRoleByRoleId 通过角色ID查询角色使用数量 +func (r *SysUserRoleImpl) CountUserRoleByRoleId(roleId string) int64 { + querySql := "select count(1) as total from sys_user_role where role_id = ?" + results, err := datasource.RawDB("", querySql, []any{roleId}) + if err != nil { + logger.Errorf("query err => %v", err) + return 0 + } + if len(results) > 0 { + return parse.Number(results[0]["total"]) + } + return 0 +} + +// BatchUserRole 批量新增用户角色信息 +func (r *SysUserRoleImpl) BatchUserRole(sysUserRoles []model.SysUserRole) int64 { + keyValues := make([]string, 0) + for _, item := range sysUserRoles { + keyValues = append(keyValues, fmt.Sprintf("(%s,%s)", item.UserID, item.RoleID)) + } + sql := "insert into sys_user_role(user_id, role_id) values " + strings.Join(keyValues, ",") + results, err := datasource.ExecDB("", sql, nil) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// DeleteUserRole 批量删除用户和角色关联 +func (r *SysUserRoleImpl) DeleteUserRole(userIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(userIds)) + sql := "delete from sys_user_role where user_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(userIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// DeleteUserRoleByRoleId 批量取消授权用户角色 +func (r *SysUserRoleImpl) DeleteUserRoleByRoleId(roleId string, userIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(userIds)) + sql := "delete from sys_user_role where role_id= ? and user_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(userIds) + parameters = append([]any{roleId}, parameters...) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/system/service/sys_config.go b/src/modules/system/service/sys_config.go new file mode 100644 index 00000000..4ec80db1 --- /dev/null +++ b/src/modules/system/service/sys_config.go @@ -0,0 +1,30 @@ +package service + +import "ems.agt/src/modules/system/model" + +// ISysConfig 参数配置 服务层接口 +type ISysConfig interface { + // SelectDictDataPage 分页查询参数配置列表数据 + SelectConfigPage(query map[string]any) map[string]any + + // SelectConfigValueByKey 通过参数键名查询参数键值 + SelectConfigValueByKey(configKey string) string + + // SelectConfigById 通过配置ID查询参数配置信息 + SelectConfigById(configId string) model.SysConfig + + // CheckUniqueConfigKey 校验参数键名是否唯一 + CheckUniqueConfigKey(configKey, configId string) bool + + // InsertConfig 新增参数配置 + InsertConfig(sysConfig model.SysConfig) string + + // UpdateConfig 修改参数配置 + UpdateConfig(sysConfig model.SysConfig) int64 + + // DeleteConfigByIds 批量删除参数配置信息 + DeleteConfigByIds(configIds []string) (int64, error) + + // ResetConfigCache 重置参数缓存数据 + ResetConfigCache() +} diff --git a/src/modules/system/service/sys_config.impl.go b/src/modules/system/service/sys_config.impl.go new file mode 100644 index 00000000..c15eea06 --- /dev/null +++ b/src/modules/system/service/sys_config.impl.go @@ -0,0 +1,157 @@ +package service + +import ( + "errors" + + "ems.agt/src/framework/constants/cachekey" + "ems.agt/src/framework/redis" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysConfigImpl 结构体 +var NewSysConfigImpl = &SysConfigImpl{ + sysConfigRepository: repository.NewSysConfigImpl, +} + +// SysConfigImpl 参数配置 服务层处理 +type SysConfigImpl struct { + // 参数配置表 + sysConfigRepository repository.ISysConfig +} + +// SelectDictDataPage 分页查询参数配置列表数据 +func (r *SysConfigImpl) SelectConfigPage(query map[string]any) map[string]any { + return r.sysConfigRepository.SelectConfigPage(query) +} + +// SelectConfigList 查询参数配置列表 +func (r *SysConfigImpl) SelectConfigList(sysConfig model.SysConfig) []model.SysConfig { + return r.sysConfigRepository.SelectConfigList(sysConfig) +} + +// SelectConfigValueByKey 通过参数键名查询参数键值 +func (r *SysConfigImpl) SelectConfigValueByKey(configKey string) string { + cacheKey := r.getCacheKey(configKey) + // 从缓存中读取 + cacheValue, err := redis.Get("", cacheKey) + if cacheValue != "" || err != nil { + return cacheValue + } + // 无缓存时读取数据放入缓存中 + configValue := r.sysConfigRepository.SelectConfigValueByKey(configKey) + if configValue != "" { + redis.Set("", cacheKey, configValue) + return configValue + } + return "" +} + +// SelectConfigById 通过配置ID查询参数配置信息 +func (r *SysConfigImpl) SelectConfigById(configId string) model.SysConfig { + if configId == "" { + return model.SysConfig{} + } + configs := r.sysConfigRepository.SelectConfigByIds([]string{configId}) + if len(configs) > 0 { + return configs[0] + } + return model.SysConfig{} +} + +// CheckUniqueConfigKey 校验参数键名是否唯一 +func (r *SysConfigImpl) CheckUniqueConfigKey(configKey, configId string) bool { + uniqueId := r.sysConfigRepository.CheckUniqueConfig(model.SysConfig{ + ConfigKey: configKey, + }) + if uniqueId == configId { + return true + } + return uniqueId == "" +} + +// InsertConfig 新增参数配置 +func (r *SysConfigImpl) InsertConfig(sysConfig model.SysConfig) string { + configId := r.sysConfigRepository.InsertConfig(sysConfig) + if configId != "" { + r.loadingConfigCache(sysConfig.ConfigKey) + } + return configId +} + +// UpdateConfig 修改参数配置 +func (r *SysConfigImpl) UpdateConfig(sysConfig model.SysConfig) int64 { + rows := r.sysConfigRepository.UpdateConfig(sysConfig) + if rows > 0 { + r.loadingConfigCache(sysConfig.ConfigKey) + } + return rows +} + +// DeleteConfigByIds 批量删除参数配置信息 +func (r *SysConfigImpl) DeleteConfigByIds(configIds []string) (int64, error) { + // 检查是否存在 + configs := r.sysConfigRepository.SelectConfigByIds(configIds) + if len(configs) <= 0 { + return 0, errors.New("没有权限访问参数配置数据!") + } + for _, config := range configs { + // 检查是否为内置参数 + if config.ConfigType == "Y" { + return 0, errors.New(config.ConfigID + " 配置参数属于内置参数,禁止删除!") + } + // 清除缓存 + r.clearConfigCache(config.ConfigKey) + } + if len(configs) == len(configIds) { + rows := r.sysConfigRepository.DeleteConfigByIds(configIds) + return rows, nil + } + return 0, errors.New("删除参数配置信息失败!") +} + +// ResetConfigCache 重置参数缓存数据 +func (r *SysConfigImpl) ResetConfigCache() { + r.clearConfigCache("*") + r.loadingConfigCache("*") +} + +// getCacheKey 组装缓存key +func (r *SysConfigImpl) getCacheKey(configKey string) string { + return cachekey.SYS_CONFIG_KEY + configKey +} + +// loadingConfigCache 加载参数缓存数据 +func (r *SysConfigImpl) loadingConfigCache(configKey string) { + // 查询全部参数 + if configKey == "*" { + sysConfigs := r.SelectConfigList(model.SysConfig{}) + for _, v := range sysConfigs { + key := r.getCacheKey(v.ConfigKey) + redis.Del("", key) + redis.Set("", key, v.ConfigValue) + } + return + } + // 指定参数 + if configKey != "" { + cacheValue := r.sysConfigRepository.SelectConfigValueByKey(configKey) + if cacheValue != "" { + key := r.getCacheKey(configKey) + redis.Del("", key) + redis.Set("", key, cacheValue) + } + return + } +} + +// clearConfigCache 清空参数缓存数据 +func (r *SysConfigImpl) clearConfigCache(configKey string) bool { + key := r.getCacheKey(configKey) + keys, err := redis.GetKeys("", key) + if err != nil { + return false + } + delOk, _ := redis.DelKeys("", keys) + return delOk +} diff --git a/src/modules/system/service/sys_dept.go b/src/modules/system/service/sys_dept.go new file mode 100644 index 00000000..db995fc5 --- /dev/null +++ b/src/modules/system/service/sys_dept.go @@ -0,0 +1,39 @@ +package service + +import ( + "ems.agt/src/framework/vo" + "ems.agt/src/modules/system/model" +) + +// ISysDept 部门管理 服务层接口 +type ISysDept interface { + // SelectDeptList 查询部门管理数据 + SelectDeptList(sysDept model.SysDept, dataScopeSQL string) []model.SysDept + + // SelectDeptListByRoleId 根据角色ID查询部门树信息 + SelectDeptListByRoleId(roleId string) []string + + // SelectDeptById 根据部门ID查询信息 + SelectDeptById(deptId string) model.SysDept + + // HasChildByDeptId 是否存在子节点 + HasChildByDeptId(deptId string) int64 + + // CheckDeptExistUser 查询部门是否存在用户 + CheckDeptExistUser(deptId string) int64 + + // CheckUniqueDeptName 校验同级部门名称是否唯一 + CheckUniqueDeptName(deptName, parentId, deptId string) bool + + // InsertDept 新增部门信息 + InsertDept(sysDept model.SysDept) string + + // UpdateDept 修改部门信息 + UpdateDept(sysDept model.SysDept) int64 + + // DeleteDeptById 删除部门管理信息 + DeleteDeptById(deptId string) int64 + + // SelectDeptTreeSelect 查询部门树结构信息 + SelectDeptTreeSelect(sysDept model.SysDept, dataScopeSQL string) []vo.TreeSelect +} diff --git a/src/modules/system/service/sys_dept.impl.go b/src/modules/system/service/sys_dept.impl.go new file mode 100644 index 00000000..c718df6a --- /dev/null +++ b/src/modules/system/service/sys_dept.impl.go @@ -0,0 +1,202 @@ +package service + +import ( + "strings" + + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/vo" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysDeptImpl 结构体 +var NewSysDeptImpl = &SysDeptImpl{ + sysDeptRepository: repository.NewSysDeptImpl, + sysRoleRepository: repository.NewSysRoleImpl, + sysRoleDeptRepository: repository.NewSysRoleDeptImpl, +} + +// SysDeptImpl 部门表 服务层处理 +type SysDeptImpl struct { + // 部门服务 + sysDeptRepository repository.ISysDept + // 角色服务 + sysRoleRepository repository.ISysRole + // 角色与部门关联服务 + sysRoleDeptRepository repository.ISysRoleDept +} + +// SelectDeptList 查询部门管理数据 +func (r *SysDeptImpl) SelectDeptList(sysDept model.SysDept, dataScopeSQL string) []model.SysDept { + return r.sysDeptRepository.SelectDeptList(sysDept, dataScopeSQL) +} + +// SelectDeptListByRoleId 根据角色ID查询部门树信息 TODO +func (r *SysDeptImpl) SelectDeptListByRoleId(roleId string) []string { + roles := r.sysRoleRepository.SelectRoleByIds([]string{roleId}) + if len(roles) > 0 { + role := roles[0] + if role.RoleID == roleId { + return r.sysDeptRepository.SelectDeptListByRoleId( + role.RoleID, + role.DeptCheckStrictly == "1", + ) + } + } + return []string{} +} + +// SelectDeptById 根据部门ID查询信息 +func (r *SysDeptImpl) SelectDeptById(deptId string) model.SysDept { + return r.sysDeptRepository.SelectDeptById(deptId) +} + +// HasChildByDeptId 是否存在子节点 +func (r *SysDeptImpl) HasChildByDeptId(deptId string) int64 { + return r.sysDeptRepository.HasChildByDeptId(deptId) +} + +// CheckDeptExistUser 查询部门是否存在用户 +func (r *SysDeptImpl) CheckDeptExistUser(deptId string) int64 { + return r.sysDeptRepository.CheckDeptExistUser(deptId) +} + +// CheckUniqueDeptName 校验同级部门名称是否唯一 +func (r *SysDeptImpl) CheckUniqueDeptName(deptName, parentId, deptId string) bool { + uniqueId := r.sysDeptRepository.CheckUniqueDept(model.SysDept{ + DeptName: deptName, + ParentID: parentId, + }) + if uniqueId == deptId { + return true + } + return uniqueId == "" +} + +// InsertDept 新增部门信息 +func (r *SysDeptImpl) InsertDept(sysDept model.SysDept) string { + return r.sysDeptRepository.InsertDept(sysDept) +} + +// UpdateDept 修改部门信息 +func (r *SysDeptImpl) UpdateDept(sysDept model.SysDept) int64 { + parentDept := r.sysDeptRepository.SelectDeptById(sysDept.ParentID) + dept := r.sysDeptRepository.SelectDeptById(sysDept.DeptID) + // 上级与当前部门祖级列表更新 + if parentDept.DeptID == sysDept.ParentID && dept.DeptID == sysDept.DeptID { + newAncestors := parentDept.Ancestors + "," + parentDept.DeptID + oldAncestors := dept.Ancestors + // 祖级列表不一致时更新 + if newAncestors != oldAncestors { + sysDept.Ancestors = newAncestors + r.updateDeptChildren(sysDept.DeptID, newAncestors, oldAncestors) + } + } + // 如果该部门是启用状态,则启用该部门的所有上级部门 + if sysDept.Status == common.STATUS_YES && parentDept.Status == common.STATUS_NO { + r.updateDeptStatusNormal(sysDept.Ancestors) + } + return r.sysDeptRepository.UpdateDept(sysDept) +} + +// updateDeptStatusNormal 修改所在部门正常状态 +func (r *SysDeptImpl) updateDeptStatusNormal(ancestors string) int64 { + if ancestors == "" || ancestors == "0" { + return 0 + } + deptIds := strings.Split(ancestors, ",") + return r.sysDeptRepository.UpdateDeptStatusNormal(deptIds) +} + +// updateDeptChildren 修改子元素关系 +func (r *SysDeptImpl) updateDeptChildren(deptId, newAncestors, oldAncestors string) int64 { + childrens := r.sysDeptRepository.SelectChildrenDeptById(deptId) + if len(childrens) == 0 { + return 0 + } + // 替换父ID + for i := range childrens { + child := &childrens[i] + ancestors := strings.Replace(child.Ancestors, oldAncestors, newAncestors, -1) + child.Ancestors = ancestors + } + return r.sysDeptRepository.UpdateDeptChildren(childrens) +} + +// DeleteDeptById 删除部门管理信息 +func (r *SysDeptImpl) DeleteDeptById(deptId string) int64 { + // 删除角色与部门关联 + r.sysRoleDeptRepository.DeleteDeptRole([]string{deptId}) + return r.sysDeptRepository.DeleteDeptById(deptId) +} + +// SelectDeptTreeSelect 查询部门树结构信息 +func (r *SysDeptImpl) SelectDeptTreeSelect(sysDept model.SysDept, dataScopeSQL string) []vo.TreeSelect { + sysDepts := r.sysDeptRepository.SelectDeptList(sysDept, dataScopeSQL) + depts := r.parseDataToTree(sysDepts) + tree := make([]vo.TreeSelect, 0) + for _, dept := range depts { + tree = append(tree, vo.SysDeptTreeSelect(dept)) + } + return tree +} + +// parseDataToTree 将数据解析为树结构,构建前端所需要下拉树结构 +func (r *SysDeptImpl) parseDataToTree(sysDepts []model.SysDept) []model.SysDept { + // 节点分组 + nodesMap := make(map[string][]model.SysDept) + // 节点id + treeIds := []string{} + // 树节点 + tree := []model.SysDept{} + + for _, item := range sysDepts { + parentID := item.ParentID + // 分组 + mapItem, ok := nodesMap[parentID] + if !ok { + mapItem = []model.SysDept{} + } + mapItem = append(mapItem, item) + nodesMap[parentID] = mapItem + // 记录节点ID + treeIds = append(treeIds, item.DeptID) + } + + for key, value := range nodesMap { + // 选择不是节点ID的作为树节点 + found := false + for _, id := range treeIds { + if id == key { + found = true + break + } + } + if !found { + tree = append(tree, value...) + } + } + + for i, node := range tree { + iN := r.parseDataToTreeComponet(node, &nodesMap) + tree[i] = iN + } + + return tree +} + +// parseDataToTreeComponet 递归函数处理子节点 +func (r *SysDeptImpl) parseDataToTreeComponet(node model.SysDept, nodesMap *map[string][]model.SysDept) model.SysDept { + id := node.DeptID + children, ok := (*nodesMap)[id] + if ok { + node.Children = children + } + if len(node.Children) > 0 { + for i, child := range node.Children { + icN := r.parseDataToTreeComponet(child, nodesMap) + node.Children[i] = icN + } + } + return node +} diff --git a/src/modules/system/service/sys_dict_data.go b/src/modules/system/service/sys_dict_data.go new file mode 100644 index 00000000..802a7f7a --- /dev/null +++ b/src/modules/system/service/sys_dict_data.go @@ -0,0 +1,33 @@ +package service + +import "ems.agt/src/modules/system/model" + +// ISysDictData 字典类型数据 服务层接口 +type ISysDictData interface { + // SelectDictDataPage 根据条件分页查询字典数据 + SelectDictDataPage(query map[string]any) map[string]any + + // SelectDictDataList 根据条件查询字典数据 + SelectDictDataList(sysDictData model.SysDictData) []model.SysDictData + + // SelectDictDataByCode 根据字典数据编码查询信息 + SelectDictDataByCode(dictCode string) model.SysDictData + + // SelectDictDataByType 根据字典类型查询信息 + SelectDictDataByType(dictType string) []model.SysDictData + + // CheckUniqueDictLabel 校验字典标签是否唯一 + CheckUniqueDictLabel(dictType, dictLabel, dictCode string) bool + + // CheckUniqueDictValue 校验字典键值是否唯一 + CheckUniqueDictValue(dictType, dictValue, dictCode string) bool + + // DeleteDictDataByCodes 批量删除字典数据信息 + DeleteDictDataByCodes(dictCodes []string) (int64, error) + + // InsertDictData 新增字典数据信息 + InsertDictData(sysDictData model.SysDictData) string + + // UpdateDictData 修改字典数据信息 + UpdateDictData(sysDictData model.SysDictData) int64 +} diff --git a/src/modules/system/service/sys_dict_data.impl.go b/src/modules/system/service/sys_dict_data.impl.go new file mode 100644 index 00000000..dff29613 --- /dev/null +++ b/src/modules/system/service/sys_dict_data.impl.go @@ -0,0 +1,110 @@ +package service + +import ( + "errors" + + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysDictDataImpl 结构体 +var NewSysDictDataImpl = &SysDictDataImpl{ + sysDictDataRepository: repository.NewSysDictDataImpl, + sysDictTypeService: NewSysDictTypeImpl, +} + +// SysDictDataImpl 字典类型数据 服务层处理 +type SysDictDataImpl struct { + // 字典数据服务 + sysDictDataRepository repository.ISysDictData + // 字典类型服务 + sysDictTypeService ISysDictType +} + +// SelectDictDataPage 根据条件分页查询字典数据 +func (r *SysDictDataImpl) SelectDictDataPage(query map[string]any) map[string]any { + return r.sysDictDataRepository.SelectDictDataPage(query) +} + +// SelectDictDataList 根据条件查询字典数据 +func (r *SysDictDataImpl) SelectDictDataList(sysDictData model.SysDictData) []model.SysDictData { + return r.sysDictDataRepository.SelectDictDataList(sysDictData) +} + +// SelectDictDataByCode 根据字典数据编码查询信息 +func (r *SysDictDataImpl) SelectDictDataByCode(dictCode string) model.SysDictData { + if dictCode == "" { + return model.SysDictData{} + } + dictCodes := r.sysDictDataRepository.SelectDictDataByCodes([]string{dictCode}) + if len(dictCodes) > 0 { + return dictCodes[0] + } + return model.SysDictData{} +} + +// SelectDictDataByType 根据字典类型查询信息 +func (r *SysDictDataImpl) SelectDictDataByType(dictType string) []model.SysDictData { + return r.sysDictTypeService.DictDataCache(dictType) +} + +// CheckUniqueDictLabel 校验字典标签是否唯一 +func (r *SysDictDataImpl) CheckUniqueDictLabel(dictType, dictLabel, dictCode string) bool { + uniqueId := r.sysDictDataRepository.CheckUniqueDictData(model.SysDictData{ + DictType: dictType, + DictLabel: dictLabel, + }) + if uniqueId == dictCode { + return true + } + return uniqueId == "" +} + +// CheckUniqueDictValue 校验字典键值是否唯一 +func (r *SysDictDataImpl) CheckUniqueDictValue(dictType, dictValue, dictCode string) bool { + uniqueId := r.sysDictDataRepository.CheckUniqueDictData(model.SysDictData{ + DictType: dictType, + DictValue: dictValue, + }) + if uniqueId == dictCode { + return true + } + return uniqueId == "" +} + +// DeleteDictDataByCodes 批量删除字典数据信息 +func (r *SysDictDataImpl) DeleteDictDataByCodes(dictCodes []string) (int64, error) { + // 检查是否存在 + dictDatas := r.sysDictDataRepository.SelectDictDataByCodes(dictCodes) + if len(dictDatas) <= 0 { + return 0, errors.New("没有权限访问字典编码数据!") + } + if len(dictDatas) == len(dictCodes) { + for _, v := range dictDatas { + // 刷新缓存 + r.sysDictTypeService.ClearDictCache(v.DictType) + r.sysDictTypeService.LoadingDictCache(v.DictType) + } + rows := r.sysDictDataRepository.DeleteDictDataByCodes(dictCodes) + return rows, nil + } + return 0, errors.New("删除字典数据信息失败!") +} + +// InsertDictData 新增字典数据信息 +func (r *SysDictDataImpl) InsertDictData(sysDictData model.SysDictData) string { + insertId := r.sysDictDataRepository.InsertDictData(sysDictData) + if insertId != "" { + r.sysDictTypeService.LoadingDictCache(sysDictData.DictType) + } + return insertId +} + +// UpdateDictData 修改字典数据信息 +func (r *SysDictDataImpl) UpdateDictData(sysDictData model.SysDictData) int64 { + rows := r.sysDictDataRepository.UpdateDictData(sysDictData) + if rows > 0 { + r.sysDictTypeService.LoadingDictCache(sysDictData.DictType) + } + return rows +} diff --git a/src/modules/system/service/sys_dict_type.go b/src/modules/system/service/sys_dict_type.go new file mode 100644 index 00000000..a2a14467 --- /dev/null +++ b/src/modules/system/service/sys_dict_type.go @@ -0,0 +1,45 @@ +package service + +import "ems.agt/src/modules/system/model" + +// ISysDictType 字典类型 服务层接口 +type ISysDictType interface { + // SelectDictTypePage 根据条件分页查询字典类型 + SelectDictTypePage(query map[string]any) map[string]any + + // SelectDictTypeList 根据条件查询字典类型 + SelectDictTypeList(sysDictType model.SysDictType) []model.SysDictType + + // SelectDictTypeByID 根据字典类型ID查询信息 + SelectDictTypeByID(dictID string) model.SysDictType + + // SelectDictTypeByType 根据字典类型查询信息 + SelectDictTypeByType(dictType string) model.SysDictType + + // CheckUniqueDictName 校验字典名称是否唯一 + CheckUniqueDictName(dictName, dictID string) bool + + // CheckUniqueDictType 校验字典类型是否唯一 + CheckUniqueDictType(dictType, dictID string) bool + + // InsertDictType 新增字典类型信息 + InsertDictType(sysDictType model.SysDictType) string + + // UpdateDictType 修改字典类型信息 + UpdateDictType(sysDictType model.SysDictType) int64 + + // DeleteDictTypeByIDs 批量删除字典类型信息 + DeleteDictTypeByIDs(dictIDs []string) (int64, error) + + // ResetDictCache 重置字典缓存数据 + ResetDictCache() + + // 加载字典缓存数据 + LoadingDictCache(dictType string) + + // 清空字典缓存数据 + ClearDictCache(dictType string) bool + + // DictDataCache 获取字典数据缓存数据 + DictDataCache(dictType string) []model.SysDictData +} diff --git a/src/modules/system/service/sys_dict_type.impl.go b/src/modules/system/service/sys_dict_type.impl.go new file mode 100644 index 00000000..41cce72a --- /dev/null +++ b/src/modules/system/service/sys_dict_type.impl.go @@ -0,0 +1,211 @@ +package service + +import ( + "encoding/json" + "errors" + "fmt" + + "ems.agt/src/framework/constants/cachekey" + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/redis" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysDictTypeImpl 结构体 +var NewSysDictTypeImpl = &SysDictTypeImpl{ + sysDictTypeRepository: repository.NewSysDictTypeImpl, + sysDictDataRepository: repository.NewSysDictDataImpl, +} + +// SysDictTypeImpl 字典类型 服务层处理 +type SysDictTypeImpl struct { + // 字典类型服务 + sysDictTypeRepository repository.ISysDictType + // 字典数据服务 + sysDictDataRepository repository.ISysDictData +} + +// SelectDictTypePage 根据条件分页查询字典类型 +func (r *SysDictTypeImpl) SelectDictTypePage(query map[string]any) map[string]any { + return r.sysDictTypeRepository.SelectDictTypePage(query) +} + +// SelectDictTypeList 根据条件查询字典类型 +func (r *SysDictTypeImpl) SelectDictTypeList(sysDictType model.SysDictType) []model.SysDictType { + return r.sysDictTypeRepository.SelectDictTypeList(sysDictType) +} + +// SelectDictTypeByID 根据字典类型ID查询信息 +func (r *SysDictTypeImpl) SelectDictTypeByID(dictID string) model.SysDictType { + if dictID == "" { + return model.SysDictType{} + } + dictTypes := r.sysDictTypeRepository.SelectDictTypeByIDs([]string{dictID}) + if len(dictTypes) > 0 { + return dictTypes[0] + } + return model.SysDictType{} +} + +// SelectDictTypeByType 根据字典类型查询信息 +func (r *SysDictTypeImpl) SelectDictTypeByType(dictType string) model.SysDictType { + return r.sysDictTypeRepository.SelectDictTypeByType(dictType) +} + +// CheckUniqueDictName 校验字典名称是否唯一 +func (r *SysDictTypeImpl) CheckUniqueDictName(dictName, dictID string) bool { + uniqueId := r.sysDictTypeRepository.CheckUniqueDictType(model.SysDictType{ + DictName: dictName, + }) + if uniqueId == dictID { + return true + } + return uniqueId == "" +} + +// CheckUniqueDictType 校验字典类型是否唯一 +func (r *SysDictTypeImpl) CheckUniqueDictType(dictType, dictID string) bool { + uniqueId := r.sysDictTypeRepository.CheckUniqueDictType(model.SysDictType{ + DictType: dictType, + }) + if uniqueId == dictID { + return true + } + return uniqueId == "" +} + +// InsertDictType 新增字典类型信息 +func (r *SysDictTypeImpl) InsertDictType(sysDictType model.SysDictType) string { + insertId := r.sysDictTypeRepository.InsertDictType(sysDictType) + if insertId != "" { + r.LoadingDictCache(sysDictType.DictType) + } + return insertId +} + +// UpdateDictType 修改字典类型信息 +func (r *SysDictTypeImpl) UpdateDictType(sysDictType model.SysDictType) int64 { + data := r.sysDictTypeRepository.SelectDictTypeByIDs([]string{sysDictType.DictID}) + if len(data) == 0 { + return 0 + } + // 修改字典类型key时同步更新其字典数据的类型key + oldDictType := data[0].DictType + rows := r.sysDictTypeRepository.UpdateDictType(sysDictType) + if rows > 0 && oldDictType != "" && oldDictType != sysDictType.DictType { + r.sysDictDataRepository.UpdateDictDataType(oldDictType, sysDictType.DictType) + } + // 刷新缓存 + r.ClearDictCache(oldDictType) + r.LoadingDictCache(sysDictType.DictType) + return rows +} + +// DeleteDictTypeByIDs 批量删除字典类型信息 +func (r *SysDictTypeImpl) DeleteDictTypeByIDs(dictIDs []string) (int64, error) { + // 检查是否存在 + dictTypes := r.sysDictTypeRepository.SelectDictTypeByIDs(dictIDs) + if len(dictTypes) <= 0 { + return 0, errors.New("没有权限访问字典类型数据!") + } + for _, v := range dictTypes { + // 字典类型下级含有数据 + useCount := r.sysDictDataRepository.CountDictDataByType(v.DictType) + if useCount > 0 { + msg := fmt.Sprintf("【%s】存在字典数据,不能删除", v.DictName) + return 0, errors.New(msg) + } + // 清除缓存 + r.ClearDictCache(v.DictType) + } + if len(dictTypes) == len(dictIDs) { + rows := r.sysDictTypeRepository.DeleteDictTypeByIDs(dictIDs) + return rows, nil + } + return 0, errors.New("删除字典数据信息失败!") +} + +// ResetDictCache 重置字典缓存数据 +func (r *SysDictTypeImpl) ResetDictCache() { + r.ClearDictCache("*") + r.LoadingDictCache("") +} + +// getCacheKey 组装缓存key +func (r *SysDictTypeImpl) getDictCache(dictType string) string { + return cachekey.SYS_DICT_KEY + dictType +} + +// LoadingDictCache 加载字典缓存数据 +func (r *SysDictTypeImpl) LoadingDictCache(dictType string) { + sysDictData := model.SysDictData{ + Status: common.STATUS_YES, + } + + // 指定字典类型 + if dictType != "" { + sysDictData.DictType = dictType + // 删除缓存 + key := r.getDictCache(dictType) + redis.Del("", key) + } + + sysDictDataList := r.sysDictDataRepository.SelectDictDataList(sysDictData) + if len(sysDictDataList) == 0 { + return + } + + // 将字典数据按类型分组 + m := make(map[string][]model.SysDictData, 0) + for _, v := range sysDictDataList { + key := v.DictType + if item, ok := m[key]; ok { + m[key] = append(item, v) + } else { + m[key] = []model.SysDictData{v} + } + } + + // 放入缓存 + for k, v := range m { + key := r.getDictCache(k) + values, _ := json.Marshal(v) + redis.Set("", key, string(values)) + } +} + +// ClearDictCache 清空字典缓存数据 +func (r *SysDictTypeImpl) ClearDictCache(dictType string) bool { + key := r.getDictCache(dictType) + keys, err := redis.GetKeys("", key) + if err != nil { + return false + } + delOk, _ := redis.DelKeys("", keys) + return delOk +} + +// DictDataCache 获取字典数据缓存数据 +func (r *SysDictTypeImpl) DictDataCache(dictType string) []model.SysDictData { + data := []model.SysDictData{} + key := r.getDictCache(dictType) + jsonStr, _ := redis.Get("", key) + if len(jsonStr) > 7 { + err := json.Unmarshal([]byte(jsonStr), &data) + if err != nil { + data = []model.SysDictData{} + } + } else { + data = r.sysDictDataRepository.SelectDictDataList(model.SysDictData{ + Status: common.STATUS_YES, + DictType: dictType, + }) + if len(data) > 0 { + redis.Del("", key) + values, _ := json.Marshal(data) + redis.Set("", key, string(values)) + } + } + return data +} diff --git a/src/modules/system/service/sys_log_login.go b/src/modules/system/service/sys_log_login.go new file mode 100644 index 00000000..f2be50d4 --- /dev/null +++ b/src/modules/system/service/sys_log_login.go @@ -0,0 +1,24 @@ +package service + +import "ems.agt/src/modules/system/model" + +// ISysLogLogin 系统登录日志 服务层接口 +type ISysLogLogin interface { + // SelectSysLogLoginPage 分页查询系统登录日志集合 + SelectSysLogLoginPage(query map[string]any) map[string]any + + // SelectSysLogLoginList 查询系统登录日志集合 + SelectSysLogLoginList(sysLogLogin model.SysLogLogin) []model.SysLogLogin + + // InsertSysLogLogin 新增系统登录日志 + InsertSysLogLogin(sysLogLogin model.SysLogLogin) string + + // DeleteSysLogLoginByIds 批量删除系统登录日志 + DeleteSysLogLoginByIds(loginIds []string) int64 + + // CleanSysLogLogin 清空系统登录日志 + CleanSysLogLogin() error + + // NewSysLogLogin 生成系统登录日志 + NewSysLogLogin(userName, status, msg string, ilobArgs ...string) string +} diff --git a/src/modules/system/service/sys_log_login.impl.go b/src/modules/system/service/sys_log_login.impl.go new file mode 100644 index 00000000..4ede16eb --- /dev/null +++ b/src/modules/system/service/sys_log_login.impl.go @@ -0,0 +1,56 @@ +package service + +import ( + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysLogLoginImpl 结构体 +var NewSysLogLoginImpl = &SysLogLoginImpl{ + sysLogLoginService: repository.NewSysLogLoginImpl, +} + +// SysLogLoginImpl 系统登录访问 服务层处理 +type SysLogLoginImpl struct { + // 系统登录访问信息 + sysLogLoginService repository.ISysLogLogin +} + +// SelectSysLogLoginPage 分页查询系统登录日志集合 +func (s *SysLogLoginImpl) SelectSysLogLoginPage(query map[string]any) map[string]any { + return s.sysLogLoginService.SelectSysLogLoginPage(query) +} + +// SelectSysLogLoginList 查询系统登录日志集合 +func (s *SysLogLoginImpl) SelectSysLogLoginList(sysSysLogLogin model.SysLogLogin) []model.SysLogLogin { + return s.sysLogLoginService.SelectSysLogLoginList(sysSysLogLogin) +} + +// InsertSysLogLogin 新增系统登录日志 +func (s *SysLogLoginImpl) InsertSysLogLogin(sysSysLogLogin model.SysLogLogin) string { + return s.sysLogLoginService.InsertSysLogLogin(sysSysLogLogin) +} + +// DeleteSysLogLoginByIds 批量删除系统登录日志 +func (s *SysLogLoginImpl) DeleteSysLogLoginByIds(loginIds []string) int64 { + return s.sysLogLoginService.DeleteSysLogLoginByIds(loginIds) +} + +// CleanSysLogLogin 清空系统登录日志 +func (s *SysLogLoginImpl) CleanSysLogLogin() error { + return s.sysLogLoginService.CleanSysLogLogin() +} + +// NewSysLogLogin 生成系统登录日志 +func (s *SysLogLoginImpl) NewSysLogLogin(userName, status, msg string, ilobArgs ...string) string { + sysSysLogLogin := model.SysLogLogin{ + IPAddr: ilobArgs[0], + LoginLocation: ilobArgs[1], + OS: ilobArgs[2], + Browser: ilobArgs[3], + UserName: userName, + Status: status, + Msg: msg, + } + return s.InsertSysLogLogin(sysSysLogLogin) +} diff --git a/src/modules/system/service/sys_log_operate.go b/src/modules/system/service/sys_log_operate.go new file mode 100644 index 00000000..54411199 --- /dev/null +++ b/src/modules/system/service/sys_log_operate.go @@ -0,0 +1,24 @@ +package service + +import "ems.agt/src/modules/system/model" + +// ISysLogOperate 操作日志表 服务层接口 +type ISysLogOperate interface { + // SelectSysLogOperatePage 分页查询系统操作日志集合 + SelectSysLogOperatePage(query map[string]any) map[string]any + + // SelectSysLogOperateList 查询系统操作日志集合 + SelectSysLogOperateList(sysLogOperate model.SysLogOperate) []model.SysLogOperate + + // SelectSysLogOperateById 查询操作日志详细 + SelectSysLogOperateById(operId string) model.SysLogOperate + + // InsertSysLogOperate 新增操作日志 + InsertSysLogOperate(sysLogOperate model.SysLogOperate) string + + // DeleteSysLogOperateByIds 批量删除系统操作日志 + DeleteSysLogOperateByIds(operIds []string) int64 + + // CleanSysLogOperate 清空操作日志 + CleanSysLogOperate() error +} diff --git a/src/modules/system/service/sys_log_operate.impl.go b/src/modules/system/service/sys_log_operate.impl.go new file mode 100644 index 00000000..6c40be58 --- /dev/null +++ b/src/modules/system/service/sys_log_operate.impl.go @@ -0,0 +1,47 @@ +package service + +import ( + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysLogOperateImpl 结构体 +var NewSysLogOperateImpl = &SysLogOperateImpl{ + SysLogOperateService: repository.NewSysLogOperateImpl, +} + +// SysLogOperateImpl 操作日志表 服务层处理 +type SysLogOperateImpl struct { + // 操作日志信息 + SysLogOperateService repository.ISysLogOperate +} + +// SelectSysLogOperatePage 分页查询系统操作日志集合 +func (r *SysLogOperateImpl) SelectSysLogOperatePage(query map[string]any) map[string]any { + return r.SysLogOperateService.SelectSysLogOperatePage(query) +} + +// SelectSysLogOperateList 查询系统操作日志集合 +func (r *SysLogOperateImpl) SelectSysLogOperateList(SysLogOperate model.SysLogOperate) []model.SysLogOperate { + return r.SysLogOperateService.SelectSysLogOperateList(SysLogOperate) +} + +// SelectSysLogOperateById 查询操作日志详细 +func (r *SysLogOperateImpl) SelectSysLogOperateById(operId string) model.SysLogOperate { + return r.SysLogOperateService.SelectSysLogOperateById(operId) +} + +// InsertSysLogOperate 新增操作日志 +func (r *SysLogOperateImpl) InsertSysLogOperate(SysLogOperate model.SysLogOperate) string { + return r.SysLogOperateService.InsertSysLogOperate(SysLogOperate) +} + +// DeleteSysLogOperateByIds 批量删除系统操作日志 +func (r *SysLogOperateImpl) DeleteSysLogOperateByIds(operIds []string) int64 { + return r.SysLogOperateService.DeleteSysLogOperateByIds(operIds) +} + +// CleanSysLogOperate 清空操作日志 +func (r *SysLogOperateImpl) CleanSysLogOperate() error { + return r.SysLogOperateService.CleanSysLogOperate() +} diff --git a/src/modules/system/service/sys_menu.go b/src/modules/system/service/sys_menu.go new file mode 100644 index 00000000..10374be1 --- /dev/null +++ b/src/modules/system/service/sys_menu.go @@ -0,0 +1,51 @@ +package service + +import ( + "ems.agt/src/framework/vo" + "ems.agt/src/modules/system/model" +) + +// ISysMenu 菜单 服务层接口 +type ISysMenu interface { + // SelectMenuList 查询系统菜单列表 + SelectMenuList(sysMenu model.SysMenu, userId string) []model.SysMenu + + // SelectMenuPermsByUserId 根据用户ID查询权限 + SelectMenuPermsByUserId(userId string) []string + + // SelectMenuPermsByUserId 根据用户ID查询权限 + SelectMenuTreeByUserId(userId string) []model.SysMenu + + // SelectMenuTreeSelectByUserId 查询菜单树结构信息 + SelectMenuTreeSelectByUserId(sysMenu model.SysMenu, userId string) []vo.TreeSelect + + // SelectMenuListByRoleId 根据角色ID查询菜单树信息 + SelectMenuListByRoleId(roleId string) []string + + // SelectMenuById 根据菜单ID查询信息 + SelectMenuById(menuId string) model.SysMenu + + // HasChildByMenuIdAndStatus 存在菜单子节点数量与状态 + HasChildByMenuIdAndStatus(menuId, status string) int64 + + // CheckMenuExistRole 查询菜单分配角色数量 + CheckMenuExistRole(menuId string) int64 + + // InsertMenu 新增菜单信息 + InsertMenu(sysMenu model.SysMenu) string + + // UpdateMenu 修改菜单信息 + UpdateMenu(sysMenu model.SysMenu) int64 + + // DeleteMenuById 删除菜单管理信息 + DeleteMenuById(menuId string) int64 + + // CheckUniqueMenuName 校验菜单名称是否唯一 + CheckUniqueMenuName(menuName, parentId, menuId string) bool + + // CheckUniqueMenuPath 校验路由地址是否唯一(针对目录和菜单) + CheckUniqueMenuPath(path, menuId string) bool + + // BuildRouteMenus 构建前端路由所需要的菜单 + BuildRouteMenus(sysMenus []model.SysMenu, prefix string) []vo.Router +} diff --git a/src/modules/system/service/sys_menu.impl.go b/src/modules/system/service/sys_menu.impl.go new file mode 100644 index 00000000..1dd1d837 --- /dev/null +++ b/src/modules/system/service/sys_menu.impl.go @@ -0,0 +1,380 @@ +package service + +import ( + "encoding/base64" + "strings" + + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/constants/menu" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/regular" + "ems.agt/src/framework/vo" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysMenuImpl 结构体 +var NewSysMenuImpl = &SysMenuImpl{ + sysMenuRepository: repository.NewSysMenuImpl, + sysRoleMenuRepository: repository.NewSysRoleMenuImpl, + sysRoleRepository: repository.NewSysRoleImpl, +} + +// SysMenuImpl 菜单 服务层处理 +type SysMenuImpl struct { + // 菜单服务 + sysMenuRepository repository.ISysMenu + // 角色与菜单关联服务 + sysRoleMenuRepository repository.ISysRoleMenu + // 角色服务 + sysRoleRepository repository.ISysRole +} + +// SelectMenuList 查询系统菜单列表 +func (r *SysMenuImpl) SelectMenuList(sysMenu model.SysMenu, userId string) []model.SysMenu { + return r.sysMenuRepository.SelectMenuList(sysMenu, userId) +} + +// SelectMenuPermsByUserId 根据用户ID查询权限 +func (r *SysMenuImpl) SelectMenuPermsByUserId(userId string) []string { + return r.sysMenuRepository.SelectMenuPermsByUserId(userId) +} + +// SelectMenuTreeByUserId 根据用户ID查询菜单 +func (r *SysMenuImpl) SelectMenuTreeByUserId(userId string) []model.SysMenu { + sysMenus := r.sysMenuRepository.SelectMenuTreeByUserId(userId) + return r.parseDataToTree(sysMenus) +} + +// SelectMenuTreeSelectByUserId 根据用户ID查询菜单树结构信息 +func (r *SysMenuImpl) SelectMenuTreeSelectByUserId(sysMenu model.SysMenu, userId string) []vo.TreeSelect { + sysMenus := r.sysMenuRepository.SelectMenuList(sysMenu, userId) + menus := r.parseDataToTree(sysMenus) + tree := make([]vo.TreeSelect, 0) + for _, menu := range menus { + tree = append(tree, vo.SysMenuTreeSelect(menu)) + } + return tree +} + +// SelectMenuListByRoleId 根据角色ID查询菜单树信息 TODO +func (r *SysMenuImpl) SelectMenuListByRoleId(roleId string) []string { + roles := r.sysRoleRepository.SelectRoleByIds([]string{roleId}) + if len(roles) > 0 { + role := roles[0] + if role.RoleID == roleId { + return r.sysMenuRepository.SelectMenuListByRoleId( + role.RoleID, + role.MenuCheckStrictly == "1", + ) + } + } + return []string{} +} + +// SelectMenuById 根据菜单ID查询信息 +func (r *SysMenuImpl) SelectMenuById(menuId string) model.SysMenu { + if menuId == "" { + return model.SysMenu{} + } + menus := r.sysMenuRepository.SelectMenuByIds([]string{menuId}) + if len(menus) > 0 { + return menus[0] + } + return model.SysMenu{} +} + +// HasChildByMenuIdAndStatus 存在菜单子节点数量与状态 +func (r *SysMenuImpl) HasChildByMenuIdAndStatus(menuId, status string) int64 { + return r.sysMenuRepository.HasChildByMenuIdAndStatus(menuId, status) +} + +// CheckMenuExistRole 查询菜单是否存在角色 +func (r *SysMenuImpl) CheckMenuExistRole(menuId string) int64 { + return r.sysRoleMenuRepository.CheckMenuExistRole(menuId) +} + +// InsertMenu 新增菜单信息 +func (r *SysMenuImpl) InsertMenu(sysMenu model.SysMenu) string { + return r.sysMenuRepository.InsertMenu(sysMenu) +} + +// UpdateMenu 修改菜单信息 +func (r *SysMenuImpl) UpdateMenu(sysMenu model.SysMenu) int64 { + return r.sysMenuRepository.UpdateMenu(sysMenu) +} + +// DeleteMenuById 删除菜单管理信息 +func (r *SysMenuImpl) DeleteMenuById(menuId string) int64 { + // 删除菜单与角色关联 + r.sysRoleMenuRepository.DeleteMenuRole([]string{menuId}) + return r.sysMenuRepository.DeleteMenuById(menuId) +} + +// CheckUniqueMenuName 校验菜单名称是否唯一 +func (r *SysMenuImpl) CheckUniqueMenuName(menuName, parentId, menuId string) bool { + uniqueId := r.sysMenuRepository.CheckUniqueMenu(model.SysMenu{ + MenuName: menuName, + ParentID: parentId, + }) + if uniqueId == menuId { + return true + } + return uniqueId == "" +} + +// CheckUniqueMenuPath 校验路由地址是否唯一(针对目录和菜单) +func (r *SysMenuImpl) CheckUniqueMenuPath(path, menuId string) bool { + uniqueId := r.sysMenuRepository.CheckUniqueMenu(model.SysMenu{ + Path: path, + }) + if uniqueId == menuId { + return true + } + return uniqueId == "" +} + +// BuildRouteMenus 构建前端路由所需要的菜单 +func (r *SysMenuImpl) BuildRouteMenus(sysMenus []model.SysMenu, prefix string) []vo.Router { + routers := []vo.Router{} + for _, item := range sysMenus { + router := vo.Router{} + router.Name = r.getRouteName(item) + router.Path = r.getRouterPath(item) + router.Component = r.getComponent(item) + router.Meta = r.getRouteMeta(item) + + // 子项菜单 目录类型 非路径链接 + cMenus := item.Children + if len(cMenus) > 0 && item.MenuType == menu.TYPE_DIR && !regular.ValidHttp(item.Path) { + // 获取重定向地址 + redirectPrefix, redirectPath := r.getRouteRedirect( + cMenus, + router.Path, + prefix, + ) + router.Redirect = redirectPath + // 子菜单进入递归 + router.Children = r.BuildRouteMenus(cMenus, redirectPrefix) + } else if item.ParentID == "0" && item.IsFrame == common.STATUS_YES && item.MenuType == menu.TYPE_MENU { + // 父菜单 内部跳转 菜单类型 + menuPath := "/" + item.MenuID + childPath := menuPath + r.getRouterPath(item) + children := vo.Router{ + Name: r.getRouteName(item), + Path: childPath, + Component: item.Component, + Meta: r.getRouteMeta(item), + } + router.Meta.HideChildInMenu = true + router.Children = append(router.Children, children) + router.Name = item.MenuID + router.Path = menuPath + router.Redirect = childPath + router.Component = menu.COMPONENT_LAYOUT_BASIC + } else if item.ParentID == "0" && item.IsFrame == common.STATUS_YES && regular.ValidHttp(item.Path) { + // 父菜单 内部跳转 路径链接 + menuPath := "/" + item.MenuID + childPath := menuPath + r.getRouterPath(item) + children := vo.Router{ + Name: r.getRouteName(item), + Path: childPath, + Component: menu.COMPONENT_LAYOUT_LINK, + Meta: r.getRouteMeta(item), + } + router.Meta.HideChildInMenu = true + router.Children = append(router.Children, children) + router.Name = item.MenuID + router.Path = menuPath + router.Redirect = childPath + router.Component = menu.COMPONENT_LAYOUT_BASIC + } + + routers = append(routers, router) + } + return routers +} + +// getRouteName 获取路由名称 路径英文首字母大写 +func (r *SysMenuImpl) getRouteName(sysMenu model.SysMenu) string { + routerName := parse.ConvertToCamelCase(sysMenu.Path) + // 路径链接 + if regular.ValidHttp(sysMenu.Path) { + routerName = routerName[:5] + "Link" + } + // 拼上菜单ID防止name重名 + return routerName + "_" + sysMenu.MenuID +} + +// getRouterPath 获取路由地址 +func (r *SysMenuImpl) getRouterPath(sysMenu model.SysMenu) string { + routerPath := sysMenu.Path + + // 显式路径 + if routerPath == "" || strings.HasPrefix(routerPath, "/") { + return routerPath + } + + // 路径链接 内部跳转 + if regular.ValidHttp(routerPath) && sysMenu.IsFrame == common.STATUS_YES { + routerPath = regular.Replace(routerPath, `/^http(s)?:\/\/+/`, "") + routerPath = base64.StdEncoding.EncodeToString([]byte(routerPath)) + } + + // 父菜单 内部跳转 + if sysMenu.ParentID == "0" && sysMenu.IsFrame == common.STATUS_YES { + routerPath = "/" + routerPath + } + + return routerPath +} + +// getComponent 获取组件信息 +func (r *SysMenuImpl) getComponent(sysMenu model.SysMenu) string { + // 内部跳转 路径链接 + if sysMenu.IsFrame == common.STATUS_YES && regular.ValidHttp(sysMenu.Path) { + return menu.COMPONENT_LAYOUT_LINK + } + + // 非父菜单 目录类型 + if sysMenu.ParentID != "0" && sysMenu.MenuType == menu.TYPE_DIR { + return menu.COMPONENT_LAYOUT_BLANK + } + + // 组件路径 内部跳转 菜单类型 + if sysMenu.Component != "" && sysMenu.IsFrame == common.STATUS_YES && sysMenu.MenuType == menu.TYPE_MENU { + // 父菜单套外层布局 + if sysMenu.ParentID == "0" { + return menu.COMPONENT_LAYOUT_BASIC + } + return sysMenu.Component + } + + return menu.COMPONENT_LAYOUT_BASIC +} + +// getRouteMeta 获取路由元信息 +func (r *SysMenuImpl) getRouteMeta(sysMenu model.SysMenu) vo.RouterMeta { + meta := vo.RouterMeta{} + if sysMenu.Icon == "#" { + meta.Icon = "" + } else { + meta.Icon = sysMenu.Icon + } + meta.Title = sysMenu.MenuName + meta.HideChildInMenu = false + meta.HideInMenu = sysMenu.Visible == common.STATUS_NO + meta.Cache = sysMenu.IsCache == common.STATUS_YES + meta.Target = "" + + // 路径链接 非内部跳转 + if regular.ValidHttp(sysMenu.Path) && sysMenu.IsFrame == common.STATUS_NO { + meta.Target = "_blank" + } + + return meta +} + +// getRouteRedirect 获取路由重定向地址(针对目录) +// +// cMenus 子菜单数组 +// routerPath 当前菜单路径 +// prefix 菜单重定向路径前缀 +func (r *SysMenuImpl) getRouteRedirect(cMenus []model.SysMenu, routerPath string, prefix string) (string, string) { + redirectPath := "" + + // 重定向为首个显示并启用的子菜单 + var firstChild *model.SysMenu + for _, item := range cMenus { + if item.IsFrame == common.STATUS_YES && item.Visible == common.STATUS_YES { + firstChild = &item + break + } + } + + // 检查内嵌隐藏菜单是否可做重定向 + if firstChild == nil { + for _, item := range cMenus { + if item.IsFrame == common.STATUS_YES && item.Visible == common.STATUS_NO && strings.Contains(item.Path, menu.PATH_INLINE) { + firstChild = &item + break + } + } + } + + if firstChild != nil { + firstChildPath := r.getRouterPath(*firstChild) + if strings.HasPrefix(firstChildPath, "/") { + redirectPath = firstChildPath + } else { + // 拼接追加路径 + if !strings.HasPrefix(routerPath, "/") { + prefix += "/" + } + prefix = prefix + routerPath + redirectPath = prefix + "/" + firstChildPath + } + } + + return prefix, redirectPath +} + +// parseDataToTree 将数据解析为树结构,构建前端所需要下拉树结构 +func (r *SysMenuImpl) parseDataToTree(sysMenus []model.SysMenu) []model.SysMenu { + // 节点分组 + nodesMap := make(map[string][]model.SysMenu) + // 节点id + treeIds := []string{} + // 树节点 + tree := []model.SysMenu{} + + for _, item := range sysMenus { + parentID := item.ParentID + // 分组 + mapItem, ok := nodesMap[parentID] + if !ok { + mapItem = []model.SysMenu{} + } + mapItem = append(mapItem, item) + nodesMap[parentID] = mapItem + // 记录节点ID + treeIds = append(treeIds, item.MenuID) + } + + for key, value := range nodesMap { + // 选择不是节点ID的作为树节点 + found := false + for _, id := range treeIds { + if id == key { + found = true + break + } + } + if !found { + tree = append(tree, value...) + } + } + + for i, node := range tree { + iN := r.parseDataToTreeComponet(node, &nodesMap) + tree[i] = iN + } + + return tree +} + +// parseDataToTreeComponet 递归函数处理子节点 +func (r *SysMenuImpl) parseDataToTreeComponet(node model.SysMenu, nodesMap *map[string][]model.SysMenu) model.SysMenu { + id := node.MenuID + children, ok := (*nodesMap)[id] + if ok { + node.Children = children + } + if len(node.Children) > 0 { + for i, child := range node.Children { + icN := r.parseDataToTreeComponet(child, nodesMap) + node.Children[i] = icN + } + } + return node +} diff --git a/src/modules/system/service/sys_notice.go b/src/modules/system/service/sys_notice.go new file mode 100644 index 00000000..78244f0f --- /dev/null +++ b/src/modules/system/service/sys_notice.go @@ -0,0 +1,24 @@ +package service + +import "ems.agt/src/modules/system/model" + +// ISysNotice 公告 服务层接口 +type ISysNotice interface { + // SelectNoticePage 分页查询公告列表 + SelectNoticePage(query map[string]any) map[string]any + + // SelectNoticeList 查询公告列表 + SelectNoticeList(sysNotice model.SysNotice) []model.SysNotice + + // SelectNoticeById 查询公告信息 + SelectNoticeById(noticeId string) model.SysNotice + + // InsertNotice 新增公告 + InsertNotice(sysNotice model.SysNotice) string + + // UpdateNotice 修改公告 + UpdateNotice(sysNotice model.SysNotice) int64 + + // DeleteNoticeByIds 批量删除公告信息 + DeleteNoticeByIds(noticeIds []string) (int64, error) +} diff --git a/src/modules/system/service/sys_notice.impl.go b/src/modules/system/service/sys_notice.impl.go new file mode 100644 index 00000000..55eb34fa --- /dev/null +++ b/src/modules/system/service/sys_notice.impl.go @@ -0,0 +1,71 @@ +package service + +import ( + "errors" + + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysNoticeImpl 结构体 +var NewSysNoticeImpl = &SysNoticeImpl{ + sysNoticeRepository: repository.NewSysNoticeImpl, +} + +// SysNoticeImpl 公告 服务层处理 +type SysNoticeImpl struct { + // 公告服务 + sysNoticeRepository repository.ISysNotice +} + +// SelectNoticePage 分页查询公告列表 +func (r *SysNoticeImpl) SelectNoticePage(query map[string]any) map[string]any { + return r.sysNoticeRepository.SelectNoticePage(query) +} + +// SelectNoticeList 查询公告列表 +func (r *SysNoticeImpl) SelectNoticeList(sysNotice model.SysNotice) []model.SysNotice { + return r.sysNoticeRepository.SelectNoticeList(sysNotice) +} + +// SelectNoticeById 查询公告信息 +func (r *SysNoticeImpl) SelectNoticeById(noticeId string) model.SysNotice { + if noticeId == "" { + return model.SysNotice{} + } + configs := r.sysNoticeRepository.SelectNoticeByIds([]string{noticeId}) + if len(configs) > 0 { + return configs[0] + } + return model.SysNotice{} +} + +// InsertNotice 新增公告 +func (r *SysNoticeImpl) InsertNotice(sysNotice model.SysNotice) string { + return r.sysNoticeRepository.InsertNotice(sysNotice) +} + +// UpdateNotice 修改公告 +func (r *SysNoticeImpl) UpdateNotice(sysNotice model.SysNotice) int64 { + return r.sysNoticeRepository.UpdateNotice(sysNotice) +} + +// DeleteNoticeByIds 批量删除公告信息 +func (r *SysNoticeImpl) DeleteNoticeByIds(noticeIds []string) (int64, error) { + // 检查是否存在 + notices := r.sysNoticeRepository.SelectNoticeByIds(noticeIds) + if len(notices) <= 0 { + return 0, errors.New("没有权限访问公告信息数据!") + } + for _, notice := range notices { + // 检查是否为已删除 + if notice.DelFlag == "1" { + return 0, errors.New(notice.NoticeID + " 公告信息已经删除!") + } + } + if len(notices) == len(noticeIds) { + rows := r.sysNoticeRepository.DeleteNoticeByIds(noticeIds) + return rows, nil + } + return 0, errors.New("删除公告信息失败!") +} diff --git a/src/modules/system/service/sys_post.go b/src/modules/system/service/sys_post.go new file mode 100644 index 00000000..5903384e --- /dev/null +++ b/src/modules/system/service/sys_post.go @@ -0,0 +1,33 @@ +package service + +import "ems.agt/src/modules/system/model" + +// ISysPost 岗位信息 服务层接口 +type ISysPost interface { + // SelectPostPage 查询岗位分页数据集合 + SelectPostPage(query map[string]any) map[string]any + + // SelectPostList 查询岗位数据集合 + SelectPostList(sysPost model.SysPost) []model.SysPost + + // SelectPostById 通过岗位ID查询岗位信息 + SelectPostById(postId string) model.SysPost + + // SelectPostListByUserId 根据用户ID获取岗位选择框列表 + SelectPostListByUserId(userId string) []model.SysPost + + // DeletePostByIds 批量删除岗位信息 + DeletePostByIds(postIds []string) (int64, error) + + // UpdatePost 修改岗位信息 + UpdatePost(sysPost model.SysPost) int64 + + // InsertPost 新增岗位信息 + InsertPost(sysPost model.SysPost) string + + // CheckUniquePostName 校验岗位名称 + CheckUniquePostName(postName, postId string) bool + + // CheckUniquePostCode 校验岗位编码 + CheckUniquePostCode(postCode, postId string) bool +} diff --git a/src/modules/system/service/sys_post.impl.go b/src/modules/system/service/sys_post.impl.go new file mode 100644 index 00000000..ccaed52e --- /dev/null +++ b/src/modules/system/service/sys_post.impl.go @@ -0,0 +1,103 @@ +package service + +import ( + "errors" + "fmt" + + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysPostImpl 结构体 +var NewSysPostImpl = &SysPostImpl{ + sysPostRepository: repository.NewSysPostImpl, + sysUserPostRepository: repository.NewSysUserPostImpl, +} + +// SysPostImpl 岗位表 服务层处理 +type SysPostImpl struct { + // 岗位服务 + sysPostRepository repository.ISysPost + // 用户与岗位关联服务 + sysUserPostRepository repository.ISysUserPost +} + +// SelectPostPage 查询岗位分页数据集合 +func (r *SysPostImpl) SelectPostPage(query map[string]any) map[string]any { + return r.sysPostRepository.SelectPostPage(query) +} + +// SelectPostList 查询岗位数据集合 +func (r *SysPostImpl) SelectPostList(sysPost model.SysPost) []model.SysPost { + return r.sysPostRepository.SelectPostList(sysPost) +} + +// SelectPostById 通过岗位ID查询岗位信息 +func (r *SysPostImpl) SelectPostById(postId string) model.SysPost { + if postId == "" { + return model.SysPost{} + } + posts := r.sysPostRepository.SelectPostByIds([]string{postId}) + if len(posts) > 0 { + return posts[0] + } + return model.SysPost{} +} + +// SelectPostListByUserId 根据用户ID获取岗位选择框列表 +func (r *SysPostImpl) SelectPostListByUserId(userId string) []model.SysPost { + return r.sysPostRepository.SelectPostListByUserId(userId) +} + +// DeletePostByIds 批量删除岗位信息 +func (r *SysPostImpl) DeletePostByIds(postIds []string) (int64, error) { + // 检查是否存在 + posts := r.sysPostRepository.SelectPostByIds(postIds) + if len(posts) <= 0 { + return 0, errors.New("没有权限访问岗位数据!") + } + for _, post := range posts { + useCount := r.sysUserPostRepository.CountUserPostByPostId(post.PostID) + if useCount > 0 { + msg := fmt.Sprintf("【%s】已分配给用户,不能删除", post.PostName) + return 0, errors.New(msg) + } + } + if len(posts) == len(postIds) { + rows := r.sysPostRepository.DeletePostByIds(postIds) + return rows, nil + } + return 0, errors.New("删除岗位信息失败!") +} + +// UpdatePost 修改岗位信息 +func (r *SysPostImpl) UpdatePost(sysPost model.SysPost) int64 { + return r.sysPostRepository.UpdatePost(sysPost) +} + +// InsertPost 新增岗位信息 +func (r *SysPostImpl) InsertPost(sysPost model.SysPost) string { + return r.sysPostRepository.InsertPost(sysPost) +} + +// CheckUniquePostName 校验岗位名称 +func (r *SysPostImpl) CheckUniquePostName(postName, postId string) bool { + uniqueId := r.sysPostRepository.CheckUniquePost(model.SysPost{ + PostName: postName, + }) + if uniqueId == postId { + return true + } + return uniqueId == "" +} + +// CheckUniquePostCode 校验岗位编码 +func (r *SysPostImpl) CheckUniquePostCode(postCode, postId string) bool { + uniqueId := r.sysPostRepository.CheckUniquePost(model.SysPost{ + PostCode: postCode, + }) + if uniqueId == postId { + return true + } + return uniqueId == "" +} diff --git a/src/modules/system/service/sys_role.go b/src/modules/system/service/sys_role.go new file mode 100644 index 00000000..cc4d3017 --- /dev/null +++ b/src/modules/system/service/sys_role.go @@ -0,0 +1,42 @@ +package service + +import "ems.agt/src/modules/system/model" + +// ISysRole 角色 服务层接口 +type ISysRole interface { + // SelectRolePage 根据条件分页查询角色数据 + SelectRolePage(query map[string]any, dataScopeSQL string) map[string]any + + // SelectRoleList 根据条件查询角色数据 + SelectRoleList(sysRole model.SysRole, dataScopeSQL string) []model.SysRole + + // SelectRoleListByUserId 根据用户ID获取角色选择框列表 + SelectRoleListByUserId(userId string) []model.SysRole + + // SelectRoleById 通过角色ID查询角色 + SelectRoleById(roleId string) model.SysRole + + // UpdateRole 修改角色信息 + UpdateRole(sysRole model.SysRole) int64 + + // InsertRole 新增角色信息 + InsertRole(sysRole model.SysRole) string + + // DeleteRoleByIds 批量删除角色信息 + DeleteRoleByIds(roleIds []string) (int64, error) + + // CheckUniqueRoleName 校验角色名称是否唯一 + CheckUniqueRoleName(roleName, roleId string) bool + + // CheckUniqueRoleKey 校验角色权限是否唯一 + CheckUniqueRoleKey(roleKey, roleId string) bool + + // AuthDataScope 修改数据权限信息 + AuthDataScope(sysRole model.SysRole) int64 + + // DeleteAuthUsers 批量取消授权用户角色 + DeleteAuthUsers(roleId string, userIds []string) int64 + + // InsertAuthUsers 批量新增授权用户角色 + InsertAuthUsers(roleId string, userIds []string) int64 +} diff --git a/src/modules/system/service/sys_role.impl.go b/src/modules/system/service/sys_role.impl.go new file mode 100644 index 00000000..5e093d17 --- /dev/null +++ b/src/modules/system/service/sys_role.impl.go @@ -0,0 +1,189 @@ +package service + +import ( + "errors" + "fmt" + + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysRoleImpl 结构体 +var NewSysRoleImpl = &SysRoleImpl{ + sysRoleRepository: repository.NewSysRoleImpl, + sysUserRoleRepository: repository.NewSysUserRoleImpl, + sysRoleDeptRepository: repository.NewSysRoleDeptImpl, + sysRoleMenuRepository: repository.NewSysRoleMenuImpl, +} + +// SysRoleImpl 角色 服务层处理 +type SysRoleImpl struct { + // 角色服务 + sysRoleRepository repository.ISysRole + // 用户与角色关联服务 + sysUserRoleRepository repository.ISysUserRole + // 角色与部门关联服务 + sysRoleDeptRepository repository.ISysRoleDept + // 角色与菜单关联服务 + sysRoleMenuRepository repository.ISysRoleMenu +} + +// SelectRolePage 根据条件分页查询角色数据 +func (r *SysRoleImpl) SelectRolePage(query map[string]any, dataScopeSQL string) map[string]any { + return r.sysRoleRepository.SelectRolePage(query, dataScopeSQL) +} + +// SelectRoleList 根据条件查询角色数据 +func (r *SysRoleImpl) SelectRoleList(sysRole model.SysRole, dataScopeSQL string) []model.SysRole { + return r.sysRoleRepository.SelectRoleList(sysRole, dataScopeSQL) +} + +// SelectRoleListByUserId 根据用户ID获取角色选择框列表 +func (r *SysRoleImpl) SelectRoleListByUserId(userId string) []model.SysRole { + return r.sysRoleRepository.SelectRoleListByUserId(userId) +} + +// SelectRoleById 通过角色ID查询角色 +func (r *SysRoleImpl) SelectRoleById(roleId string) model.SysRole { + if roleId == "" { + return model.SysRole{} + } + posts := r.sysRoleRepository.SelectRoleByIds([]string{roleId}) + if len(posts) > 0 { + return posts[0] + } + return model.SysRole{} +} + +// UpdateRole 修改角色信息 +func (r *SysRoleImpl) UpdateRole(sysRole model.SysRole) int64 { + rows := r.sysRoleRepository.UpdateRole(sysRole) + if rows > 0 { + // 删除角色与菜单关联 + r.sysRoleMenuRepository.DeleteRoleMenu([]string{sysRole.RoleID}) + if len(sysRole.MenuIds) > 0 { + r.insertRoleMenu(sysRole.RoleID, sysRole.MenuIds) + } + } + return rows +} + +// InsertRole 新增角色信息 +func (r *SysRoleImpl) InsertRole(sysRole model.SysRole) string { + insertId := r.sysRoleRepository.InsertRole(sysRole) + if insertId != "" && len(sysRole.MenuIds) > 0 { + r.insertRoleMenu(insertId, sysRole.MenuIds) + } + return insertId +} + +// insertRoleMenu 新增角色菜单信息 +func (r *SysRoleImpl) insertRoleMenu(roleId string, menuIds []string) int64 { + if roleId == "" || len(menuIds) <= 0 { + return 0 + } + + sysRoleMenus := []model.SysRoleMenu{} + for _, menuId := range menuIds { + if menuId == "" { + continue + } + sysRoleMenus = append(sysRoleMenus, model.NewSysRoleMenu(roleId, menuId)) + } + + return r.sysRoleMenuRepository.BatchRoleMenu(sysRoleMenus) +} + +// DeleteRoleByIds 批量删除角色信息 +func (r *SysRoleImpl) DeleteRoleByIds(roleIds []string) (int64, error) { + // 检查是否存在 + roles := r.sysRoleRepository.SelectRoleByIds(roleIds) + if len(roles) <= 0 { + return 0, errors.New("没有权限访问角色数据!") + } + for _, role := range roles { + // 检查是否为已删除 + if role.DelFlag == "1" { + return 0, errors.New(role.RoleID + " 角色信息已经删除!") + } + // 检查分配用户 + userCount := r.sysUserRoleRepository.CountUserRoleByRoleId(role.RoleID) + if userCount > 0 { + msg := fmt.Sprintf("【%s】已分配给用户,不能删除", role.RoleName) + return 0, errors.New(msg) + } + } + if len(roles) == len(roleIds) { + // 删除角色与菜单关联 + r.sysRoleMenuRepository.DeleteRoleMenu(roleIds) + // 删除角色与部门关联 + r.sysRoleDeptRepository.DeleteRoleDept(roleIds) + rows := r.sysRoleRepository.DeleteRoleByIds(roleIds) + return rows, nil + } + return 0, errors.New("删除角色信息失败!") +} + +// CheckUniqueRoleName 校验角色名称是否唯一 +func (r *SysRoleImpl) CheckUniqueRoleName(roleName, roleId string) bool { + uniqueId := r.sysRoleRepository.CheckUniqueRole(model.SysRole{ + RoleName: roleName, + }) + if uniqueId == roleId { + return true + } + return uniqueId == "" +} + +// CheckUniqueRoleKey 校验角色权限是否唯一 +func (r *SysRoleImpl) CheckUniqueRoleKey(roleKey, roleId string) bool { + uniqueId := r.sysRoleRepository.CheckUniqueRole(model.SysRole{ + RoleKey: roleKey, + }) + if uniqueId == roleId { + return true + } + return uniqueId == "" +} + +// AuthDataScope 修改数据权限信息 +func (r *SysRoleImpl) AuthDataScope(sysRole model.SysRole) int64 { + // 修改角色信息 + rows := r.sysRoleRepository.UpdateRole(sysRole) + // 删除角色与部门关联 + r.sysRoleDeptRepository.DeleteRoleDept([]string{sysRole.RoleID}) + // 新增角色和部门信息 + if sysRole.DataScope == "2" && len(sysRole.DeptIds) > 0 { + sysRoleDepts := []model.SysRoleDept{} + for _, deptId := range sysRole.DeptIds { + if deptId == "" { + continue + } + sysRoleDepts = append(sysRoleDepts, model.NewSysRoleDept(sysRole.RoleID, deptId)) + } + rows += r.sysRoleDeptRepository.BatchRoleDept(sysRoleDepts) + } + return rows +} + +// DeleteAuthUsers 批量取消授权用户角色 +func (r *SysRoleImpl) DeleteAuthUsers(roleId string, userIds []string) int64 { + return r.sysUserRoleRepository.DeleteUserRoleByRoleId(roleId, userIds) +} + +// InsertAuthUsers 批量新增授权用户角色 +func (r *SysRoleImpl) InsertAuthUsers(roleId string, userIds []string) int64 { + if roleId == "" || len(userIds) <= 0 { + return 0 + } + + sysUserRoles := []model.SysUserRole{} + for _, userId := range userIds { + if userId == "" { + continue + } + sysUserRoles = append(sysUserRoles, model.NewSysUserRole(userId, roleId)) + } + + return r.sysUserRoleRepository.BatchUserRole(sysUserRoles) +} diff --git a/src/modules/system/service/sys_user.go b/src/modules/system/service/sys_user.go new file mode 100644 index 00000000..23fd5fb0 --- /dev/null +++ b/src/modules/system/service/sys_user.go @@ -0,0 +1,45 @@ +package service + +import "ems.agt/src/modules/system/model" + +// ISysUser 用户 服务层接口 +type ISysUser interface { + // SelectUserPage 根据条件分页查询用户列表 + SelectUserPage(query map[string]any, dataScopeSQL string) map[string]any + + // SelectUserList 根据条件查询用户列表 + SelectUserList(sysUser model.SysUser, dataScopeSQL string) []model.SysUser + + // SelectAllocatedPage 根据条件分页查询分配用户角色列表 + SelectAllocatedPage(query map[string]any, dataScopeSQL string) map[string]any + + // SelectUserByUserName 通过用户名查询用户 + SelectUserByUserName(userName string) model.SysUser + + // SelectUserById 通过用户ID查询用户 + SelectUserById(userId string) model.SysUser + + // InsertUser 新增用户信息 + InsertUser(sysUser model.SysUser) string + + // UpdateUser 修改用户信息 + UpdateUser(sysUser model.SysUser) int64 + + // UpdateUserAndRolePost 修改用户信息同时更新角色和岗位 + UpdateUserAndRolePost(sysUser model.SysUser) int64 + + // DeleteUserByIds 批量删除用户信息 + DeleteUserByIds(userIds []string) (int64, error) + + // CheckUniqueUserName 校验用户名称是否唯一 + CheckUniqueUserName(userName, userId string) bool + + // CheckUniquePhone 校验手机号码是否唯一 + CheckUniquePhone(phonenumber, userId string) bool + + // CheckUniqueEmail 校验email是否唯一 + CheckUniqueEmail(email, userId string) bool + + // ImportUser 导入用户数据 + ImportUser(rows []map[string]string, isUpdateSupport bool, operName string) string +} diff --git a/src/modules/system/service/sys_user.impl.go b/src/modules/system/service/sys_user.impl.go new file mode 100644 index 00000000..9f2cfa4f --- /dev/null +++ b/src/modules/system/service/sys_user.impl.go @@ -0,0 +1,322 @@ +package service + +import ( + "errors" + "fmt" + "strings" + + "ems.agt/src/framework/constants/admin" + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/utils/regular" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysUserImpl 结构体 +var NewSysUserImpl = &SysUserImpl{ + sysUserRepository: repository.NewSysUserImpl, + sysUserRoleRepository: repository.NewSysUserRoleImpl, + sysUserPostRepository: repository.NewSysUserPostImpl, + sysDictDataService: NewSysDictDataImpl, + sysConfigService: NewSysConfigImpl, +} + +// SysUserImpl 用户 服务层处理 +type SysUserImpl struct { + // 用户服务 + sysUserRepository repository.ISysUser + // 用户与角色服务 + sysUserRoleRepository repository.ISysUserRole + // 用户与岗位服务 + sysUserPostRepository repository.ISysUserPost + // 字典数据服务 + sysDictDataService ISysDictData + // 参数配置服务 + sysConfigService ISysConfig +} + +// SelectUserPage 根据条件分页查询用户列表 +func (r *SysUserImpl) SelectUserPage(query map[string]any, dataScopeSQL string) map[string]any { + return r.sysUserRepository.SelectUserPage(query, dataScopeSQL) +} + +// SelectUserList 根据条件查询用户列表 +func (r *SysUserImpl) SelectUserList(sysUser model.SysUser, dataScopeSQL string) []model.SysUser { + return []model.SysUser{} +} + +// SelectAllocatedPage 根据条件分页查询分配用户角色列表 +func (r *SysUserImpl) SelectAllocatedPage(query map[string]any, dataScopeSQL string) map[string]any { + return r.sysUserRepository.SelectAllocatedPage(query, dataScopeSQL) +} + +// SelectUserByUserName 通过用户名查询用户 +func (r *SysUserImpl) SelectUserByUserName(userName string) model.SysUser { + return r.sysUserRepository.SelectUserByUserName(userName) +} + +// SelectUserById 通过用户ID查询用户 +func (r *SysUserImpl) SelectUserById(userId string) model.SysUser { + if userId == "" { + return model.SysUser{} + } + users := r.sysUserRepository.SelectUserByIds([]string{userId}) + if len(users) > 0 { + return users[0] + } + return model.SysUser{} +} + +// InsertUser 新增用户信息 +func (r *SysUserImpl) InsertUser(sysUser model.SysUser) string { + // 新增用户信息 + insertId := r.sysUserRepository.InsertUser(sysUser) + if insertId != "" { + // 新增用户角色信息 + r.insertUserRole(insertId, sysUser.RoleIDs) + // 新增用户岗位信息 + r.insertUserPost(insertId, sysUser.PostIDs) + } + return insertId +} + +// insertUserRole 新增用户角色信息 +func (r *SysUserImpl) insertUserRole(userId string, roleIds []string) int64 { + if userId == "" || len(roleIds) <= 0 { + return 0 + } + + sysUserRoles := []model.SysUserRole{} + for _, roleId := range roleIds { + // 管理员角色禁止操作,只能通过配置指定用户ID分配 + if roleId == "" || roleId == admin.ROLE_ID { + continue + } + sysUserRoles = append(sysUserRoles, model.NewSysUserRole(userId, roleId)) + } + + return r.sysUserRoleRepository.BatchUserRole(sysUserRoles) +} + +// insertUserPost 新增用户岗位信息 +func (r *SysUserImpl) insertUserPost(userId string, postIds []string) int64 { + if userId == "" || len(postIds) <= 0 { + return 0 + } + + sysUserPosts := []model.SysUserPost{} + for _, postId := range postIds { + if postId == "" { + continue + } + sysUserPosts = append(sysUserPosts, model.NewSysUserPost(userId, postId)) + } + + return r.sysUserPostRepository.BatchUserPost(sysUserPosts) +} + +// UpdateUser 修改用户信息 +func (r *SysUserImpl) UpdateUser(sysUser model.SysUser) int64 { + return r.sysUserRepository.UpdateUser(sysUser) +} + +// UpdateUserAndRolePost 修改用户信息同时更新角色和岗位 +func (r *SysUserImpl) UpdateUserAndRolePost(sysUser model.SysUser) int64 { + // 删除用户与角色关联 + r.sysUserRoleRepository.DeleteUserRole([]string{sysUser.UserID}) + // 新增用户角色信息 + r.insertUserRole(sysUser.UserID, sysUser.RoleIDs) + // 删除用户与岗位关联 + r.sysUserPostRepository.DeleteUserPost([]string{sysUser.UserID}) + // 新增用户岗位信息 + r.insertUserPost(sysUser.UserID, sysUser.PostIDs) + return r.sysUserRepository.UpdateUser(sysUser) +} + +// DeleteUserByIds 批量删除用户信息 +func (r *SysUserImpl) DeleteUserByIds(userIds []string) (int64, error) { + // 检查是否存在 + users := r.sysUserRepository.SelectUserByIds(userIds) + if len(users) <= 0 { + return 0, errors.New("没有权限访问用户数据!") + } + if len(users) == len(userIds) { + // 删除用户与角色关联 + r.sysUserRoleRepository.DeleteUserRole(userIds) + // 删除用户与岗位关联 + r.sysUserPostRepository.DeleteUserPost(userIds) + // ... 注意其他userId进行关联的表 + // 删除用户 + rows := r.sysUserRepository.DeleteUserByIds(userIds) + return rows, nil + } + return 0, errors.New("删除用户信息失败!") +} + +// CheckUniqueUserName 校验用户名称是否唯一 +func (r *SysUserImpl) CheckUniqueUserName(userName, userId string) bool { + uniqueId := r.sysUserRepository.CheckUniqueUser(model.SysUser{ + UserName: userName, + }) + if uniqueId == userId { + return true + } + return uniqueId == "" +} + +// CheckUniquePhone 校验手机号码是否唯一 +func (r *SysUserImpl) CheckUniquePhone(phonenumber, userId string) bool { + uniqueId := r.sysUserRepository.CheckUniqueUser(model.SysUser{ + PhoneNumber: phonenumber, + }) + if uniqueId == userId { + return true + } + return uniqueId == "" +} + +// CheckUniqueEmail 校验email是否唯一 +func (r *SysUserImpl) CheckUniqueEmail(email, userId string) bool { + uniqueId := r.sysUserRepository.CheckUniqueUser(model.SysUser{ + Email: email, + }) + if uniqueId == userId { + return true + } + return uniqueId == "" +} + +// ImportUser 导入用户数据 +func (r *SysUserImpl) ImportUser(rows []map[string]string, isUpdateSupport bool, operName string) string { + // 读取默认初始密码 + initPassword := r.sysConfigService.SelectConfigValueByKey("sys.user.initPassword") + // 读取用户性别字典数据 + dictSysUserSex := r.sysDictDataService.SelectDictDataByType("sys_user_sex") + + // 导入记录 + successNum := 0 + failureNum := 0 + successMsgArr := []string{} + failureMsgArr := []string{} + mustItemArr := []string{"C", "D"} + for _, row := range rows { + // 检查必填列 + ownItem := true + for _, item := range mustItemArr { + if v, ok := row[item]; !ok || v == "" { + ownItem = false + break + } + } + if !ownItem { + mustItemArrStr := strings.Join(mustItemArr, "、") + failureNum++ + failureMsgArr = append(failureMsgArr, fmt.Sprintf("表格中必填列表项,%s}", mustItemArrStr)) + continue + } + + // 用户性别转值 + sysUserSex := "0" + for _, v := range dictSysUserSex { + if row["G"] == v.DictLabel { + sysUserSex = v.DictValue + break + } + } + sysUserStatus := common.STATUS_NO + if row["H"] == "正常" { + sysUserStatus = common.STATUS_YES + } + + // 构建用户实体信息 + newSysUser := model.SysUser{ + UserType: "sys", + Password: initPassword, + DeptID: row["B"], + UserName: row["C"], + NickName: row["D"], + PhoneNumber: row["F"], + Email: row["E"], + Status: sysUserStatus, + Sex: sysUserSex, + } + + // 检查手机号码格式并判断是否唯一 + if newSysUser.PhoneNumber != "" { + if regular.ValidMobile(newSysUser.PhoneNumber) { + uniquePhone := r.CheckUniquePhone(newSysUser.PhoneNumber, "") + if !uniquePhone { + msg := fmt.Sprintf("序号:%s 手机号码 %s 已存在", row["A"], row["F"]) + failureNum++ + failureMsgArr = append(failureMsgArr, msg) + continue + } + } else { + msg := fmt.Sprintf("序号:%s 手机号码 %s 格式错误", row["A"], row["F"]) + failureNum++ + failureMsgArr = append(failureMsgArr, msg) + continue + } + } + + // 检查邮箱格式并判断是否唯一 + if newSysUser.Email != "" { + if regular.ValidEmail(newSysUser.Email) { + uniqueEmail := r.CheckUniqueEmail(newSysUser.Email, "") + if !uniqueEmail { + msg := fmt.Sprintf("序号:%s 用户邮箱 %s 已存在", row["A"], row["E"]) + failureNum++ + failureMsgArr = append(failureMsgArr, msg) + continue + } + } else { + msg := fmt.Sprintf("序号:%s 用户邮箱 %s 格式错误", row["A"], row["E"]) + failureNum++ + failureMsgArr = append(failureMsgArr, msg) + continue + } + } + + // 验证是否存在这个用户 + userInfo := r.sysUserRepository.SelectUserByUserName(newSysUser.UserName) + if userInfo.UserName != newSysUser.UserName { + newSysUser.CreateBy = operName + insertId := r.InsertUser(newSysUser) + if insertId != "" { + msg := fmt.Sprintf("序号:%s 登录名称 %s 导入成功", row["A"], row["C"]) + successNum++ + successMsgArr = append(successMsgArr, msg) + } else { + msg := fmt.Sprintf("序号:%s 登录名称 %s 导入失败", row["A"], row["E"]) + failureNum++ + failureMsgArr = append(failureMsgArr, msg) + } + continue + } + + // 如果用户已存在 同时 是否更新支持 + if userInfo.UserName == newSysUser.UserName && isUpdateSupport { + newSysUser.UserID = userInfo.UserID + newSysUser.UpdateBy = operName + rows := r.UpdateUser(newSysUser) + if rows > 0 { + msg := fmt.Sprintf("序号:%s 登录名称 %s 更新成功", row["A"], row["C"]) + successNum++ + successMsgArr = append(successMsgArr, msg) + } else { + msg := fmt.Sprintf("序号:%s 登录名称 %s 更新失败", row["A"], row["E"]) + failureNum++ + failureMsgArr = append(failureMsgArr, msg) + } + continue + } + } + + if failureNum > 0 { + failureMsgArr = append([]string{fmt.Sprintf("很抱歉,导入失败!共 %d 条数据格式不正确,错误如下:", failureNum)}, failureMsgArr...) + return strings.Join(failureMsgArr, "
") + } + + successMsgArr = append([]string{fmt.Sprintf("恭喜您,数据已全部导入成功!共 %d 条,数据如下:", successNum)}, successMsgArr...) + return strings.Join(successMsgArr, "
") +} diff --git a/src/modules/system/system.go b/src/modules/system/system.go new file mode 100644 index 00000000..07d226c3 --- /dev/null +++ b/src/modules/system/system.go @@ -0,0 +1,466 @@ +package system + +import ( + "ems.agt/src/framework/logger" + "ems.agt/src/framework/middleware" + "ems.agt/src/framework/middleware/collectlogs" + "ems.agt/src/framework/middleware/repeat" + "ems.agt/src/modules/system/controller" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" +) + +// Setup 模块路由注册 +func Setup(router *gin.Engine) { + logger.Infof("开始加载 ====> system 模块路由") + + // 启动时需要的初始参数 + InitLoad() + + // 参数配置信息 + sysConfigGroup := router.Group("/system/config") + { + sysConfigGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:list"}}), + controller.NewSysConfig.List, + ) + sysConfigGroup.GET("/:configId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:query"}}), + controller.NewSysConfig.Info, + ) + sysConfigGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("参数配置信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysConfig.Add, + ) + sysConfigGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("参数配置信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysConfig.Edit, + ) + sysConfigGroup.DELETE("/:configIds", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("参数配置信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysConfig.Remove, + ) + sysConfigGroup.PUT("/refreshCache", + repeat.RepeatSubmit(5), + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("参数配置信息", collectlogs.BUSINESS_TYPE_CLEAN)), + controller.NewSysConfig.RefreshCache, + ) + sysConfigGroup.GET("/configKey/:configKey", controller.NewSysConfig.ConfigKey) + sysConfigGroup.POST("/export", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:export"}}), + collectlogs.OperateLog(collectlogs.OptionNew("参数配置信息", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewSysConfig.Export, + ) + } + + // 部门信息 + sysDeptGroup := router.Group("/system/dept") + { + sysDeptGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:list"}}), + controller.NewSysDept.List, + ) + sysDeptGroup.GET("/:deptId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:query"}}), + controller.NewSysDept.Info, + ) + sysDeptGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("部门信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysDept.Add, + ) + sysDeptGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("部门信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysDept.Edit, + ) + sysDeptGroup.DELETE("/:deptId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("部门信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysDept.Remove, + ) + sysDeptGroup.GET("/list/exclude/:deptId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:list"}}), + controller.NewSysDept.ExcludeChild, + ) + sysDeptGroup.GET("/treeSelect", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:list", "system:user:list"}}), + controller.NewSysDept.TreeSelect, + ) + sysDeptGroup.GET("/roleDeptTreeSelect/:roleId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:query"}}), + controller.NewSysDept.RoleDeptTreeSelect, + ) + } + + // 字典数据信息 + sysDictDataGroup := router.Group("/system/dict/data") + { + sysDictDataGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:list"}}), + controller.NewSysDictData.List, + ) + sysDictDataGroup.GET("/:dictCode", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:query"}}), + controller.NewSysDictData.Info, + ) + sysDictDataGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("字典数据信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysDictData.Add, + ) + sysDictDataGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("字典数据信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysDictData.Edit, + ) + sysDictDataGroup.DELETE("/:dictCodes", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("字典数据信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysDictData.Remove, + ) + sysDictDataGroup.GET("/type/:dictType", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:query"}}), + controller.NewSysDictData.DictType, + ) + sysDictDataGroup.POST("/export", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:export"}}), + collectlogs.OperateLog(collectlogs.OptionNew("字典类型信息", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewSysDictData.Export, + ) + } + + // 字典类型信息 + sysDictTypeGroup := router.Group("/system/dict/type") + { + sysDictTypeGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:list"}}), + controller.NewSysDictType.List, + ) + sysDictTypeGroup.GET("/:dictId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:query"}}), + controller.NewSysDictType.Info, + ) + sysDictTypeGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("字典类型信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysDictType.Add, + ) + sysDictTypeGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("字典类型信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysDictType.Edit, + ) + sysDictTypeGroup.DELETE("/:dictIds", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("字典类型信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysDictType.Remove, + ) + sysDictTypeGroup.PUT("/refreshCache", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("字典类型信息", collectlogs.BUSINESS_TYPE_CLEAN)), + controller.NewSysDictType.RefreshCache, + ) + sysDictTypeGroup.GET("/getDictOptionselect", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:query"}}), + controller.NewSysDictType.DictOptionselect, + ) + sysDictTypeGroup.POST("/export", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:export"}}), + collectlogs.OperateLog(collectlogs.OptionNew("字典类型信息", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewSysDictType.Export, + ) + } + + // 菜单信息 + sysMenuGroup := router.Group("/system/menu") + { + sysMenuGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:list"}}), + controller.NewSysMenu.List, + ) + sysMenuGroup.GET("/:menuId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:query"}}), + controller.NewSysMenu.Info, + ) + sysMenuGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("菜单信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysMenu.Add, + ) + sysMenuGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("菜单信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysMenu.Edit, + ) + sysMenuGroup.DELETE("/:menuId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("菜单信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysMenu.Remove, + ) + sysMenuGroup.GET("/treeSelect", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:list"}}), + controller.NewSysMenu.TreeSelect, + ) + sysMenuGroup.GET("/roleMenuTreeSelect/:roleId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:list"}}), + controller.NewSysMenu.RoleMenuTreeSelect, + ) + } + + // 通知公告信息 + sysNoticeGroup := router.Group("/system/notice") + { + sysNoticeGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:notice:list"}}), + controller.NewSysNotice.List, + ) + sysNoticeGroup.GET("/:noticeId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:notice:query"}}), + controller.NewSysNotice.Info, + ) + sysNoticeGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:notice:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("参数配置信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysNotice.Add, + ) + sysNoticeGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:notice:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("参数配置信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysNotice.Edit, + ) + sysNoticeGroup.DELETE("/:noticeIds", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:notice:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("参数配置信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysNotice.Remove, + ) + } + + // 岗位信息 + sysPostGroup := router.Group("/system/post") + { + sysPostGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:post:list"}}), + controller.NewSysPost.List, + ) + sysPostGroup.GET("/:postId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:post:query"}}), + controller.NewSysPost.Info, + ) + sysPostGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:post:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("岗位信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysPost.Add, + ) + sysPostGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:post:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("岗位信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysPost.Edit, + ) + sysPostGroup.DELETE("/:postIds", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:post:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("岗位信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysPost.Remove, + ) + sysPostGroup.POST("/export", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:post:export"}}), + controller.NewSysPost.Export, + ) + } + + // 个人信息 + sysProfileGroup := router.Group("/system/user/profile") + { + sysProfileGroup.GET("", + middleware.PreAuthorize(nil), + controller.NewSysProfile.Info, + ) + sysProfileGroup.PUT("", + middleware.PreAuthorize(nil), + controller.NewSysProfile.UpdateProfile, + ) + sysProfileGroup.PUT("/updatePwd", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("个人信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysProfile.UpdatePwd, + ) + sysProfileGroup.POST("/avatar", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("用户头像", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysProfile.Avatar, + ) + } + + // 角色信息 + sysRoleGroup := router.Group("/system/role") + { + sysRoleGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:role:list"}}), + controller.NewSysRole.List, + ) + sysRoleGroup.GET("/:roleId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:role:query"}}), + controller.NewSysRole.Info, + ) + sysRoleGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:role:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("角色信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysRole.Add, + ) + sysRoleGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:role:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("角色信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysRole.Edit, + ) + sysRoleGroup.DELETE("/:roleIds", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:role:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("角色信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysRole.Remove, + ) + sysRoleGroup.PUT("/changeStatus", + repeat.RepeatSubmit(5), + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:role:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("角色信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysRole.Status, + ) + sysRoleGroup.PUT("/dataScope", + repeat.RepeatSubmit(5), + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("角色信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysRole.DataScope, + ) + sysRoleGroup.GET("/authUser/allocatedList", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:list"}}), + controller.NewSysRole.AuthUserAllocatedList, + ) + sysRoleGroup.PUT("/authUser/checked", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("角色信息", collectlogs.BUSINESS_TYPE_GRANT)), + controller.NewSysRole.AuthUserChecked, + ) + sysRoleGroup.POST("/export", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:export"}}), + collectlogs.OperateLog(collectlogs.OptionNew("角色信息", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewSysRole.Export, + ) + } + + // 用户信息 + sysUserGroup := router.Group("/system/user") + { + sysUserGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:list"}}), + controller.NewSysUser.List, + ) + sysUserGroup.GET("/:userId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:query"}}), + controller.NewSysUser.Info, + ) + sysUserGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("用户信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysUser.Add, + ) + sysUserGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("用户信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysUser.Edit, + ) + sysUserGroup.DELETE("/:userIds", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("用户信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysUser.Remove, + ) + sysUserGroup.PUT("/resetPwd", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:resetPwd"}}), + collectlogs.OperateLog(collectlogs.OptionNew("用户信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysUser.ResetPwd, + ) + sysUserGroup.PUT("/changeStatus", + repeat.RepeatSubmit(5), + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("用户信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysUser.Status, + ) + sysUserGroup.POST("/export", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:export"}}), + collectlogs.OperateLog(collectlogs.OptionNew("用户信息", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewSysUser.Export, + ) + sysUserGroup.GET("/importTemplate", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:import"}}), + controller.NewSysUser.Template, + ) + sysUserGroup.POST("/importData", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:import"}}), + collectlogs.OperateLog(collectlogs.OptionNew("用户信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysUser.ImportData, + ) + } + + // 操作日志记录信息 + sysOperLogGroup := router.Group("/system/log/operate") + { + sysOperLogGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:operate:list"}}), + controller.NewSysLogOperate.List, + ) + sysOperLogGroup.DELETE("/:operIds", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:operate:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("操作日志", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysLogOperate.Remove, + ) + sysOperLogGroup.DELETE("/clean", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:operate:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("操作日志", collectlogs.BUSINESS_TYPE_CLEAN)), + controller.NewSysLogOperate.Clean, + ) + sysOperLogGroup.POST("/export", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:operate:export"}}), + collectlogs.OperateLog(collectlogs.OptionNew("操作日志", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewSysLogOperate.Export, + ) + } + + // 系统登录日志信息 + sysLogininforGroup := router.Group("/system/log/login") + { + sysLogininforGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:login:list"}}), + controller.NewSysLogLogin.List, + ) + sysLogininforGroup.DELETE("/:loginIds", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:login:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("系统登录信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysLogLogin.Remove, + ) + sysLogininforGroup.DELETE("/clean", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:login:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("系统登录信息", collectlogs.BUSINESS_TYPE_CLEAN)), + controller.NewSysLogLogin.Clean, + ) + sysLogininforGroup.PUT("/unlock/:userName", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:login:unlock"}}), + collectlogs.OperateLog(collectlogs.OptionNew("系统登录信息", collectlogs.BUSINESS_TYPE_CLEAN)), + controller.NewSysLogLogin.Unlock, + ) + sysLogininforGroup.POST("/export", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:login:export"}}), + collectlogs.OperateLog(collectlogs.OptionNew("系统登录信息", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewSysLogLogin.Export, + ) + } +} + +// InitLoad 初始参数 +func InitLoad() { + // 启动时,刷新缓存-参数配置 + service.NewSysConfigImpl.ResetConfigCache() + // 启动时,刷新缓存-字典类型数据 + service.NewSysDictTypeImpl.ResetDictCache() +}