Compare commits
3 Commits
ab8be1a832
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 68c91b4ba3 | |||
| 133209691e | |||
| a8233ceb72 |
@@ -1,225 +0,0 @@
|
|||||||
# 数据库初始化说明
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
本目录包含城市生命线AI数智化平台的数据库初始化脚本,用于创建表结构和插入基础数据。
|
|
||||||
|
|
||||||
## 文件说明
|
|
||||||
|
|
||||||
### 表结构创建脚本
|
|
||||||
|
|
||||||
| 文件名 | 说明 |
|
|
||||||
|--------|------|
|
|
||||||
| `createDB.sql` | 创建数据库 |
|
|
||||||
| `createTablePermission.sql` | 创建权限相关表(部门、角色、权限、视图、ACL等) |
|
|
||||||
| `createTableUser.sql` | 创建用户相关表(用户、用户信息、登录日志) |
|
|
||||||
| `createTableFile.sql` | 创建文件管理表 |
|
|
||||||
| `createTableMessage.sql` | 创建消息通知表 |
|
|
||||||
| `createTableLog.sql` | 创建日志表 |
|
|
||||||
| `createTableConfig.sql` | 创建配置管理表 |
|
|
||||||
| `createTableKnowledge.sql` | 创建知识库表 |
|
|
||||||
| `createTableBidding.sql` | 创建招投标业务表 |
|
|
||||||
| `createTableCustomerService.sql` | 创建智能客服表 |
|
|
||||||
| `createTableAgent.sql` | 创建智能体表(暂不启用) |
|
|
||||||
|
|
||||||
### 数据初始化脚本
|
|
||||||
|
|
||||||
| 文件名 | 说明 | 是否必需 |
|
|
||||||
|--------|------|----------|
|
|
||||||
| `initDataPermission.sql` | 初始化权限基础数据(部门、角色、权限、视图、模块) | ✅ 必需 |
|
|
||||||
| `initDataUser.sql` | 初始化用户数据(管理员账户、演示用户) | ✅ 必需 |
|
|
||||||
| `initDataMessage.sql` | 初始化消息渠道配置和模板 | ✅ 必需 |
|
|
||||||
| `initDataConfig.sql` | 初始化系统配置参数 | ✅ 必需 |
|
|
||||||
|
|
||||||
### 总入口脚本
|
|
||||||
|
|
||||||
- `initAll.sql` - 一键执行所有建表和初始化脚本
|
|
||||||
|
|
||||||
## 使用方法
|
|
||||||
|
|
||||||
### 方式一:使用总入口脚本(推荐)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 进入 PostgreSQL 命令行
|
|
||||||
psql -U postgres
|
|
||||||
|
|
||||||
# 执行总初始化脚本
|
|
||||||
\i /path/to/urbanLifelineServ/.bin/database/postgres/sql/initAll.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
### 方式二:分步执行
|
|
||||||
|
|
||||||
#### 第一步:创建表结构
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 创建数据库
|
|
||||||
\i createDB.sql
|
|
||||||
|
|
||||||
# 创建各模块表结构
|
|
||||||
\i createTablePermission.sql
|
|
||||||
\i createTableUser.sql
|
|
||||||
\i createTableFile.sql
|
|
||||||
\i createTableMessage.sql
|
|
||||||
\i createTableLog.sql
|
|
||||||
\i createTableConfig.sql
|
|
||||||
\i createTableKnowledge.sql
|
|
||||||
\i createTableBidding.sql
|
|
||||||
\i createTableCustomerService.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 第二步:初始化基础数据
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 按顺序执行初始化脚本
|
|
||||||
\i initDataPermission.sql
|
|
||||||
\i initDataUser.sql
|
|
||||||
\i initDataMessage.sql
|
|
||||||
\i initDataConfig.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
## 初始化数据说明
|
|
||||||
|
|
||||||
### 1. 权限基础数据 (initDataPermission.sql)
|
|
||||||
|
|
||||||
**初始化内容:**
|
|
||||||
- **根部门**:dept_root
|
|
||||||
- **全局角色**:
|
|
||||||
- 超级管理员 (role_super_admin) - 拥有所有权限
|
|
||||||
- 系统管理员 (role_system_admin) - 拥有系统管理权限
|
|
||||||
- 普通用户 (role_user) - 基础查看和操作权限
|
|
||||||
- 访客 (role_guest) - 仅查看权限
|
|
||||||
|
|
||||||
- **系统模块**:
|
|
||||||
- 系统管理 (mod_system)
|
|
||||||
- 文件管理 (mod_file)
|
|
||||||
- 消息通知 (mod_message)
|
|
||||||
- 配置管理 (mod_config)
|
|
||||||
- 知识库 (mod_knowledge)
|
|
||||||
- 招投标 (mod_bidding)
|
|
||||||
- 智能客服 (mod_customer_service)
|
|
||||||
|
|
||||||
- **系统权限**:30+ 个基础权限,涵盖用户、角色、部门、权限、文件、消息、配置管理
|
|
||||||
|
|
||||||
- **系统视图(菜单)**:
|
|
||||||
- 系统管理菜单及其子菜单
|
|
||||||
- 业务管理菜单框架
|
|
||||||
|
|
||||||
### 2. 用户数据 (initDataUser.sql)
|
|
||||||
|
|
||||||
**初始化账户:**
|
|
||||||
|
|
||||||
| 用户 | 账号 | 默认密码 | 角色 | 说明 |
|
|
||||||
|------|------|----------|------|------|
|
|
||||||
| 系统管理员 | admin | admin123 | 超级管理员 | 拥有所有权限 |
|
|
||||||
| 演示用户 | demo | admin123 | 普通用户 | 用于演示和测试 |
|
|
||||||
|
|
||||||
**⚠️ 安全提示:**
|
|
||||||
- 生产环境部署前,必须修改默认密码
|
|
||||||
- 密码使用 bcrypt 加密存储
|
|
||||||
- 建议删除演示用户账户
|
|
||||||
|
|
||||||
### 3. 消息渠道配置 (initDataMessage.sql)
|
|
||||||
|
|
||||||
**初始化内容:**
|
|
||||||
- **消息渠道**(默认禁用,需配置后启用):
|
|
||||||
- 应用内消息 (app) - 默认启用
|
|
||||||
- 短信通知 (sms)
|
|
||||||
- 邮件通知 (email)
|
|
||||||
- 微信公众号 (wechat_official_account)
|
|
||||||
- 微信小程序 (wechat_applet)
|
|
||||||
- 钉钉通知 (dingtalk)
|
|
||||||
|
|
||||||
- **消息模板**:
|
|
||||||
- 用户注册欢迎
|
|
||||||
- 密码重置通知
|
|
||||||
- 系统维护通知
|
|
||||||
- 工单创建通知
|
|
||||||
- 招标公告发布
|
|
||||||
|
|
||||||
### 4. 系统配置 (initDataConfig.sql)
|
|
||||||
|
|
||||||
**初始化配置分组:**
|
|
||||||
- **站点配置**:站点名称、Logo、ICP备案
|
|
||||||
- **国际化**:默认语言、时区
|
|
||||||
- **安全认证**:密码策略、JWT过期时间、会话超时、注册开关
|
|
||||||
- **存储上传**:最大上传大小、存储后端、存储路径
|
|
||||||
- **通知配置**:邮件SMTP、短信服务商
|
|
||||||
- **日志审计**:日志级别、审计日志保留天数
|
|
||||||
- **平台特性**:维护模式、ACL策略开关
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. **执行顺序**:必须按照 `initAll.sql` 中的顺序执行,先创建表结构,再插入数据
|
|
||||||
2. **依赖关系**:初始化数据脚本有依赖关系,必须按顺序执行
|
|
||||||
3. **数据库权限**:执行脚本需要具有创建数据库、创建表、插入数据的权限
|
|
||||||
4. **字符编码**:确保数据库使用 UTF-8 编码
|
|
||||||
5. **时区设置**:建议数据库时区设置为 Asia/Shanghai 或 UTC
|
|
||||||
|
|
||||||
## 验证初始化结果
|
|
||||||
|
|
||||||
执行以下 SQL 验证初始化是否成功:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 检查表是否创建成功
|
|
||||||
SELECT schemaname, tablename
|
|
||||||
FROM pg_tables
|
|
||||||
WHERE schemaname IN ('sys', 'file', 'message', 'config')
|
|
||||||
ORDER BY schemaname, tablename;
|
|
||||||
|
|
||||||
-- 检查角色数量
|
|
||||||
SELECT COUNT(*) as role_count FROM sys.tb_sys_role WHERE deleted = false;
|
|
||||||
-- 预期结果:4
|
|
||||||
|
|
||||||
-- 检查权限数量
|
|
||||||
SELECT COUNT(*) as permission_count FROM sys.tb_sys_permission WHERE deleted = false;
|
|
||||||
-- 预期结果:30+
|
|
||||||
|
|
||||||
-- 检查用户数量
|
|
||||||
SELECT COUNT(*) as user_count FROM sys.tb_sys_user WHERE deleted = false;
|
|
||||||
-- 预期结果:2
|
|
||||||
|
|
||||||
-- 检查消息渠道数量
|
|
||||||
SELECT COUNT(*) as channel_count FROM message.tb_message_channel WHERE deleted = false;
|
|
||||||
-- 预期结果:6
|
|
||||||
|
|
||||||
-- 检查系统配置数量
|
|
||||||
SELECT COUNT(*) as config_count FROM config.tb_sys_config WHERE deleted = false;
|
|
||||||
-- 预期结果:20+
|
|
||||||
```
|
|
||||||
|
|
||||||
## 重置数据库
|
|
||||||
|
|
||||||
如需重新初始化数据库,可以执行以下操作:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 删除所有 schema(谨慎操作!)
|
|
||||||
DROP SCHEMA IF EXISTS sys CASCADE;
|
|
||||||
DROP SCHEMA IF EXISTS file CASCADE;
|
|
||||||
DROP SCHEMA IF EXISTS message CASCADE;
|
|
||||||
DROP SCHEMA IF EXISTS config CASCADE;
|
|
||||||
DROP SCHEMA IF EXISTS knowledge CASCADE;
|
|
||||||
DROP SCHEMA IF EXISTS bidding CASCADE;
|
|
||||||
DROP SCHEMA IF EXISTS customer_service CASCADE;
|
|
||||||
DROP SCHEMA IF EXISTS log CASCADE;
|
|
||||||
|
|
||||||
-- 然后重新执行 initAll.sql
|
|
||||||
\i initAll.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
## 常见问题
|
|
||||||
|
|
||||||
### Q: 执行初始化脚本报错 "relation already exists"
|
|
||||||
A: 表已存在,可以选择删除对应的表或 schema 后重新执行
|
|
||||||
|
|
||||||
### Q: 如何修改管理员默认密码?
|
|
||||||
A: 修改 `initDataUser.sql` 中的 password 字段,使用 bcrypt 加密后的密码哈希
|
|
||||||
|
|
||||||
### Q: 如何自定义初始化数据?
|
|
||||||
A: 直接修改对应的 `initData*.sql` 文件,按照现有格式添加或修改数据
|
|
||||||
|
|
||||||
### Q: 业务表(知识库、招投标、客服)需要初始化数据吗?
|
|
||||||
A: 不需要,这些表的数据在系统运行时动态产生
|
|
||||||
|
|
||||||
## 联系支持
|
|
||||||
|
|
||||||
如有问题,请联系技术支持团队。
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
|
|
||||||
-- 删除已存在的数据库(如果存在)
|
-- 删除已存在的数据库(如果存在)
|
||||||
DROP DATABASE IF EXISTS urban-lifeline;
|
DROP DATABASE IF EXISTS urban_lifeline;
|
||||||
|
|
||||||
-- 创建新数据库,使用 UTF8 编码,并设置适合中文的排序规则
|
-- 创建新数据库,使用 UTF8 编码,并设置适合中文的排序规则
|
||||||
-- 使用 template0 确保干净的数据库模板
|
-- 使用 template0 确保干净的数据库模板
|
||||||
-- zh_CN.UTF-8 支持中文字符排序和比较
|
-- zh_CN.UTF-8 支持中文字符排序和比较
|
||||||
CREATE DATABASE urban-lifeline
|
CREATE DATABASE urban_lifeline
|
||||||
ENCODING 'UTF8'
|
ENCODING 'UTF8'
|
||||||
TEMPLATE template0
|
TEMPLATE template0
|
||||||
LC_COLLATE 'zh_CN.UTF-8'
|
LC_COLLATE 'zh_CN.UTF-8'
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ CREATE TABLE config.tb_sys_config (
|
|||||||
description VARCHAR(255) NOT NULL, -- 配置描述
|
description VARCHAR(255) NOT NULL, -- 配置描述
|
||||||
re JSON DEFAULT NULL, -- 正则表达式校验规则
|
re JSON DEFAULT NULL, -- 正则表达式校验规则
|
||||||
options JSON DEFAULT NULL, -- 可选项,render_type为select、checkbox、radio时使用
|
options JSON DEFAULT NULL, -- 可选项,render_type为select、checkbox、radio时使用
|
||||||
group VARCHAR(255) NOT NULL, -- 配置组
|
"group" VARCHAR(255) NOT NULL, -- 配置组
|
||||||
module_id VARCHAR(255) NOT NULL, -- 模块id
|
module_id VARCHAR(255) NOT NULL, -- 模块id
|
||||||
order_num INT NOT NULL, -- 配置顺序
|
order_num INT NOT NULL, -- 配置顺序
|
||||||
status INT NOT NULL DEFAULT 0, -- 配置状态 0:启用 1:禁用
|
status INT NOT NULL DEFAULT 0, -- 配置状态 0:启用 1:禁用
|
||||||
|
|||||||
@@ -1,89 +1,153 @@
|
|||||||
-- ====================================================
|
-- ====================================================
|
||||||
-- 定时任务表
|
-- 定时任务表
|
||||||
-- ====================================================
|
-- ====================================================
|
||||||
DROP TABLE IF EXISTS `tb_crontab_task`;
|
CREATE SCHEMA IF NOT EXISTS crontab;
|
||||||
CREATE TABLE `tb_crontab_task` (
|
DROP TABLE IF EXISTS crontab.tb_crontab_task CASCADE;
|
||||||
`id` VARCHAR(64) NOT NULL COMMENT '主键ID',
|
CREATE TABLE crontab.tb_crontab_task (
|
||||||
`task_id` VARCHAR(64) NOT NULL COMMENT '任务ID',
|
id VARCHAR(64) NOT NULL,
|
||||||
`task_name` VARCHAR(100) NOT NULL COMMENT '任务名称',
|
task_id VARCHAR(64) NOT NULL,
|
||||||
`task_group` VARCHAR(50) NOT NULL DEFAULT 'DEFAULT' COMMENT '任务分组',
|
task_name VARCHAR(100) NOT NULL,
|
||||||
`meta_id` VARCHAR(64) NOT NULL COMMENT '任务元数据ID',
|
task_group VARCHAR(50) NOT NULL DEFAULT 'DEFAULT',
|
||||||
`default_recipient` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否使用默认接收人(0:否 1:是)',
|
meta_id VARCHAR(64) NOT NULL,
|
||||||
`bean_name` VARCHAR(100) NOT NULL COMMENT 'Bean名称',
|
default_recipient SMALLINT NOT NULL DEFAULT 0, -- 是否使用默认接收人(0:否 1:是)
|
||||||
`method_name` VARCHAR(100) NOT NULL COMMENT '方法名称',
|
bean_name VARCHAR(100) NOT NULL,
|
||||||
`method_params` VARCHAR(500) DEFAULT NULL COMMENT '方法参数',
|
method_name VARCHAR(100) NOT NULL,
|
||||||
`cron_expression` VARCHAR(100) NOT NULL COMMENT 'Cron表达式',
|
method_params VARCHAR(500) DEFAULT NULL,
|
||||||
`status` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '任务状态(0:暂停 1:运行中)',
|
cron_expression VARCHAR(100) NOT NULL,
|
||||||
`description` VARCHAR(500) DEFAULT NULL COMMENT '任务描述',
|
status SMALLINT NOT NULL DEFAULT 0, -- 任务状态(0:暂停 1:运行中)
|
||||||
`concurrent` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否允许并发执行(0:否 1:是)',
|
description VARCHAR(500) DEFAULT NULL,
|
||||||
`misfire_policy` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '错过执行策略(1:立即执行 2:执行一次 3:放弃执行)',
|
concurrent SMALLINT NOT NULL DEFAULT 0, -- 是否允许并发执行(0:否 1:是)
|
||||||
`creator` VARCHAR(64) DEFAULT NULL COMMENT '创建者',
|
misfire_policy SMALLINT NOT NULL DEFAULT 1, -- 错过执行策略(1:立即执行 2:执行一次 3:放弃执行)
|
||||||
`updater` VARCHAR(64) DEFAULT NULL COMMENT '更新者',
|
creator VARCHAR(64) DEFAULT NULL,
|
||||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
updater VARCHAR(64) DEFAULT NULL,
|
||||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
create_time TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
`delete_time` DATETIME DEFAULT NULL COMMENT '删除时间',
|
update_time TIMESTAMPTZ DEFAULT NULL,
|
||||||
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除(0:否 1:是)',
|
delete_time TIMESTAMPTZ DEFAULT NULL,
|
||||||
PRIMARY KEY (`id`),
|
deleted SMALLINT NOT NULL DEFAULT 0, -- 是否删除(0:否 1:是)
|
||||||
KEY `idx_task_name` (`task_name`),
|
PRIMARY KEY (id)
|
||||||
KEY `idx_bean_name` (`bean_name`),
|
);
|
||||||
KEY `idx_status` (`status`),
|
|
||||||
KEY `idx_deleted` (`deleted`)
|
CREATE INDEX IF NOT EXISTS idx_task_name ON crontab.tb_crontab_task(task_name);
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='定时任务配置表';
|
CREATE INDEX IF NOT EXISTS idx_bean_name ON crontab.tb_crontab_task(bean_name);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_status ON crontab.tb_crontab_task(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_deleted ON crontab.tb_crontab_task(deleted);
|
||||||
|
|
||||||
|
COMMENT ON TABLE crontab.tb_crontab_task IS '定时任务配置表';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task.id IS '主键ID';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task.task_id IS '任务ID';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task.task_name IS '任务名称';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task.task_group IS '任务分组';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task.meta_id IS '任务元数据ID';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task.default_recipient IS '是否使用默认接收人(0:否 1:是)';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task.bean_name IS 'Bean名称';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task.method_name IS '方法名称';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task.method_params IS '方法参数';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task.cron_expression IS 'Cron表达式';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task.status IS '任务状态(0:暂停 1:运行中)';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task.description IS '任务描述';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task.concurrent IS '是否允许并发执行(0:否 1:是)';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task.misfire_policy IS '错过执行策略(1:立即执行 2:执行一次 3:放弃执行)';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task.creator IS '创建者';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task.updater IS '更新者';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task.create_time IS '创建时间';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task.update_time IS '更新时间';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task.delete_time IS '删除时间';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task.deleted IS '是否删除(0:否 1:是)';
|
||||||
|
|
||||||
-- ====================================================
|
-- ====================================================
|
||||||
-- 定时任务执行日志表
|
-- 定时任务执行日志表
|
||||||
-- ====================================================
|
-- ====================================================
|
||||||
DROP TABLE IF EXISTS `tb_crontab_log`;
|
DROP TABLE IF EXISTS crontab.tb_crontab_log CASCADE;
|
||||||
CREATE TABLE `tb_crontab_log` (
|
CREATE TABLE crontab.tb_crontab_log (
|
||||||
`id` VARCHAR(64) NOT NULL COMMENT '主键ID',
|
id VARCHAR(64) NOT NULL,
|
||||||
`task_id` VARCHAR(64) NOT NULL COMMENT '任务ID',
|
task_id VARCHAR(64) NOT NULL,
|
||||||
`task_name` VARCHAR(100) NOT NULL COMMENT '任务名称',
|
task_name VARCHAR(100) NOT NULL,
|
||||||
`task_group` VARCHAR(50) NOT NULL DEFAULT 'DEFAULT' COMMENT '任务分组',
|
task_group VARCHAR(50) NOT NULL DEFAULT 'DEFAULT',
|
||||||
`bean_name` VARCHAR(100) NOT NULL COMMENT 'Bean名称',
|
bean_name VARCHAR(100) NOT NULL,
|
||||||
`method_name` VARCHAR(100) NOT NULL COMMENT '方法名称',
|
method_name VARCHAR(100) NOT NULL,
|
||||||
`method_params` VARCHAR(500) DEFAULT NULL COMMENT '方法参数',
|
method_params VARCHAR(500) DEFAULT NULL,
|
||||||
`execute_status` TINYINT(1) NOT NULL COMMENT '执行状态(0:失败 1:成功)',
|
execute_status SMALLINT NOT NULL, -- 执行状态(0:失败 1:成功)
|
||||||
`execute_message` TEXT DEFAULT NULL COMMENT '执行结果信息',
|
execute_message TEXT DEFAULT NULL,
|
||||||
`exception_info` TEXT DEFAULT NULL COMMENT '异常信息',
|
exception_info TEXT DEFAULT NULL,
|
||||||
`start_time` DATETIME NOT NULL COMMENT '开始时间',
|
start_time TIMESTAMPTZ NOT NULL,
|
||||||
`end_time` DATETIME DEFAULT NULL COMMENT '结束时间',
|
end_time TIMESTAMPTZ DEFAULT NULL,
|
||||||
`execute_duration` INT DEFAULT NULL COMMENT '执行时长(毫秒)',
|
execute_duration INT DEFAULT NULL,
|
||||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
create_time TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
update_time TIMESTAMPTZ DEFAULT NULL,
|
||||||
`delete_time` DATETIME DEFAULT NULL COMMENT '删除时间',
|
delete_time TIMESTAMPTZ DEFAULT NULL,
|
||||||
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除(0:否 1:是)',
|
deleted SMALLINT NOT NULL DEFAULT 0, -- 是否删除(0:否 1:是)
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (id)
|
||||||
KEY `idx_task_id` (`task_id`),
|
);
|
||||||
KEY `idx_task_name` (`task_name`),
|
|
||||||
KEY `idx_execute_status` (`execute_status`),
|
CREATE INDEX IF NOT EXISTS idx_task_id ON crontab.tb_crontab_log(task_id);
|
||||||
KEY `idx_start_time` (`start_time`),
|
CREATE INDEX IF NOT EXISTS idx_log_task_name ON crontab.tb_crontab_log(task_name);
|
||||||
KEY `idx_deleted` (`deleted`)
|
CREATE INDEX IF NOT EXISTS idx_execute_status ON crontab.tb_crontab_log(execute_status);
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='定时任务执行日志表';
|
CREATE INDEX IF NOT EXISTS idx_start_time ON crontab.tb_crontab_log(start_time);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_log_deleted ON crontab.tb_crontab_log(deleted);
|
||||||
|
|
||||||
|
COMMENT ON TABLE crontab.tb_crontab_log IS '定时任务执行日志表';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_log.id IS '主键ID';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_log.task_id IS '任务ID';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_log.task_name IS '任务名称';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_log.task_group IS '任务分组';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_log.bean_name IS 'Bean名称';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_log.method_name IS '方法名称';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_log.method_params IS '方法参数';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_log.execute_status IS '执行状态(0:失败 1:成功)';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_log.execute_message IS '执行结果信息';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_log.exception_info IS '异常信息';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_log.start_time IS '开始时间';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_log.end_time IS '结束时间';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_log.execute_duration IS '执行时长(毫秒)';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_log.create_time IS '创建时间';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_log.update_time IS '更新时间';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_log.delete_time IS '删除时间';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_log.deleted IS '是否删除(0:否 1:是)';
|
||||||
|
|
||||||
-- ====================================================
|
-- ====================================================
|
||||||
-- 定时任务元数据表(存储爬虫任务的元数据配置)
|
-- 定时任务元数据表(存储爬虫任务的元数据配置)
|
||||||
-- ====================================================
|
-- ====================================================
|
||||||
DROP TABLE IF EXISTS `tb_crontab_task_meta`;
|
DROP TABLE IF EXISTS crontab.tb_crontab_task_meta CASCADE;
|
||||||
CREATE TABLE `tb_crontab_task_meta` (
|
CREATE TABLE crontab.tb_crontab_task_meta (
|
||||||
`id` VARCHAR(64) NOT NULL COMMENT '主键ID',
|
id VARCHAR(64) NOT NULL,
|
||||||
`meta_id` VARCHAR(64) NOT NULL COMMENT '元数据ID',
|
meta_id VARCHAR(64) NOT NULL,
|
||||||
`name` VARCHAR(100) NOT NULL COMMENT '任务名称',
|
name VARCHAR(100) NOT NULL,
|
||||||
`description` VARCHAR(500) DEFAULT NULL COMMENT '任务描述',
|
description VARCHAR(500) DEFAULT NULL,
|
||||||
`category` VARCHAR(50) NOT NULL COMMENT '任务分类(如:人民日报新闻爬取)',
|
category VARCHAR(50) NOT NULL,
|
||||||
`bean_name` VARCHAR(100) NOT NULL COMMENT 'Bean名称(执行器类名)',
|
bean_name VARCHAR(100) NOT NULL,
|
||||||
`method_name` VARCHAR(100) NOT NULL COMMENT '执行方法名',
|
method_name VARCHAR(100) NOT NULL,
|
||||||
`script_path` VARCHAR(255) DEFAULT NULL COMMENT 'Python脚本路径(相对于basePath)',
|
script_path VARCHAR(255) DEFAULT NULL,
|
||||||
`param_schema` TEXT DEFAULT NULL COMMENT '参数模板(JSON格式,定义参数名、类型、描述、默认值等)',
|
param_schema TEXT DEFAULT NULL,
|
||||||
`auto_publish` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否自动发布',
|
auto_publish SMALLINT NOT NULL DEFAULT 0, -- 是否自动发布
|
||||||
`sort_order` INT DEFAULT 0 COMMENT '排序号',
|
sort_order INT DEFAULT 0,
|
||||||
`creator` VARCHAR(64) DEFAULT NULL COMMENT '创建者',
|
creator VARCHAR(64) DEFAULT NULL,
|
||||||
`updater` VARCHAR(64) DEFAULT NULL COMMENT '更新者',
|
updater VARCHAR(64) DEFAULT NULL,
|
||||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
create_time TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
update_time TIMESTAMPTZ DEFAULT NULL,
|
||||||
`delete_time` DATETIME DEFAULT NULL COMMENT '删除时间',
|
delete_time TIMESTAMPTZ DEFAULT NULL,
|
||||||
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除(0:否 1:是)',
|
deleted SMALLINT NOT NULL DEFAULT 0, -- 是否删除(0:否 1:是)
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (id),
|
||||||
UNIQUE KEY `uk_meta_id` (`meta_id`),
|
UNIQUE (meta_id)
|
||||||
KEY `idx_category` (`category`),
|
);
|
||||||
KEY `idx_deleted` (`deleted`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='定时任务元数据表';
|
CREATE INDEX IF NOT EXISTS idx_category ON crontab.tb_crontab_task_meta(category);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_meta_deleted ON crontab.tb_crontab_task_meta(deleted);
|
||||||
|
|
||||||
|
COMMENT ON TABLE crontab.tb_crontab_task_meta IS '定时任务元数据表';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task_meta.id IS '主键ID';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task_meta.meta_id IS '元数据ID';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task_meta.name IS '任务名称';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task_meta.description IS '任务描述';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task_meta.category IS '任务分类(如:人民日报新闻爬取)';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task_meta.bean_name IS 'Bean名称(执行器类名)';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task_meta.method_name IS '执行方法名';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task_meta.script_path IS 'Python脚本路径(相对于basePath)';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task_meta.param_schema IS '参数模板(JSON格式,定义参数名、类型、描述、默认值等)';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task_meta.auto_publish IS '是否自动发布';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task_meta.sort_order IS '排序号';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task_meta.creator IS '创建者';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task_meta.updater IS '更新者';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task_meta.create_time IS '创建时间';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task_meta.update_time IS '更新时间';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task_meta.delete_time IS '删除时间';
|
||||||
|
COMMENT ON COLUMN crontab.tb_crontab_task_meta.deleted IS '是否删除(0:否 1:是)';
|
||||||
@@ -73,7 +73,7 @@ CREATE TABLE knowledge.tb_knowledge_document (
|
|||||||
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间
|
||||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||||
PRIMARY KEY (doc_id),
|
PRIMARY KEY (doc_id),
|
||||||
UNIQUE (optsn),
|
UNIQUE (optsn)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX idx_doc_kb ON knowledge.tb_knowledge_document(knowledge_id) WHERE deleted = false;
|
CREATE INDEX idx_doc_kb ON knowledge.tb_knowledge_document(knowledge_id) WHERE deleted = false;
|
||||||
@@ -112,7 +112,7 @@ CREATE TABLE knowledge.tb_knowledge_chunk (
|
|||||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间
|
||||||
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
|
||||||
PRIMARY KEY (chunk_id),
|
PRIMARY KEY (chunk_id),
|
||||||
UNIQUE (optsn),
|
UNIQUE (optsn)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX idx_chunk_doc ON knowledge.tb_knowledge_chunk(doc_id) WHERE deleted = false;
|
CREATE INDEX idx_chunk_doc ON knowledge.tb_knowledge_chunk(doc_id) WHERE deleted = false;
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ DROP TABLE IF EXISTS message.tb_message_channel CASCADE;
|
|||||||
CREATE TABLE message.tb_message_channel (
|
CREATE TABLE message.tb_message_channel (
|
||||||
optsn VARCHAR(50) NOT NULL, -- 流水号
|
optsn VARCHAR(50) NOT NULL, -- 流水号
|
||||||
channel_id VARCHAR(50) NOT NULL, -- 渠道ID
|
channel_id VARCHAR(50) NOT NULL, -- 渠道ID
|
||||||
channel_code VARCHAR(20) NOT NULL, -- 渠道编码:app/sms/email/wechat/dingtalk等
|
channel_code VARCHAR(50) NOT NULL, -- 渠道编码:app/sms/email/wechat/dingtalk等
|
||||||
channel_name VARCHAR(100) NOT NULL, -- 渠道名称
|
channel_name VARCHAR(100) NOT NULL, -- 渠道名称
|
||||||
channel_desc VARCHAR(255) DEFAULT NULL, -- 渠道描述
|
channel_desc VARCHAR(255) DEFAULT NULL, -- 渠道描述
|
||||||
config JSON DEFAULT NULL, -- 渠道配置(如API密钥、服务器地址等)
|
config JSON DEFAULT NULL, -- 渠道配置(如API密钥、服务器地址等)
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ CREATE TABLE sys.tb_sys_user (
|
|||||||
usercode VARCHAR(100) DEFAULT NULL, -- 用户code。sso同步数据获取
|
usercode VARCHAR(100) DEFAULT NULL, -- 用户code。sso同步数据获取
|
||||||
password VARCHAR(128) NOT NULL, -- 密码(建议存储 bcrypt/argon2 哈希)
|
password VARCHAR(128) NOT NULL, -- 密码(建议存储 bcrypt/argon2 哈希)
|
||||||
email VARCHAR(100), -- 电子邮件
|
email VARCHAR(100), -- 电子邮件
|
||||||
phone VARCHAR(20), -- 电话号码
|
phone VARCHAR(500), -- 电话号码
|
||||||
|
phone_hash VARCHAR(200), -- 电话hash
|
||||||
wechat_id VARCHAR(50), -- 微信ID
|
wechat_id VARCHAR(50), -- 微信ID
|
||||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间(使用带时区时间)
|
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间(使用带时区时间)
|
||||||
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间(由触发器维护)
|
update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间(由触发器维护)
|
||||||
|
|||||||
@@ -28,10 +28,23 @@ INSERT INTO config.tb_sys_config (
|
|||||||
('CFG-0303', 'cfg_storage_base', 'storage.basePath', '存储路径', '/data/urban-lifeline', 'String', 'input', '本地存储基路径', NULL, NULL, 'storage', 'mod_file', 30, 0, '当 backend=local', 'system', NULL, NULL, now(), NULL, NULL, false),
|
('CFG-0303', 'cfg_storage_base', 'storage.basePath', '存储路径', '/data/urban-lifeline', 'String', 'input', '本地存储基路径', NULL, NULL, 'storage', 'mod_file', 30, 0, '当 backend=local', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||||
|
|
||||||
-- 通知(邮件/SMS)
|
-- 通知(邮件/SMS)
|
||||||
('CFG-0401', 'cfg_mail_host', 'mail.smtp.host', 'SMTP主机', '', 'String', 'input', 'SMTP主机', NULL, NULL, 'notify', 'mod_message', 10, 1, '留空为未配置', 'system', NULL, NULL, now(), NULL, NULL, false),
|
-- 邮件配置
|
||||||
('CFG-0402', 'cfg_mail_port', 'mail.smtp.port', 'SMTP端口', '465', 'INTEGER', 'input', 'SMTP端口', NULL, NULL, 'notify', 'mod_message', 20, 1, 'SSL常用465', 'system', NULL, NULL, now(), NULL, NULL, false),
|
('CFG-0401', 'cfg_mail_host', 'email.host', 'SMTP服务器地址', 'smtp.qq.com', 'String', 'input', 'SMTP服务器地址', NULL, NULL, 'notify', 'mod_message', 10, 1, '邮件发送服务器地址', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||||
('CFG-0403', 'cfg_mail_from', 'mail.from', '发件人邮箱', '', 'String', 'input', '发件人邮箱', NULL, NULL, 'notify', 'mod_message', 30, 1, '如 no-reply@x.com', 'system', NULL, NULL, now(), NULL, NULL, false),
|
('CFG-0402', 'cfg_mail_port', 'email.port', 'SMTP端口', '587', 'INTEGER', 'input', 'SMTP服务器端口', NULL, NULL, 'notify', 'mod_message', 20, 1, '常用:25/465/587', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||||
('CFG-0411', 'cfg_sms_provider', 'sms.provider', '短信服务商', '', 'String', 'select', '短信服务商', NULL, '["aliyun", "tencent"]'::json, 'notify', 'mod_message', 40, 1, '如 aliyun/tencent', 'system', NULL, NULL, now(), NULL, NULL, false),
|
('CFG-0403', 'cfg_mail_username', 'email.username', '发件人邮箱', '3223905473@qq.com', 'String', 'input', '发件人邮箱地址', NULL, NULL, 'notify', 'mod_message', 30, 1, '用于发送邮件的邮箱账号', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||||
|
('CFG-0404', 'cfg_mail_password', 'email.password', '邮箱授权码', 'xmdmxvtjumxocicc', 'String', 'password', '邮箱授权码/密码', NULL, NULL, 'notify', 'mod_message', 40, 1, '邮箱的授权码或密码', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||||
|
('CFG-0405', 'cfg_mail_fromname', 'email.fromName', '发件人名称', 'urban-lifeline平台', 'String', 'input', '发件人显示名称', NULL, NULL, 'notify', 'mod_message', 50, 1, '邮件中显示的发件人名称', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||||
|
('CFG-0406', 'cfg_mail_ssl', 'email.ssl.enable', '启用SSL', 'true', 'BOOLEAN', 'switch', '是否启用SSL', NULL, NULL, 'notify', 'mod_message', 60, 1, 'SSL加密连接', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||||
|
('CFG-0407', 'cfg_mail_timeout', 'email.timeout', '连接超时时间', '30000', 'INTEGER', 'input', '连接超时时间(毫秒)', NULL, NULL, 'notify', 'mod_message', 70, 1, 'SMTP连接超时时间(5000-60000)', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||||
|
|
||||||
|
-- 短信配置
|
||||||
|
('CFG-0411', 'cfg_sms_provider', 'sms.provider', '短信服务商', 'aliyun', 'String', 'select', '短信服务提供商', NULL, '["aliyun", "tencent"]'::json, 'notify', 'mod_message', 80, 1, '短信服务提供商类型', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||||
|
('CFG-0412', 'cfg_sms_keyid', 'sms.accessKeyId', 'AccessKey ID', 'LTAI5t68do3qVXx5Rufugt3X', 'String', 'input', '短信服务AccessKey ID', NULL, NULL, 'notify', 'mod_message', 90, 1, '云服务商的AccessKey ID', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||||
|
('CFG-0413', 'cfg_sms_secret', 'sms.accessKeySecret', 'AccessKey Secret', '2vD9ToIff49Vph4JQXsn0Cy8nXQfzA', 'String', 'password', '短信服务AccessKey Secret', NULL, NULL, 'notify', 'mod_message', 100, 1, '云服务商的AccessKey Secret', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||||
|
('CFG-0414', 'cfg_sms_sign', 'sms.signName', '短信签名', 'urban-lifeline', 'String', 'input', '短信签名', NULL, NULL, 'notify', 'mod_message', 110, 1, '发送短信使用的签名', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||||
|
('CFG-0415', 'cfg_sms_tpl_login', 'sms.templateCode.login', '登录验证码模板', 'SMS_491985030', 'String', 'input', '登录验证码模板编码', NULL, NULL, 'notify', 'mod_message', 120, 1, '登录验证码短信模板', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||||
|
('CFG-0416', 'cfg_sms_tpl_register', 'sms.templateCode.register','注册验证码模板', 'SMS_491985030', 'String', 'input', '注册验证码模板编码', NULL, NULL, 'notify', 'mod_message', 130, 1, '注册验证码短信模板', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||||
|
('CFG-0417', 'cfg_sms_timeout', 'sms.timeout', '请求超时时间', '30000', 'INTEGER', 'input', '请求超时时间(毫秒)', NULL, NULL, 'notify', 'mod_message', 140, 1, 'API请求超时时间(5000-60000)', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||||
|
|
||||||
-- 日志与审计
|
-- 日志与审计
|
||||||
('CFG-0501', 'cfg_log_level', 'log.level', '日志级别', 'INFO', 'String', 'select', '系统日志级别', NULL, '["DEBUG", "INFO", "WARN", "ERROR"]'::json, 'log', 'mod_system', 10, 0, 'DEBUG/INFO/WARN/ERROR', 'system', NULL, NULL, now(), NULL, NULL, false),
|
('CFG-0501', 'cfg_log_level', 'log.level', '日志级别', 'INFO', 'String', 'select', '系统日志级别', NULL, '["DEBUG", "INFO", "WARN", "ERROR"]'::json, 'log', 'mod_system', 10, 0, 'DEBUG/INFO/WARN/ERROR', 'system', NULL, NULL, now(), NULL, NULL, false),
|
||||||
|
|||||||
1
urbanLifelineServ/.pids/auth.pid
Normal file
1
urbanLifelineServ/.pids/auth.pid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
1028
|
||||||
1
urbanLifelineServ/.pids/gateway.pid
Normal file
1
urbanLifelineServ/.pids/gateway.pid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
1003
|
||||||
1
urbanLifelineServ/.pids/system.pid
Normal file
1
urbanLifelineServ/.pids/system.pid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
1053
|
||||||
163
urbanLifelineServ/.vscode/launch.json
vendored
163
urbanLifelineServ/.vscode/launch.json
vendored
@@ -3,21 +3,162 @@
|
|||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"type": "java",
|
"type": "java",
|
||||||
"name": "SystemApp",
|
"name": "Gateway (8180)",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"mainClass": "org.xyzh.SystemApp",
|
"mainClass": "org.xyzh.gateway.GatewayApplication",
|
||||||
|
"projectName": "gateway",
|
||||||
|
"cwd": "${workspaceFolder}/gateway",
|
||||||
|
"args": ["--spring.profiles.active=dev"],
|
||||||
|
"vmArgs": ["-Dserver.port=8180"],
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "Auth (8181)",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "org.xyzh.auth.AuthApp",
|
||||||
|
"projectName": "auth",
|
||||||
|
"cwd": "${workspaceFolder}/auth",
|
||||||
|
"args": ["--spring.profiles.active=dev"],
|
||||||
|
"vmArgs": ["-Dserver.port=8181"],
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "System (8182)",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "org.xyzh.system.SystemApp",
|
||||||
"projectName": "system",
|
"projectName": "system",
|
||||||
"cwd": "${workspaceFolder}/system",
|
"cwd": "${workspaceFolder}/system",
|
||||||
"args": [
|
"args": ["--spring.profiles.active=dev"],
|
||||||
"--spring.profiles.active=dev"
|
"vmArgs": ["-Dserver.port=8182"],
|
||||||
],
|
|
||||||
"vmArgs": [
|
|
||||||
"-Dspring.profiles.active=dev"
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"SPRING_PROFILES_ACTIVE": "dev"
|
|
||||||
},
|
|
||||||
"console": "integratedTerminal"
|
"console": "integratedTerminal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "Log (8183)",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "org.xyzh.log.LogApp",
|
||||||
|
"projectName": "log",
|
||||||
|
"cwd": "${workspaceFolder}/log",
|
||||||
|
"args": ["--spring.profiles.active=dev"],
|
||||||
|
"vmArgs": ["-Dserver.port=8183"],
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "File (8184)",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "org.xyzh.file.FileApp",
|
||||||
|
"projectName": "file",
|
||||||
|
"cwd": "${workspaceFolder}/file",
|
||||||
|
"args": ["--spring.profiles.active=dev"],
|
||||||
|
"vmArgs": ["-Dserver.port=8184"],
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "Message (8185)",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "org.xyzh.message.MessageApp",
|
||||||
|
"projectName": "message",
|
||||||
|
"cwd": "${workspaceFolder}/message",
|
||||||
|
"args": ["--spring.profiles.active=dev"],
|
||||||
|
"vmArgs": ["-Dserver.port=8185"],
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "Bidding (8186)",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "org.xyzh.bidding.BiddingApp",
|
||||||
|
"projectName": "bidding",
|
||||||
|
"cwd": "${workspaceFolder}/bidding",
|
||||||
|
"args": ["--spring.profiles.active=dev"],
|
||||||
|
"vmArgs": ["-Dserver.port=8186"],
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "Platform (8187)",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "org.xyzh.platform.PlatformApp",
|
||||||
|
"projectName": "platform",
|
||||||
|
"cwd": "${workspaceFolder}/platform",
|
||||||
|
"args": ["--spring.profiles.active=dev"],
|
||||||
|
"vmArgs": ["-Dserver.port=8187"],
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "Workcase (8188)",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "org.xyzh.workcase.WorkcaseApp",
|
||||||
|
"projectName": "workcase",
|
||||||
|
"cwd": "${workspaceFolder}/workcase",
|
||||||
|
"args": ["--spring.profiles.active=dev"],
|
||||||
|
"vmArgs": ["-Dserver.port=8188"],
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "Crontab (8189)",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "org.xyzh.crontab.CrontabApp",
|
||||||
|
"projectName": "crontab",
|
||||||
|
"cwd": "${workspaceFolder}/crontab",
|
||||||
|
"args": ["--spring.profiles.active=dev"],
|
||||||
|
"vmArgs": ["-Dserver.port=8189"],
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "Agent (8190)",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "org.xyzh.agent.AgentApp",
|
||||||
|
"projectName": "agent",
|
||||||
|
"cwd": "${workspaceFolder}/agent",
|
||||||
|
"args": ["--spring.profiles.active=dev"],
|
||||||
|
"vmArgs": ["-Dserver.port=8190"],
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compounds": [
|
||||||
|
{
|
||||||
|
"name": "Core Services (Gateway + Auth + System)",
|
||||||
|
"configurations": [
|
||||||
|
"Gateway (8180)",
|
||||||
|
"Auth (8181)",
|
||||||
|
"System (8182)"
|
||||||
|
],
|
||||||
|
"stopAll": true,
|
||||||
|
"presentation": {
|
||||||
|
"hidden": false,
|
||||||
|
"group": "服务组合",
|
||||||
|
"order": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "All Services",
|
||||||
|
"configurations": [
|
||||||
|
"Gateway (8180)",
|
||||||
|
"Auth (8181)",
|
||||||
|
"System (8182)",
|
||||||
|
"Log (8183)",
|
||||||
|
"File (8184)",
|
||||||
|
"Message (8185)",
|
||||||
|
"Bidding (8186)",
|
||||||
|
"Platform (8187)",
|
||||||
|
"Workcase (8188)",
|
||||||
|
"Crontab (8189)",
|
||||||
|
"Agent (8190)"
|
||||||
|
],
|
||||||
|
"stopAll": true,
|
||||||
|
"presentation": {
|
||||||
|
"hidden": false,
|
||||||
|
"group": "服务组合",
|
||||||
|
"order": 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
23
urbanLifelineServ/.vscode/settings.json
vendored
23
urbanLifelineServ/.vscode/settings.json
vendored
@@ -4,12 +4,31 @@
|
|||||||
"maven.view": "hierarchical",
|
"maven.view": "hierarchical",
|
||||||
"tabSize": 4,
|
"tabSize": 4,
|
||||||
"java.debug.settings.onBuildFailureProceed": true,
|
"java.debug.settings.onBuildFailureProceed": true,
|
||||||
"java.configuration.maven.userSettings": null,
|
"java.configuration.maven.userSettings": "",
|
||||||
"java.import.maven.enabled": true,
|
"java.import.maven.enabled": true,
|
||||||
"java.project.referencedLibraries": [
|
"java.project.referencedLibraries": [
|
||||||
"**/*.jar"
|
"**/*.jar"
|
||||||
],
|
],
|
||||||
"maven.terminal.useJavaHome": true,
|
"maven.terminal.useJavaHome": true,
|
||||||
"java.configuration.runtimes": [],
|
"java.configuration.runtimes": [],
|
||||||
"Codegeex.RepoIndex": true
|
"Codegeex.RepoIndex": true,
|
||||||
|
"terminal.integrated.defaultProfile.windows": "Command Prompt",
|
||||||
|
"terminal.integrated.profiles.windows": {
|
||||||
|
"PowerShell": {
|
||||||
|
"source": "PowerShell",
|
||||||
|
"args": ["-NoExit", "-Command", "chcp 65001"]
|
||||||
|
},
|
||||||
|
"Command Prompt": {
|
||||||
|
"path": "cmd.exe",
|
||||||
|
"args": ["/k", "chcp 65001"]
|
||||||
|
},
|
||||||
|
"Git Bash": {
|
||||||
|
"path": "F:\\Environment\\Git\\bin\\bash.exe",
|
||||||
|
"args": ["-i"],
|
||||||
|
"env": {
|
||||||
|
"LANG": "zh_CN.UTF-8",
|
||||||
|
"LC_ALL": "zh_CN.UTF-8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
247
urbanLifelineServ/Makefile
Normal file
247
urbanLifelineServ/Makefile
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
# ================================================
|
||||||
|
# Urban Lifeline 微服务管理 Makefile
|
||||||
|
# ================================================
|
||||||
|
|
||||||
|
# 项目根目录
|
||||||
|
PROJECT_ROOT := $(shell pwd)
|
||||||
|
|
||||||
|
# Maven 命令
|
||||||
|
MAVEN := mvn
|
||||||
|
MAVEN_OPTS := -Dmaven.test.skip=true
|
||||||
|
|
||||||
|
# 服务列表
|
||||||
|
SERVICES := gateway auth system log file message bidding platform workcase crontab agent
|
||||||
|
|
||||||
|
# 服务端口映射
|
||||||
|
PORT_gateway := 8080
|
||||||
|
PORT_auth := 8081
|
||||||
|
PORT_system := 8082
|
||||||
|
PORT_log := 8083
|
||||||
|
PORT_file := 8084
|
||||||
|
PORT_message := 8085
|
||||||
|
PORT_bidding := 8086
|
||||||
|
PORT_platform := 8087
|
||||||
|
PORT_workcase := 8088
|
||||||
|
PORT_crontab := 8089
|
||||||
|
PORT_agent := 8090
|
||||||
|
|
||||||
|
# 服务主类映射
|
||||||
|
MAINCLASS_gateway := org.xyzh.gateway.GatewayApplication
|
||||||
|
MAINCLASS_auth := org.xyzh.auth.AuthApp
|
||||||
|
MAINCLASS_system := org.xyzh.system.SystemApp
|
||||||
|
MAINCLASS_log := org.xyzh.log.LogApp
|
||||||
|
MAINCLASS_file := org.xyzh.file.FileApp
|
||||||
|
MAINCLASS_message := org.xyzh.message.MessageApp
|
||||||
|
MAINCLASS_bidding := org.xyzh.bidding.BiddingApp
|
||||||
|
MAINCLASS_platform := org.xyzh.platform.PlatformApp
|
||||||
|
MAINCLASS_workcase := org.xyzh.workcase.WorkcaseApp
|
||||||
|
MAINCLASS_crontab := org.xyzh.crontab.CrontabApp
|
||||||
|
MAINCLASS_agent := org.xyzh.agent.AgentApp
|
||||||
|
|
||||||
|
# PID 文件目录
|
||||||
|
PID_DIR := $(PROJECT_ROOT)/.pids
|
||||||
|
LOG_DIR := $(PROJECT_ROOT)/.logs
|
||||||
|
|
||||||
|
# 颜色定义
|
||||||
|
COLOR_RESET := \033[0m
|
||||||
|
COLOR_GREEN := \033[0;32m
|
||||||
|
COLOR_YELLOW := \033[0;33m
|
||||||
|
COLOR_BLUE := \033[0;34m
|
||||||
|
COLOR_RED := \033[0;31m
|
||||||
|
|
||||||
|
# ================================================
|
||||||
|
# 帮助信息
|
||||||
|
# ================================================
|
||||||
|
.PHONY: help
|
||||||
|
help:
|
||||||
|
@echo "$(COLOR_BLUE)=============================================$(COLOR_RESET)"
|
||||||
|
@echo "$(COLOR_BLUE) Urban Lifeline 微服务管理工具$(COLOR_RESET)"
|
||||||
|
@echo "$(COLOR_BLUE)=============================================$(COLOR_RESET)"
|
||||||
|
@echo ""
|
||||||
|
@echo "$(COLOR_GREEN)构建命令:$(COLOR_RESET)"
|
||||||
|
@echo " make build - 构建所有服务"
|
||||||
|
@echo " make build-<service> - 构建指定服务 (如: make build-system)"
|
||||||
|
@echo " make clean - 清理所有服务"
|
||||||
|
@echo ""
|
||||||
|
@echo "$(COLOR_GREEN)启动命令:$(COLOR_RESET)"
|
||||||
|
@echo " make start - 启动所有服务"
|
||||||
|
@echo " make start-<service> - 启动指定服务 (如: make start-system)"
|
||||||
|
@echo " make start-core - 启动核心服务 (gateway, auth, system)"
|
||||||
|
@echo ""
|
||||||
|
@echo "$(COLOR_GREEN)停止命令:$(COLOR_RESET)"
|
||||||
|
@echo " make stop - 停止所有服务"
|
||||||
|
@echo " make stop-<service> - 停止指定服务 (如: make stop-system)"
|
||||||
|
@echo ""
|
||||||
|
@echo "$(COLOR_GREEN)重启命令:$(COLOR_RESET)"
|
||||||
|
@echo " make restart - 重启所有服务"
|
||||||
|
@echo " make restart-<service> - 重启指定服务 (如: make restart-system)"
|
||||||
|
@echo ""
|
||||||
|
@echo "$(COLOR_GREEN)状态查看:$(COLOR_RESET)"
|
||||||
|
@echo " make status - 查看所有服务状态"
|
||||||
|
@echo " make status-<service> - 查看指定服务状态"
|
||||||
|
@echo " make logs-<service> - 查看指定服务日志"
|
||||||
|
@echo ""
|
||||||
|
@echo "$(COLOR_GREEN)可用服务:$(COLOR_RESET)"
|
||||||
|
@echo " $(SERVICES)"
|
||||||
|
@echo ""
|
||||||
|
|
||||||
|
# ================================================
|
||||||
|
# 初始化目录
|
||||||
|
# ================================================
|
||||||
|
.PHONY: init-dirs
|
||||||
|
init-dirs:
|
||||||
|
@mkdir -p $(PID_DIR)
|
||||||
|
@mkdir -p $(LOG_DIR)
|
||||||
|
|
||||||
|
# ================================================
|
||||||
|
# 构建相关
|
||||||
|
# ================================================
|
||||||
|
.PHONY: build
|
||||||
|
build: init-dirs
|
||||||
|
@echo "$(COLOR_YELLOW)开始构建所有服务...$(COLOR_RESET)"
|
||||||
|
@$(MAVEN) clean install $(MAVEN_OPTS)
|
||||||
|
@echo "$(COLOR_GREEN)✓ 所有服务构建完成$(COLOR_RESET)"
|
||||||
|
|
||||||
|
.PHONY: $(addprefix build-,$(SERVICES))
|
||||||
|
$(addprefix build-,$(SERVICES)): build-%:
|
||||||
|
@echo "$(COLOR_YELLOW)开始构建 $* 服务...$(COLOR_RESET)"
|
||||||
|
@cd $(PROJECT_ROOT)/$* && $(MAVEN) clean install $(MAVEN_OPTS)
|
||||||
|
@echo "$(COLOR_GREEN)✓ $* 服务构建完成$(COLOR_RESET)"
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
@echo "$(COLOR_YELLOW)开始清理所有服务...$(COLOR_RESET)"
|
||||||
|
@$(MAVEN) clean
|
||||||
|
@rm -rf $(PID_DIR)
|
||||||
|
@rm -rf $(LOG_DIR)
|
||||||
|
@echo "$(COLOR_GREEN)✓ 清理完成$(COLOR_RESET)"
|
||||||
|
|
||||||
|
# ================================================
|
||||||
|
# 启动服务
|
||||||
|
# ================================================
|
||||||
|
.PHONY: start
|
||||||
|
start: init-dirs $(addprefix start-,$(SERVICES))
|
||||||
|
@echo "$(COLOR_GREEN)✓ 所有服务启动完成$(COLOR_RESET)"
|
||||||
|
|
||||||
|
.PHONY: start-core
|
||||||
|
start-core: init-dirs start-gateway start-auth start-system
|
||||||
|
@echo "$(COLOR_GREEN)✓ 核心服务启动完成$(COLOR_RESET)"
|
||||||
|
|
||||||
|
.PHONY: $(addprefix start-,$(SERVICES))
|
||||||
|
$(addprefix start-,$(SERVICES)): start-%:
|
||||||
|
@if [ -f "$(PID_DIR)/$*.pid" ] && kill -0 $$(cat $(PID_DIR)/$*.pid) 2>/dev/null; then \
|
||||||
|
echo "$(COLOR_YELLOW)⚠ $* 服务已在运行中 (PID: $$(cat $(PID_DIR)/$*.pid))$(COLOR_RESET)"; \
|
||||||
|
else \
|
||||||
|
echo "$(COLOR_BLUE)启动 $* 服务...$(COLOR_RESET)"; \
|
||||||
|
nohup $(MAVEN) -pl $* spring-boot:run > $(LOG_DIR)/$*.log 2>&1 & \
|
||||||
|
echo $$! > $(PID_DIR)/$*.pid; \
|
||||||
|
sleep 2; \
|
||||||
|
if kill -0 $$(cat $(PID_DIR)/$*.pid) 2>/dev/null; then \
|
||||||
|
echo "$(COLOR_GREEN)✓ $* 服务启动成功 (PID: $$(cat $(PID_DIR)/$*.pid), Port: $(PORT_$*))$(COLOR_RESET)"; \
|
||||||
|
else \
|
||||||
|
echo "$(COLOR_RED)✗ $* 服务启动失败$(COLOR_RESET)"; \
|
||||||
|
rm -f $(PID_DIR)/$*.pid; \
|
||||||
|
fi; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ================================================
|
||||||
|
# 停止服务
|
||||||
|
# ================================================
|
||||||
|
.PHONY: stop
|
||||||
|
stop: $(addprefix stop-,$(SERVICES))
|
||||||
|
@echo "$(COLOR_GREEN)✓ 所有服务停止完成$(COLOR_RESET)"
|
||||||
|
|
||||||
|
.PHONY: $(addprefix stop-,$(SERVICES))
|
||||||
|
$(addprefix stop-,$(SERVICES)): stop-%:
|
||||||
|
@if [ -f "$(PID_DIR)/$*.pid" ]; then \
|
||||||
|
PID=$$(cat $(PID_DIR)/$*.pid); \
|
||||||
|
if kill -0 $$PID 2>/dev/null; then \
|
||||||
|
echo "$(COLOR_YELLOW)停止 $* 服务 (PID: $$PID)...$(COLOR_RESET)"; \
|
||||||
|
kill $$PID; \
|
||||||
|
sleep 2; \
|
||||||
|
if kill -0 $$PID 2>/dev/null; then \
|
||||||
|
echo "$(COLOR_RED)强制停止 $* 服务...$(COLOR_RESET)"; \
|
||||||
|
kill -9 $$PID; \
|
||||||
|
fi; \
|
||||||
|
rm -f $(PID_DIR)/$*.pid; \
|
||||||
|
echo "$(COLOR_GREEN)✓ $* 服务已停止$(COLOR_RESET)"; \
|
||||||
|
else \
|
||||||
|
echo "$(COLOR_YELLOW)⚠ $* 服务未运行$(COLOR_RESET)"; \
|
||||||
|
rm -f $(PID_DIR)/$*.pid; \
|
||||||
|
fi; \
|
||||||
|
else \
|
||||||
|
echo "$(COLOR_YELLOW)⚠ $* 服务未运行$(COLOR_RESET)"; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ================================================
|
||||||
|
# 重启服务
|
||||||
|
# ================================================
|
||||||
|
.PHONY: restart
|
||||||
|
restart: stop start
|
||||||
|
@echo "$(COLOR_GREEN)✓ 所有服务重启完成$(COLOR_RESET)"
|
||||||
|
|
||||||
|
.PHONY: $(addprefix restart-,$(SERVICES))
|
||||||
|
$(addprefix restart-,$(SERVICES)): restart-%: stop-% start-%
|
||||||
|
@echo "$(COLOR_GREEN)✓ $* 服务重启完成$(COLOR_RESET)"
|
||||||
|
|
||||||
|
# ================================================
|
||||||
|
# 查看状态
|
||||||
|
# ================================================
|
||||||
|
.PHONY: status
|
||||||
|
status:
|
||||||
|
@echo "$(COLOR_BLUE)=============================================$(COLOR_RESET)"
|
||||||
|
@echo "$(COLOR_BLUE) 服务运行状态$(COLOR_RESET)"
|
||||||
|
@echo "$(COLOR_BLUE)=============================================$(COLOR_RESET)"
|
||||||
|
@for service in $(SERVICES); do \
|
||||||
|
printf "%-15s" "$$service:"; \
|
||||||
|
if [ -f "$(PID_DIR)/$$service.pid" ]; then \
|
||||||
|
PID=$$(cat $(PID_DIR)/$$service.pid); \
|
||||||
|
if kill -0 $$PID 2>/dev/null; then \
|
||||||
|
echo "$(COLOR_GREEN)✓ Running (PID: $$PID)$(COLOR_RESET)"; \
|
||||||
|
else \
|
||||||
|
echo "$(COLOR_RED)✗ Stopped$(COLOR_RESET)"; \
|
||||||
|
rm -f $(PID_DIR)/$$service.pid; \
|
||||||
|
fi; \
|
||||||
|
else \
|
||||||
|
echo "$(COLOR_YELLOW)○ Not Started$(COLOR_RESET)"; \
|
||||||
|
fi; \
|
||||||
|
done
|
||||||
|
@echo ""
|
||||||
|
|
||||||
|
.PHONY: $(addprefix status-,$(SERVICES))
|
||||||
|
$(addprefix status-,$(SERVICES)): status-%:
|
||||||
|
@if [ -f "$(PID_DIR)/$*.pid" ]; then \
|
||||||
|
PID=$$(cat $(PID_DIR)/$*.pid); \
|
||||||
|
if kill -0 $$PID 2>/dev/null; then \
|
||||||
|
echo "$(COLOR_GREEN)✓ $* 服务运行中 (PID: $$PID, Port: $(PORT_$*))$(COLOR_RESET)"; \
|
||||||
|
else \
|
||||||
|
echo "$(COLOR_RED)✗ $* 服务已停止$(COLOR_RESET)"; \
|
||||||
|
fi; \
|
||||||
|
else \
|
||||||
|
echo "$(COLOR_YELLOW)○ $* 服务未启动$(COLOR_RESET)"; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ================================================
|
||||||
|
# 查看日志
|
||||||
|
# ================================================
|
||||||
|
.PHONY: $(addprefix logs-,$(SERVICES))
|
||||||
|
$(addprefix logs-,$(SERVICES)): logs-%:
|
||||||
|
@if [ -f "$(LOG_DIR)/$*.log" ]; then \
|
||||||
|
tail -f $(LOG_DIR)/$*.log; \
|
||||||
|
else \
|
||||||
|
echo "$(COLOR_YELLOW)⚠ $* 服务日志文件不存在$(COLOR_RESET)"; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ================================================
|
||||||
|
# 快捷命令
|
||||||
|
# ================================================
|
||||||
|
.PHONY: dev
|
||||||
|
dev: start-core
|
||||||
|
@echo "$(COLOR_GREEN)✓ 开发环境启动完成 (gateway, auth, system)$(COLOR_RESET)"
|
||||||
|
|
||||||
|
.PHONY: prod
|
||||||
|
prod: start
|
||||||
|
@echo "$(COLOR_GREEN)✓ 生产环境启动完成$(COLOR_RESET)"
|
||||||
|
|
||||||
|
# 默认目标
|
||||||
|
.DEFAULT_GOAL := help
|
||||||
89
urbanLifelineServ/agent/src/main/resources/application.yml
Normal file
89
urbanLifelineServ/agent/src/main/resources/application.yml
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# ================== Server ==================
|
||||||
|
server:
|
||||||
|
port: 8190
|
||||||
|
servlet:
|
||||||
|
context-path: /urban-lifeline/agent
|
||||||
|
|
||||||
|
# ================== Auth ====================
|
||||||
|
urban-lifeline:
|
||||||
|
auth:
|
||||||
|
enabled: true
|
||||||
|
whitelist:
|
||||||
|
- /swagger-ui/**
|
||||||
|
- /swagger-ui.html
|
||||||
|
- /v3/api-docs/**
|
||||||
|
- /webjars/**
|
||||||
|
- /favicon.ico
|
||||||
|
- /error
|
||||||
|
- /actuator/health
|
||||||
|
- /actuator/info
|
||||||
|
|
||||||
|
security:
|
||||||
|
aes:
|
||||||
|
secret-key: 1234567890qwer
|
||||||
|
|
||||||
|
# ================== Spring ==================
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: agent-service
|
||||||
|
|
||||||
|
# ================== Spring Cloud Nacos ==================
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
server-addr: 127.0.0.1:8848
|
||||||
|
namespace: dev
|
||||||
|
group: DEFAULT_GROUP
|
||||||
|
|
||||||
|
# ================== DataSource ==================
|
||||||
|
datasource:
|
||||||
|
url: jdbc:postgresql://127.0.0.1:5432/urban_lifeline
|
||||||
|
username: postgres
|
||||||
|
password: postgres
|
||||||
|
driver-class-name: org.postgresql.Driver
|
||||||
|
|
||||||
|
# ================== Redis ==================
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: 127.0.0.1 # 如果是 docker 跑的 redis,按实际 host / 端口改
|
||||||
|
port: 6379
|
||||||
|
database: 0
|
||||||
|
password: 123456 # 如果有密码就填上,没密码可以去掉这一行
|
||||||
|
|
||||||
|
# ================== SpringDoc ==================
|
||||||
|
springdoc:
|
||||||
|
api-docs:
|
||||||
|
enabled: true
|
||||||
|
path: /v3/api-docs
|
||||||
|
swagger-ui:
|
||||||
|
enabled: true
|
||||||
|
path: /swagger-ui.html
|
||||||
|
group-configs:
|
||||||
|
- group: 'default'
|
||||||
|
display-name: 'AI代理服务 API'
|
||||||
|
paths-to-match: '/**'
|
||||||
|
|
||||||
|
# ================== Dubbo + Nacos ==================
|
||||||
|
dubbo:
|
||||||
|
application:
|
||||||
|
name: urban-lifeline-agent
|
||||||
|
qos-enable: false
|
||||||
|
protocol:
|
||||||
|
name: dubbo
|
||||||
|
port: -1
|
||||||
|
registry:
|
||||||
|
address: nacos://127.0.0.1:8848
|
||||||
|
scan:
|
||||||
|
base-packages: org.xyzh.agent.service.impl
|
||||||
|
|
||||||
|
# ================== MyBatis ==================
|
||||||
|
mybatis-plus:
|
||||||
|
mapper-locations: classpath:mapper/**/*.xml
|
||||||
|
type-aliases-package: org.xyzh.common.dto, org.xyzh.api
|
||||||
|
|
||||||
|
# ================== Logging ==================
|
||||||
|
logging:
|
||||||
|
config: classpath:log4j2.xml
|
||||||
|
charset:
|
||||||
|
console: UTF-8
|
||||||
|
file: UTF-8
|
||||||
85
urbanLifelineServ/agent/src/main/resources/log4j2.xml
Normal file
85
urbanLifelineServ/agent/src/main/resources/log4j2.xml
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration status="WARN" monitorInterval="30">
|
||||||
|
<Properties>
|
||||||
|
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
|
||||||
|
<property name="FILE_PATH" value="./logs" />
|
||||||
|
<property name="FILE_NAME" value="agent-service" />
|
||||||
|
<property name="file.encoding" value="UTF-8" />
|
||||||
|
<property name="console.encoding" value="UTF-8" />
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
<appenders>
|
||||||
|
<console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
</console>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${FILE_NAME}-info.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/${FILE_NAME}-warn.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/${FILE_NAME}-error.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
</appenders>
|
||||||
|
|
||||||
|
<loggers>
|
||||||
|
<logger name="com.alibaba.nacos" level="info" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<logger name="org.mybatis" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<Logger name="org.springframework" level="info" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<Logger name="org.xyzh.agent" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFileInfo"/>
|
||||||
|
<AppenderRef ref="RollingFileWarn"/>
|
||||||
|
<AppenderRef ref="RollingFileError"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<Logger name="org.xyzh.common" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFileInfo"/>
|
||||||
|
<AppenderRef ref="RollingFileWarn"/>
|
||||||
|
<AppenderRef ref="RollingFileError"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="Console"/>
|
||||||
|
<appender-ref ref="RollingFileInfo"/>
|
||||||
|
<appender-ref ref="RollingFileWarn"/>
|
||||||
|
<appender-ref ref="RollingFileError"/>
|
||||||
|
</root>
|
||||||
|
</loggers>
|
||||||
|
</configuration>
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package org.xyzh.api.message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Message服务接口
|
|
||||||
* 用于消息管理
|
|
||||||
*/
|
|
||||||
public interface MessageService {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,19 @@ import org.xyzh.common.core.page.PageParam;
|
|||||||
*/
|
*/
|
||||||
public interface MessageService {
|
public interface MessageService {
|
||||||
|
|
||||||
|
// ================ 发送邮件 ==================
|
||||||
|
ResultDomain<String> sendSimpleEmail(String to, String subject, String content);
|
||||||
|
|
||||||
|
ResultDomain<String> sendHtmlEmail(String to, String subject, String content);
|
||||||
|
|
||||||
|
ResultDomain<String> sendEmailVerificationCode(String to, String code);
|
||||||
|
|
||||||
|
// ================ 发送短信 ==================
|
||||||
|
|
||||||
//================= 用户查看消息列表 =================
|
//================= 用户查看消息列表 =================
|
||||||
|
ResultDomain<String> sendPhoneVerificationCode(String phone, String code);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 获取我的消息列表
|
* @description 获取我的消息列表
|
||||||
|
|||||||
@@ -13,6 +13,43 @@ import org.xyzh.common.dto.sys.TbSysConfigDTO;
|
|||||||
* @since 2025-11-05
|
* @since 2025-11-05
|
||||||
*/
|
*/
|
||||||
public interface SysConfigService {
|
public interface SysConfigService {
|
||||||
|
// ================== 读取配置 ========================
|
||||||
|
/**
|
||||||
|
* 获取字符串类型配置
|
||||||
|
* @param key 配置键
|
||||||
|
* @return 配置值
|
||||||
|
*/
|
||||||
|
String getStringConfig(String key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取整数类型配置
|
||||||
|
* @param key 配置键
|
||||||
|
* @return 配置值,如果不存在或解析失败返回null
|
||||||
|
*/
|
||||||
|
Integer getIntConfig(String key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取布尔类型配置
|
||||||
|
* @param key 配置键
|
||||||
|
* @return 配置值,如果不存在或解析失败返回null
|
||||||
|
*/
|
||||||
|
Boolean getBooleanConfig(String key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取浮点数类型配置
|
||||||
|
* @param key 配置键
|
||||||
|
* @return 配置值,如果不存在或解析失败返回null
|
||||||
|
*/
|
||||||
|
Double getDoubleConfig(String key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取长整数类型配置
|
||||||
|
* @param key 配置键
|
||||||
|
* @return 配置值,如果不存在或解析失败返回null
|
||||||
|
*/
|
||||||
|
Long getLongConfig(String key);
|
||||||
|
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 插入系统配置
|
* @description 插入系统配置
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ import org.xyzh.common.dto.sys.TbSysUserRoleDTO;
|
|||||||
public interface SysUserService {
|
public interface SysUserService {
|
||||||
|
|
||||||
// ================= 用户基本信息管理 =================
|
// ================= 用户基本信息管理 =================
|
||||||
|
|
||||||
|
ResultDomain<TbSysUserDTO> registerUser(SysUserVO userVO);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 插入用户
|
* @description 插入用户
|
||||||
* @param userVO 用户VO
|
* @param userVO 用户VO
|
||||||
@@ -61,7 +65,7 @@ public interface SysUserService {
|
|||||||
* @author yslg
|
* @author yslg
|
||||||
* @since 2025-11-05
|
* @since 2025-11-05
|
||||||
*/
|
*/
|
||||||
ResultDomain<SysUserVO> getLoginUser(SysUserVO filter);
|
ResultDomain<TbSysUserDTO> getLoginUser(TbSysUserDTO filter);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 根据条件查询用户列表
|
* @description 根据条件查询用户列表
|
||||||
|
|||||||
@@ -23,10 +23,42 @@
|
|||||||
<groupId>org.xyzh.apis</groupId>
|
<groupId>org.xyzh.apis</groupId>
|
||||||
<artifactId>api-auth</artifactId>
|
<artifactId>api-auth</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.xyzh.apis</groupId>
|
||||||
|
<artifactId>api-system</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.xyzh.apis</groupId>
|
||||||
|
<artifactId>api-message</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.xyzh.common</groupId>
|
<groupId>org.xyzh.common</groupId>
|
||||||
<artifactId>common-auth</artifactId>
|
<artifactId>common-auth</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.xyzh.common</groupId>
|
||||||
|
<artifactId>common-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.xyzh.common</groupId>
|
||||||
|
<artifactId>common-utils</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Cloud Nacos Discovery - 用于 Gateway 路由发现 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.alibaba.nacos</groupId>
|
||||||
|
<artifactId>nacos-logback-adapter-12</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.alibaba.nacos</groupId>
|
||||||
|
<artifactId>logback-adapter</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
@@ -0,0 +1,433 @@
|
|||||||
|
package org.xyzh.auth.controller;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.xyzh.api.auth.service.AuthService;
|
||||||
|
import org.xyzh.api.message.service.MessageService;
|
||||||
|
import org.xyzh.api.system.service.SysUserService;
|
||||||
|
import org.xyzh.api.system.vo.SysUserVO;
|
||||||
|
import org.xyzh.auth.utils.CapcatUtils;
|
||||||
|
import org.xyzh.common.core.domain.LoginParam;
|
||||||
|
import org.xyzh.common.core.domain.LoginDomain;
|
||||||
|
import org.xyzh.common.core.domain.ResultDomain;
|
||||||
|
import org.xyzh.common.dto.sys.TbSysUserDTO;
|
||||||
|
import org.xyzh.common.utils.IDUtils;
|
||||||
|
import org.xyzh.common.utils.validation.method.PhoneValidateMethod;
|
||||||
|
import org.xyzh.common.redis.service.RedisService;
|
||||||
|
import org.apache.dubbo.config.annotation.DubboReference;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description AuthController.java文件描述 认证控制器
|
||||||
|
* @filename AuthController.java
|
||||||
|
* @author yslg
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-09-28
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/auth")
|
||||||
|
public class AuthController {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(AuthController.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AuthService authService;
|
||||||
|
|
||||||
|
@DubboReference(version = "1.0.0", group = "system", timeout = 5000, check = false)
|
||||||
|
private SysUserService userService;
|
||||||
|
|
||||||
|
@DubboReference(version = "1.0.0", group = "message", timeout = 5000, check = false)
|
||||||
|
private MessageService messageService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisService redisService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
/**
|
||||||
|
* @description 用户登录
|
||||||
|
* @param loginParam 登录参数
|
||||||
|
* @return ResultDomain<LoginDomain> 登录结果
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-09-28
|
||||||
|
*/
|
||||||
|
@PostMapping("/login")
|
||||||
|
public ResultDomain<LoginDomain> login(@RequestBody LoginParam loginParam, HttpServletRequest request) {
|
||||||
|
return authService.login(loginParam, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 用户退出登录
|
||||||
|
* @param loginDomain 登录域对象
|
||||||
|
* @return ResultDomain<String> 退出结果
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-09-28
|
||||||
|
*/
|
||||||
|
@PostMapping("/logout")
|
||||||
|
public ResultDomain<LoginDomain> logout(HttpServletRequest request) {
|
||||||
|
return authService.logout(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取验证码
|
||||||
|
* @return ResultDomain<String> 验证码
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-09-28
|
||||||
|
*/
|
||||||
|
@GetMapping("/captcha")
|
||||||
|
public ResultDomain<String> getCaptcha() {
|
||||||
|
// TODO: 实现验证码生成逻辑
|
||||||
|
// 生成验证码会话ID,用于验证时匹配
|
||||||
|
String captchaId = IDUtils.generateID();
|
||||||
|
String captchaData = captchaId + ":captcha-placeholder"; // 格式: ID:验证码内容
|
||||||
|
|
||||||
|
return ResultDomain.success("验证码获取成功", captchaData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 刷新令牌
|
||||||
|
* @param token 原令牌
|
||||||
|
* @return ResultDomain<String> 新令牌
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-09-28
|
||||||
|
*/
|
||||||
|
@PostMapping("/refresh")
|
||||||
|
public ResultDomain<String> refreshToken(@RequestHeader("Authorization") String token) {
|
||||||
|
// TODO: 实现令牌刷新逻辑
|
||||||
|
// 为新令牌生成唯一ID
|
||||||
|
String newTokenId = IDUtils.generateID();
|
||||||
|
String newToken = "new-token-" + newTokenId; // 临时占位符
|
||||||
|
|
||||||
|
return ResultDomain.success("令牌刷新成功", newToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 健康检查
|
||||||
|
* @return ResultDomain<String> 健康状态
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-09-28
|
||||||
|
*/
|
||||||
|
@GetMapping("/health")
|
||||||
|
public ResultDomain<String> health() {
|
||||||
|
return ResultDomain.success("认证服务运行正常", "OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 发送邮箱验证码
|
||||||
|
* @param requestBody 包含email字段的请求体
|
||||||
|
* @return ResultDomain<Boolean> 发送结果
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-11-03
|
||||||
|
*/
|
||||||
|
@PostMapping("/send-email-code")
|
||||||
|
public ResultDomain<Map<String, String>> sendEmailCode(@RequestBody Map<String, String> requestBody) {
|
||||||
|
String email = requestBody.get("email");
|
||||||
|
|
||||||
|
// 验证邮箱格式
|
||||||
|
if (email == null || email.trim().isEmpty()) {
|
||||||
|
return ResultDomain.failure("邮箱不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
String emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$";
|
||||||
|
if (!email.matches(emailRegex)) {
|
||||||
|
return ResultDomain.failure("邮箱格式不正确");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否频繁发送(60秒内只能发送一次)
|
||||||
|
String rateLimitKey = "email:code:ratelimit:" + email;
|
||||||
|
if (redisService.hasKey(rateLimitKey)) {
|
||||||
|
return ResultDomain.failure("验证码已发送,请勿重复发送");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成会话ID(用于绑定验证码和用户)
|
||||||
|
String sessionId = IDUtils.generateID();
|
||||||
|
|
||||||
|
// 生成6位数字验证码
|
||||||
|
String code = CapcatUtils.generateVerificationCode();
|
||||||
|
|
||||||
|
// 发送邮件
|
||||||
|
boolean success = messageService.sendEmailVerificationCode(email, code).getSuccess();
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
// 将验证码存储到Redis,绑定sessionId,有效期5分钟
|
||||||
|
String codeKey = "email:code:" + sessionId;
|
||||||
|
String codeValue = email + ":" + code; // 格式:邮箱:验证码
|
||||||
|
redisService.set(codeKey, codeValue, 5, TimeUnit.MINUTES);
|
||||||
|
|
||||||
|
// 设置5分钟的发送频率限制
|
||||||
|
redisService.set(rateLimitKey, "1", 5, TimeUnit.MINUTES);
|
||||||
|
|
||||||
|
// 返回sessionId给前端
|
||||||
|
Map<String, String> data = Map.of(
|
||||||
|
"sessionId", sessionId,
|
||||||
|
"message", "验证码已发送到邮箱"
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info("邮箱验证码已发送,邮箱: {}, sessionId: {}", email, sessionId);
|
||||||
|
return ResultDomain.success("验证码已发送到邮箱", data);
|
||||||
|
} else {
|
||||||
|
return ResultDomain.failure("验证码发送失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 发送手机验证码
|
||||||
|
* @param requestBody 包含phone字段的请求体
|
||||||
|
* @return ResultDomain<Boolean> 发送结果
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-11-03
|
||||||
|
*/
|
||||||
|
@PostMapping("/send-sms-code")
|
||||||
|
public ResultDomain<Map<String, String>> sendSmsCode(@RequestBody Map<String, String> requestBody) {
|
||||||
|
String phone = requestBody.get("phone");
|
||||||
|
|
||||||
|
// 验证手机号格式
|
||||||
|
if (phone == null || phone.trim().isEmpty()) {
|
||||||
|
return ResultDomain.failure("手机号不能为空");
|
||||||
|
}
|
||||||
|
PhoneValidateMethod validateMethod = new PhoneValidateMethod();
|
||||||
|
if (!validateMethod.validate(phone)) {
|
||||||
|
return ResultDomain.failure("手机号格式不正确");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否频繁发送(60秒内只能发送一次)
|
||||||
|
String rateLimitKey = "sms:code:ratelimit:" + phone;
|
||||||
|
if (redisService.hasKey(rateLimitKey)) {
|
||||||
|
return ResultDomain.failure("验证码已发送,请勿重复发送");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成会话ID(用于绑定验证码和用户)
|
||||||
|
String sessionId = IDUtils.generateID();
|
||||||
|
|
||||||
|
// 生成6位数字验证码
|
||||||
|
String code = CapcatUtils.generateVerificationCode();
|
||||||
|
|
||||||
|
// 发送短信
|
||||||
|
boolean success = messageService.sendPhoneVerificationCode(phone, code).getSuccess();
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
// 将验证码存储到Redis,绑定sessionId,有效期5分钟
|
||||||
|
String codeKey = "sms:code:" + sessionId;
|
||||||
|
String codeValue = phone + ":" + code; // 格式:手机号:验证码
|
||||||
|
redisService.set(codeKey, codeValue, 5, TimeUnit.MINUTES);
|
||||||
|
|
||||||
|
// 设置5分钟的发送频率限制
|
||||||
|
redisService.set(rateLimitKey, "1", 5, TimeUnit.MINUTES);
|
||||||
|
|
||||||
|
// 返回sessionId给前端
|
||||||
|
Map<String, String> data = Map.of(
|
||||||
|
"sessionId", sessionId,
|
||||||
|
"message", "验证码已发送"
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info("短信验证码已发送,手机号: {}, sessionId: {}", phone, sessionId);
|
||||||
|
return ResultDomain.success("验证码已发送", data);
|
||||||
|
} else {
|
||||||
|
return ResultDomain.failure("验证码发送失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 用户注册
|
||||||
|
* @param requestBody 注册参数
|
||||||
|
* @return ResultDomain<LoginDomain> 注册结果
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-11-03
|
||||||
|
*/
|
||||||
|
@PostMapping("/register")
|
||||||
|
public ResultDomain<LoginDomain> register(@RequestBody Map<String, Object> requestBody, HttpServletRequest request) {
|
||||||
|
try {
|
||||||
|
// 获取注册参数
|
||||||
|
String registerType = (String) requestBody.get("registerType");
|
||||||
|
String username = (String) requestBody.get("username");
|
||||||
|
String phone = (String) requestBody.get("phone");
|
||||||
|
String email = (String) requestBody.get("email");
|
||||||
|
String password = (String) requestBody.get("password");
|
||||||
|
String confirmPassword = (String) requestBody.get("confirmPassword");
|
||||||
|
String smsCode = (String) requestBody.get("smsCode");
|
||||||
|
String emailCode = (String) requestBody.get("emailCode");
|
||||||
|
String smsSessionId = (String) requestBody.get("smsSessionId");
|
||||||
|
String emailSessionId = (String) requestBody.get("emailSessionId");
|
||||||
|
String studentId = (String) requestBody.get("studentId");
|
||||||
|
|
||||||
|
// 1. 参数验证
|
||||||
|
if (password == null || password.trim().isEmpty()) {
|
||||||
|
return ResultDomain.failure("密码不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.length() < 6) {
|
||||||
|
return ResultDomain.failure("密码至少6个字符");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password.equals(confirmPassword)) {
|
||||||
|
return ResultDomain.failure("两次输入的密码不一致");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 根据注册类型进行不同的验证
|
||||||
|
SysUserVO user = new SysUserVO();
|
||||||
|
|
||||||
|
switch (registerType) {
|
||||||
|
case "username":
|
||||||
|
// 用户名注册
|
||||||
|
if (username == null || username.trim().isEmpty()) {
|
||||||
|
return ResultDomain.failure("用户名不能为空");
|
||||||
|
}
|
||||||
|
if (username.length() < 3 || username.length() > 20) {
|
||||||
|
return ResultDomain.failure("用户名长度为3-20个字符");
|
||||||
|
}
|
||||||
|
if (!username.matches("^[a-zA-Z0-9_]+$")) {
|
||||||
|
return ResultDomain.failure("用户名只能包含字母、数字和下划线");
|
||||||
|
}
|
||||||
|
user.setUsername(username);
|
||||||
|
logger.info("用户名注册: {}", username);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "phone":
|
||||||
|
// 手机号注册
|
||||||
|
if (phone == null || phone.trim().isEmpty()) {
|
||||||
|
return ResultDomain.failure("手机号不能为空");
|
||||||
|
}
|
||||||
|
if (!phone.matches("^1[3-9]\\d{9}$")) {
|
||||||
|
return ResultDomain.failure("手机号格式不正确");
|
||||||
|
}
|
||||||
|
if (smsCode == null || smsCode.trim().isEmpty()) {
|
||||||
|
return ResultDomain.failure("请输入手机验证码");
|
||||||
|
}
|
||||||
|
if (smsSessionId == null || smsSessionId.trim().isEmpty()) {
|
||||||
|
return ResultDomain.failure("会话已失效,请重新获取验证码");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过sessionId验证手机验证码
|
||||||
|
String smsCodeKey = "sms:code:" + smsSessionId;
|
||||||
|
String storedSmsValue = (String) redisService.get(smsCodeKey);
|
||||||
|
if (storedSmsValue == null) {
|
||||||
|
return ResultDomain.failure("验证码已过期,请重新获取");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析存储的值:手机号:验证码
|
||||||
|
String[] smsParts = storedSmsValue.split(":");
|
||||||
|
if (smsParts.length != 2) {
|
||||||
|
return ResultDomain.failure("验证码数据异常");
|
||||||
|
}
|
||||||
|
|
||||||
|
String storedPhone = smsParts[0];
|
||||||
|
String storedSmsCode = smsParts[1];
|
||||||
|
|
||||||
|
// 验证手机号和验证码是否匹配
|
||||||
|
if (!storedPhone.equals(phone)) {
|
||||||
|
logger.warn("手机号注册验证失败,提交手机号: {}, 验证码绑定手机号: {}", phone, storedPhone);
|
||||||
|
return ResultDomain.failure("手机号与验证码不匹配");
|
||||||
|
}
|
||||||
|
if (!storedSmsCode.equals(smsCode)) {
|
||||||
|
return ResultDomain.failure("验证码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证码使用后删除
|
||||||
|
redisService.delete(smsCodeKey);
|
||||||
|
|
||||||
|
user.setPhone(phone);
|
||||||
|
user.setUsername(phone); // 使用手机号作为用户名
|
||||||
|
logger.info("手机号注册: {}, sessionId: {}", phone, smsSessionId);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "email":
|
||||||
|
// 邮箱注册
|
||||||
|
if (email == null || email.trim().isEmpty()) {
|
||||||
|
return ResultDomain.failure("邮箱不能为空");
|
||||||
|
}
|
||||||
|
if (!email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")) {
|
||||||
|
return ResultDomain.failure("邮箱格式不正确");
|
||||||
|
}
|
||||||
|
if (emailCode == null || emailCode.trim().isEmpty()) {
|
||||||
|
return ResultDomain.failure("请输入邮箱验证码");
|
||||||
|
}
|
||||||
|
if (emailSessionId == null || emailSessionId.trim().isEmpty()) {
|
||||||
|
return ResultDomain.failure("会话已失效,请重新获取验证码");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过sessionId验证邮箱验证码
|
||||||
|
String emailCodeKey = "email:code:" + emailSessionId;
|
||||||
|
String storedEmailValue = (String) redisService.get(emailCodeKey);
|
||||||
|
if (storedEmailValue == null) {
|
||||||
|
return ResultDomain.failure("验证码已过期,请重新获取");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析存储的值:邮箱:验证码
|
||||||
|
String[] emailParts = storedEmailValue.split(":");
|
||||||
|
if (emailParts.length != 2) {
|
||||||
|
return ResultDomain.failure("验证码数据异常");
|
||||||
|
}
|
||||||
|
|
||||||
|
String storedEmail = emailParts[0];
|
||||||
|
String storedEmailCode = emailParts[1];
|
||||||
|
|
||||||
|
// 验证邮箱和验证码是否匹配
|
||||||
|
if (!storedEmail.equals(email)) {
|
||||||
|
logger.warn("邮箱注册验证失败,提交邮箱: {}, 验证码绑定邮箱: {}", email, storedEmail);
|
||||||
|
return ResultDomain.failure("邮箱与验证码不匹配");
|
||||||
|
}
|
||||||
|
if (!storedEmailCode.equals(emailCode)) {
|
||||||
|
return ResultDomain.failure("验证码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证码使用后删除
|
||||||
|
redisService.delete(emailCodeKey);
|
||||||
|
|
||||||
|
user.setEmail(email);
|
||||||
|
user.setUsername(email.split("@")[0]); // 使用邮箱前缀作为用户名
|
||||||
|
logger.info("邮箱注册: {}, sessionId: {}", email, emailSessionId);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ResultDomain.failure("未知的注册类型");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 设置密码(明文,Service层会加密)
|
||||||
|
user.setPassword(password);
|
||||||
|
|
||||||
|
// 4. 设置用户状态为正常
|
||||||
|
user.setStatus("0");
|
||||||
|
|
||||||
|
// 5. 调用UserService注册用户(Service层会加密密码)
|
||||||
|
ResultDomain<TbSysUserDTO> registerResult = userService.registerUser(user);
|
||||||
|
|
||||||
|
if (!registerResult.getSuccess()) {
|
||||||
|
return ResultDomain.failure(registerResult.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("用户注册成功: {}", user.getUsername());
|
||||||
|
|
||||||
|
// 6. 注册成功后自动登录
|
||||||
|
LoginParam loginParam = new LoginParam();
|
||||||
|
loginParam.setUsername(user.getUsername());
|
||||||
|
loginParam.setPassword(password);
|
||||||
|
loginParam.setLoginType("password");
|
||||||
|
|
||||||
|
if (phone != null && !phone.trim().isEmpty()) {
|
||||||
|
loginParam.setPhone(phone);
|
||||||
|
}
|
||||||
|
if (email != null && !email.trim().isEmpty()) {
|
||||||
|
loginParam.setEmail(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultDomain<LoginDomain> loginResult = authService.login(loginParam, request);
|
||||||
|
|
||||||
|
if (loginResult.getSuccess()) {
|
||||||
|
return ResultDomain.success("注册成功", loginResult.getData());
|
||||||
|
} else {
|
||||||
|
// 注册成功但登录失败,返回注册成功信息
|
||||||
|
return ResultDomain.success("注册成功,请登录", loginResult.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("用户注册失败", e);
|
||||||
|
return ResultDomain.failure("注册失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package org.xyzh.auth.exception;
|
||||||
|
import org.xyzh.common.core.exception.BaseException;
|
||||||
|
|
||||||
|
public class AuthException extends BaseException {
|
||||||
|
|
||||||
|
public AuthException(Integer code, String description, String message){
|
||||||
|
super(code, description, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import org.xyzh.api.auth.service.AuthService;
|
|||||||
import org.xyzh.common.core.domain.LoginDomain;
|
import org.xyzh.common.core.domain.LoginDomain;
|
||||||
import org.xyzh.common.core.domain.LoginParam;
|
import org.xyzh.common.core.domain.LoginParam;
|
||||||
import org.xyzh.common.core.domain.ResultDomain;
|
import org.xyzh.common.core.domain.ResultDomain;
|
||||||
|
import org.apache.dubbo.config.annotation.DubboService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -17,6 +17,12 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
* @copyright yslg
|
* @copyright yslg
|
||||||
* @since 2025-11-09
|
* @since 2025-11-09
|
||||||
*/
|
*/
|
||||||
|
@DubboService(
|
||||||
|
version = "1.0.0",
|
||||||
|
group = "auth",
|
||||||
|
timeout = 3000,
|
||||||
|
retries = 0
|
||||||
|
)
|
||||||
public class AuthServiceImpl implements AuthService{
|
public class AuthServiceImpl implements AuthService{
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(AuthServiceImpl.class);
|
private static final Logger logger = LoggerFactory.getLogger(AuthServiceImpl.class);
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package org.xyzh.auth.strategy;
|
||||||
|
|
||||||
|
import org.xyzh.common.core.domain.LoginParam;
|
||||||
|
import org.xyzh.common.dto.sys.TbSysUserDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description LoginStrategy.java文件描述 登录策略接口
|
||||||
|
* @filename LoginStrategy.java
|
||||||
|
* @author yslg
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-12-05
|
||||||
|
*/
|
||||||
|
public interface LoginStrategy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 支持的登录类型
|
||||||
|
* @return String 登录类型
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-12-05
|
||||||
|
*/
|
||||||
|
String getLoginType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 验证登录参数
|
||||||
|
* @param loginParam 登录参数
|
||||||
|
* @return boolean 是否有效
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-12-05
|
||||||
|
*/
|
||||||
|
boolean validate(LoginParam loginParam);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 根据登录参数查找用户
|
||||||
|
* @param loginParam 登录参数
|
||||||
|
* @return TbSysUser 用户对象
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-12-05
|
||||||
|
*/
|
||||||
|
TbSysUserDTO findUser(LoginParam loginParam);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 验证凭据(密码或验证码)
|
||||||
|
* @param inputCredential 输入凭据(密码或验证码)
|
||||||
|
* @param storedCredential 存储凭据(密码或验证码)
|
||||||
|
* @return boolean 是否匹配
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-12-05
|
||||||
|
*/
|
||||||
|
boolean verifyCredential(String inputCredential, String storedCredential);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 验证验证码(从Redis获取并验证SessionID)
|
||||||
|
* @param loginParam 登录参数
|
||||||
|
* @return boolean 是否验证成功
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-12-05
|
||||||
|
*/
|
||||||
|
default boolean verifyCaptchaWithSession(LoginParam loginParam) {
|
||||||
|
// 默认实现:不支持验证码登录
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package org.xyzh.auth.strategy;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.xyzh.auth.exception.AuthException;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description LoginStrategyFactory.java文件描述 登录策略工厂
|
||||||
|
* @filename LoginStrategyFactory.java
|
||||||
|
* @author yslg
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-12-05
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class LoginStrategyFactory {
|
||||||
|
|
||||||
|
private final Map<String, LoginStrategy> strategies;
|
||||||
|
|
||||||
|
public LoginStrategyFactory(List<LoginStrategy> loginStrategies) {
|
||||||
|
this.strategies = loginStrategies.stream()
|
||||||
|
.collect(Collectors.toMap(LoginStrategy::getLoginType, Function.identity()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取登录策略
|
||||||
|
* @param loginType 登录类型
|
||||||
|
* @return LoginStrategy 登录策略
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-12-05
|
||||||
|
*/
|
||||||
|
public LoginStrategy getStrategy(String loginType) {
|
||||||
|
LoginStrategy strategy = strategies.get(loginType);
|
||||||
|
if (strategy == null) {
|
||||||
|
throw new AuthException(1,"UNSUPPORTED_LOGIN_TYPE", "不支持的登录类型: " + loginType);
|
||||||
|
}
|
||||||
|
return strategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取所有支持的登录类型
|
||||||
|
* @return Set<String> 登录类型集合
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-12-05
|
||||||
|
*/
|
||||||
|
public java.util.Set<String> getSupportedLoginTypes() {
|
||||||
|
return strategies.keySet();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package org.xyzh.auth.strategy.impl;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.xyzh.auth.strategy.LoginStrategy;
|
||||||
|
import org.xyzh.common.core.domain.LoginParam;
|
||||||
|
import org.xyzh.common.dto.sys.TbSysUserDTO;
|
||||||
|
import org.xyzh.api.system.service.SysUserService;
|
||||||
|
import org.xyzh.api.system.vo.SysUserVO;
|
||||||
|
import org.xyzh.common.redis.service.RedisService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description EmailLoginStrategy.java文件描述 邮箱登录策略
|
||||||
|
* @filename EmailLoginStrategy.java
|
||||||
|
* @author yslg
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-12-05
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class EmailLoginStrategy implements LoginStrategy {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SysUserService userService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLoginType() {
|
||||||
|
return "email";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validate(LoginParam loginParam) {
|
||||||
|
if (loginParam.getEmail() == null || loginParam.getEmail().trim().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 密码登录或验证码登录都可以
|
||||||
|
return (loginParam.getPassword() != null && !loginParam.getPassword().trim().isEmpty())
|
||||||
|
|| (loginParam.getCaptcha() != null && !loginParam.getCaptcha().trim().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TbSysUserDTO findUser(LoginParam loginParam) {
|
||||||
|
TbSysUserDTO filter = new TbSysUserDTO();
|
||||||
|
filter.setEmail(loginParam.getEmail());
|
||||||
|
TbSysUserDTO user = userService.getLoginUser(filter).getData();
|
||||||
|
if(user == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisService redisService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean verifyCredential(String inputCredential, String storedCredential) {
|
||||||
|
// 密码验证
|
||||||
|
return passwordEncoder.matches(inputCredential, storedCredential);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean verifyCaptchaWithSession(LoginParam loginParam) {
|
||||||
|
String captchaId = loginParam.getCaptchaId();
|
||||||
|
String inputCaptcha = loginParam.getCaptcha();
|
||||||
|
String email = loginParam.getEmail();
|
||||||
|
|
||||||
|
// 验证参数
|
||||||
|
if (captchaId == null || captchaId.trim().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (inputCaptcha == null || inputCaptcha.trim().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从Redis获取验证码
|
||||||
|
String codeKey = "email:code:" + captchaId;
|
||||||
|
String storedValue = (String) redisService.get(codeKey);
|
||||||
|
|
||||||
|
if (storedValue == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析存储的值:邮箱:验证码
|
||||||
|
String[] parts = storedValue.split(":");
|
||||||
|
if (parts.length != 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String storedEmail = parts[0];
|
||||||
|
String storedCaptcha = parts[1];
|
||||||
|
|
||||||
|
// 验证邮箱和验证码是否匹配
|
||||||
|
if (!storedEmail.equals(email) || !storedCaptcha.equals(inputCaptcha)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证码使用后删除
|
||||||
|
redisService.delete(codeKey);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package org.xyzh.auth.strategy.impl;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.xyzh.auth.strategy.LoginStrategy;
|
||||||
|
import org.xyzh.common.core.domain.LoginParam;
|
||||||
|
import org.xyzh.common.dto.sys.TbSysUserDTO;
|
||||||
|
import org.xyzh.common.utils.NonUtils;
|
||||||
|
import org.xyzh.common.utils.validation.method.EmailValidateMethod;
|
||||||
|
import org.xyzh.common.utils.validation.method.PhoneValidateMethod;
|
||||||
|
import org.xyzh.api.system.service.SysUserService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description PasswordLoginStrategy.java文件描述 密码登录策略
|
||||||
|
* @filename PasswordLoginStrategy.java
|
||||||
|
* @author yslg
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-12-05
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class PasswordLoginStrategy implements LoginStrategy {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(PasswordLoginStrategy.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SysUserService userService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLoginType() {
|
||||||
|
return "password";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validate(LoginParam loginParam) {
|
||||||
|
if (NonUtils.isEmpty(loginParam.getPassword())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NonUtils.isEmpty(loginParam.getUsername()) && NonUtils.isEmpty(loginParam.getEmail()) && NonUtils.isEmpty(loginParam.getPhone())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TbSysUserDTO findUser(LoginParam loginParam) {
|
||||||
|
TbSysUserDTO filter = new TbSysUserDTO();
|
||||||
|
EmailValidateMethod emailValidateMethod = new EmailValidateMethod();
|
||||||
|
PhoneValidateMethod phoneValidateMethod = new PhoneValidateMethod();
|
||||||
|
if(emailValidateMethod.validate(loginParam.getUsername())){
|
||||||
|
filter.setEmail(loginParam.getUsername());
|
||||||
|
}else if (phoneValidateMethod.validate(loginParam.getUsername())){
|
||||||
|
filter.setPhone(loginParam.getUsername());
|
||||||
|
}else{
|
||||||
|
filter.setUsername(loginParam.getUsername());
|
||||||
|
}
|
||||||
|
// 【优化】删除无用的密码编码,SQL查询不使用password字段
|
||||||
|
// 密码验证在 verifyCredential() 方法中进行
|
||||||
|
TbSysUserDTO user = userService.getLoginUser(filter).getData();
|
||||||
|
if(user == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean verifyCredential(String inputCredential, String storedCredential) {
|
||||||
|
// 使用BCrypt的matches方法验证密码(内部会自动处理salt)
|
||||||
|
return passwordEncoder.matches(inputCredential, storedCredential);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package org.xyzh.auth.strategy.impl;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.xyzh.auth.strategy.LoginStrategy;
|
||||||
|
import org.xyzh.common.core.domain.LoginParam;
|
||||||
|
import org.xyzh.common.dto.sys.TbSysUserDTO;
|
||||||
|
import org.xyzh.api.system.service.SysUserService;
|
||||||
|
import org.xyzh.common.redis.service.RedisService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description PhoneLoginStrategy.java文件描述 手机号登录策略
|
||||||
|
* @filename PhoneLoginStrategy.java
|
||||||
|
* @author yslg
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-12-05
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class PhoneLoginStrategy implements LoginStrategy {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SysUserService userService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLoginType() {
|
||||||
|
return "phone";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validate(LoginParam loginParam) {
|
||||||
|
if (loginParam.getPhone() == null || loginParam.getPhone().trim().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 密码登录或验证码登录都可以
|
||||||
|
return (loginParam.getPassword() != null && !loginParam.getPassword().trim().isEmpty())
|
||||||
|
|| (loginParam.getCaptcha() != null && !loginParam.getCaptcha().trim().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TbSysUserDTO findUser(LoginParam loginParam) {
|
||||||
|
TbSysUserDTO filter = new TbSysUserDTO();
|
||||||
|
filter.setPhone(loginParam.getPhone());
|
||||||
|
TbSysUserDTO user = userService.getLoginUser(filter).getData();
|
||||||
|
if(user == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisService redisService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean verifyCredential(String inputCredential, String storedCredential) {
|
||||||
|
// 密码验证
|
||||||
|
return passwordEncoder.matches(inputCredential, storedCredential);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean verifyCaptchaWithSession(LoginParam loginParam) {
|
||||||
|
String captchaId = loginParam.getCaptchaId();
|
||||||
|
String inputCaptcha = loginParam.getCaptcha();
|
||||||
|
String phone = loginParam.getPhone();
|
||||||
|
|
||||||
|
// 验证参数
|
||||||
|
if (captchaId == null || captchaId.trim().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (inputCaptcha == null || inputCaptcha.trim().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从Redis获取验证码
|
||||||
|
String codeKey = "sms:code:" + captchaId;
|
||||||
|
String storedValue = (String) redisService.get(codeKey);
|
||||||
|
|
||||||
|
if (storedValue == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析存储的值:手机号:验证码
|
||||||
|
String[] parts = storedValue.split(":");
|
||||||
|
if (parts.length != 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String storedPhone = parts[0];
|
||||||
|
String storedCaptcha = parts[1];
|
||||||
|
|
||||||
|
// 验证手机号和验证码是否匹配
|
||||||
|
if (!storedPhone.equals(phone) || !storedCaptcha.equals(inputCaptcha)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证码使用后删除
|
||||||
|
redisService.delete(codeKey);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package org.xyzh.auth.strategy.impl;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.xyzh.auth.strategy.LoginStrategy;
|
||||||
|
import org.xyzh.common.core.domain.LoginParam;
|
||||||
|
import org.xyzh.common.dto.sys.TbSysUserDTO;
|
||||||
|
import org.xyzh.api.system.service.SysUserService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description UsernameLoginStrategy.java文件描述 用户名登录策略
|
||||||
|
* @filename UsernameLoginStrategy.java
|
||||||
|
* @author yslg
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-12-05
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class UsernameLoginStrategy implements LoginStrategy {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SysUserService userService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLoginType() {
|
||||||
|
return "username";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validate(LoginParam loginParam) {
|
||||||
|
return loginParam.getUsername() != null && !loginParam.getUsername().trim().isEmpty()
|
||||||
|
&& loginParam.getPassword() != null && !loginParam.getPassword().trim().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TbSysUserDTO findUser(LoginParam loginParam) {
|
||||||
|
TbSysUserDTO filter = new TbSysUserDTO();
|
||||||
|
filter.setUsername(loginParam.getUsername());
|
||||||
|
TbSysUserDTO user = userService.getLoginUser(filter).getData();
|
||||||
|
if(user == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean verifyCredential(String inputCredential, String storedCredential) {
|
||||||
|
return passwordEncoder.matches(inputCredential, storedCredential);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package org.xyzh.auth.strategy.impl;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.xyzh.auth.strategy.LoginStrategy;
|
||||||
|
import org.xyzh.common.core.domain.LoginParam;
|
||||||
|
import org.xyzh.common.dto.sys.TbSysUserDTO;
|
||||||
|
import org.xyzh.api.system.service.SysUserService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description WechatLoginStrategy.java文件描述 微信登录策略
|
||||||
|
* @filename WechatLoginStrategy.java
|
||||||
|
* @author yslg
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-12-05
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class WechatLoginStrategy implements LoginStrategy {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SysUserService userService;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLoginType() {
|
||||||
|
return "wechat";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validate(LoginParam loginParam) {
|
||||||
|
return loginParam.getWechatId() != null && !loginParam.getWechatId().trim().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TbSysUserDTO findUser(LoginParam loginParam) {
|
||||||
|
TbSysUserDTO filter = new TbSysUserDTO();
|
||||||
|
filter.setWechatId(loginParam.getWechatId());
|
||||||
|
TbSysUserDTO user = userService.getLoginUser(filter).getData();
|
||||||
|
if(user == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean verifyCredential(String inputCredential, String storedCredential) {
|
||||||
|
// 微信登录通常不需要密码验证,通过微信授权码验证
|
||||||
|
// 这里可以添加微信授权验证逻辑
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.xyzh.auth.utils;
|
||||||
|
|
||||||
|
public class CapcatUtils {
|
||||||
|
/**
|
||||||
|
* 生成6位数字验证码
|
||||||
|
* @return 验证码
|
||||||
|
*/
|
||||||
|
public static String generateVerificationCode() {
|
||||||
|
return String.valueOf((int)((Math.random() * 9 + 1) * 100000));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,36 +1,94 @@
|
|||||||
|
# ================== Server ==================
|
||||||
server:
|
server:
|
||||||
port: 8081
|
port: 8181
|
||||||
servlet:
|
servlet:
|
||||||
context-path: /urban-lifeline/auth
|
context-path: /urban-lifeline/auth
|
||||||
|
|
||||||
|
# ================== Auth ====================
|
||||||
|
urban-lifeline:
|
||||||
|
auth:
|
||||||
|
enabled: false # 认证服务自己不需要认证
|
||||||
|
whitelist:
|
||||||
|
- /** # 认证服务的所有接口都放行
|
||||||
|
security:
|
||||||
|
aes:
|
||||||
|
secret-key: 1234567890qwer
|
||||||
|
# ================== Spring ==================
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: auth-service
|
||||||
|
|
||||||
|
# ================== Spring Cloud Nacos ==================
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
server-addr: 127.0.0.1:8848
|
||||||
|
namespace: dev
|
||||||
|
group: DEFAULT_GROUP
|
||||||
|
|
||||||
|
# ================== DataSource ==================
|
||||||
|
datasource:
|
||||||
|
url: jdbc:postgresql://127.0.0.1:5432/urban_lifeline
|
||||||
|
username: postgres
|
||||||
|
password: postgres
|
||||||
|
driver-class-name: org.postgresql.Driver
|
||||||
|
|
||||||
|
# ================== Redis ==================
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: 127.0.0.1 # 如果是 docker 跑的 redis,按实际 host / 端口改
|
||||||
|
port: 6379
|
||||||
|
database: 0
|
||||||
|
password: 123456 # 如果有密码就填上,没密码可以去掉这一行
|
||||||
|
|
||||||
|
# ================== SpringDoc ==================
|
||||||
springdoc:
|
springdoc:
|
||||||
api-docs:
|
api-docs:
|
||||||
# 启用 API 文档
|
|
||||||
enabled: true
|
enabled: true
|
||||||
# API 文档路径
|
|
||||||
path: /v3/api-docs
|
path: /v3/api-docs
|
||||||
swagger-ui:
|
swagger-ui:
|
||||||
# 启用 Swagger UI
|
|
||||||
enabled: true
|
enabled: true
|
||||||
# Swagger UI 路径
|
|
||||||
path: /swagger-ui.html
|
path: /swagger-ui.html
|
||||||
# 尝试请求超时时间(毫秒)
|
|
||||||
try-it-out-enabled: true
|
try-it-out-enabled: true
|
||||||
# 显示请求执行时间
|
|
||||||
show-common-extensions: true
|
show-common-extensions: true
|
||||||
# 显示请求头部
|
|
||||||
show-extensions: true
|
show-extensions: true
|
||||||
# 显示模型
|
|
||||||
show-request-duration: true
|
show-request-duration: true
|
||||||
# 过滤开关
|
|
||||||
filter: true
|
filter: true
|
||||||
# 标签排序
|
|
||||||
tags-sorter: alpha
|
tags-sorter: alpha
|
||||||
# 操作排序
|
|
||||||
operations-sorter: alpha
|
operations-sorter: alpha
|
||||||
# 分组配置(可选)
|
|
||||||
group-configs:
|
group-configs:
|
||||||
- group: 'default'
|
- group: 'default'
|
||||||
display-name: '认证服务 API'
|
display-name: '认证服务 API'
|
||||||
paths-to-match: '/**'
|
paths-to-match: '/**'
|
||||||
|
|
||||||
|
# ================== Dubbo + Nacos ==================
|
||||||
|
dubbo:
|
||||||
|
application:
|
||||||
|
name: urban-lifeline-auth
|
||||||
|
qos-enable: false
|
||||||
|
protocol:
|
||||||
|
name: dubbo
|
||||||
|
port: -1
|
||||||
|
registry:
|
||||||
|
address: nacos://127.0.0.1:8848
|
||||||
|
scan:
|
||||||
|
base-packages: org.xyzh.auth.service.impl
|
||||||
|
|
||||||
|
# ================== MyBatis ==================
|
||||||
|
mybatis-plus:
|
||||||
|
mapper-locations: classpath:mapper/**/*.xml
|
||||||
|
type-aliases-package: org.xyzh.common.dto, org.xyzh.api
|
||||||
|
|
||||||
|
# ================== JWT Configuration ==================
|
||||||
|
jwt:
|
||||||
|
secret: urban-lifeline-secret-key-2025-xyzh
|
||||||
|
expiration: 86400 # 24小时(秒)
|
||||||
|
refresh-expiration: 604800 # 7天(秒)
|
||||||
|
|
||||||
|
# ================== Logging ==================
|
||||||
|
logging:
|
||||||
|
config: classpath:log4j2.xml
|
||||||
|
charset:
|
||||||
|
console: UTF-8
|
||||||
|
file: UTF-8
|
||||||
|
|
||||||
|
|||||||
94
urbanLifelineServ/auth/src/main/resources/log4j2.xml
Normal file
94
urbanLifelineServ/auth/src/main/resources/log4j2.xml
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration status="WARN" monitorInterval="30">
|
||||||
|
<Properties>
|
||||||
|
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
|
||||||
|
<property name="FILE_PATH" value="./logs" />
|
||||||
|
<property name="FILE_NAME" value="auth-service" />
|
||||||
|
<property name="file.encoding" value="UTF-8" />
|
||||||
|
<property name="console.encoding" value="UTF-8" />
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
<appenders>
|
||||||
|
<!-- 控制台输出 -->
|
||||||
|
<console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
</console>
|
||||||
|
|
||||||
|
<!-- INFO级别日志 -->
|
||||||
|
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${FILE_NAME}-info.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<!-- WARN级别日志 -->
|
||||||
|
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/${FILE_NAME}-warn.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<!-- ERROR级别日志 -->
|
||||||
|
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/${FILE_NAME}-error.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
</appenders>
|
||||||
|
|
||||||
|
<loggers>
|
||||||
|
<!-- Nacos 日志 -->
|
||||||
|
<logger name="com.alibaba.nacos" level="info" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<!-- MyBatis 日志 -->
|
||||||
|
<logger name="org.mybatis" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<!-- Spring 日志 -->
|
||||||
|
<Logger name="org.springframework" level="info" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<!-- Auth 模块日志 -->
|
||||||
|
<Logger name="org.xyzh.auth" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFileInfo"/>
|
||||||
|
<AppenderRef ref="RollingFileWarn"/>
|
||||||
|
<AppenderRef ref="RollingFileError"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<!-- 项目通用模块日志 -->
|
||||||
|
<Logger name="org.xyzh.common" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFileInfo"/>
|
||||||
|
<AppenderRef ref="RollingFileWarn"/>
|
||||||
|
<AppenderRef ref="RollingFileError"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="Console"/>
|
||||||
|
<appender-ref ref="RollingFileInfo"/>
|
||||||
|
<appender-ref ref="RollingFileWarn"/>
|
||||||
|
<appender-ref ref="RollingFileError"/>
|
||||||
|
</root>
|
||||||
|
</loggers>
|
||||||
|
</configuration>
|
||||||
89
urbanLifelineServ/bidding/src/main/resources/application.yml
Normal file
89
urbanLifelineServ/bidding/src/main/resources/application.yml
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# ================== Server ==================
|
||||||
|
server:
|
||||||
|
port: 8186
|
||||||
|
servlet:
|
||||||
|
context-path: /urban-lifeline/bidding
|
||||||
|
|
||||||
|
# ================== Auth ====================
|
||||||
|
urban-lifeline:
|
||||||
|
auth:
|
||||||
|
enabled: true
|
||||||
|
whitelist:
|
||||||
|
- /swagger-ui/**
|
||||||
|
- /swagger-ui.html
|
||||||
|
- /v3/api-docs/**
|
||||||
|
- /webjars/**
|
||||||
|
- /favicon.ico
|
||||||
|
- /error
|
||||||
|
- /actuator/health
|
||||||
|
- /actuator/info
|
||||||
|
|
||||||
|
security:
|
||||||
|
aes:
|
||||||
|
secret-key: 1234567890qwer
|
||||||
|
|
||||||
|
# ================== Spring ==================
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: bidding-service
|
||||||
|
|
||||||
|
# ================== Spring Cloud Nacos ==================
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
server-addr: 127.0.0.1:8848
|
||||||
|
namespace: dev
|
||||||
|
group: DEFAULT_GROUP
|
||||||
|
|
||||||
|
# ================== DataSource ==================
|
||||||
|
datasource:
|
||||||
|
url: jdbc:postgresql://127.0.0.1:5432/urban_lifeline
|
||||||
|
username: postgres
|
||||||
|
password: postgres
|
||||||
|
driver-class-name: org.postgresql.Driver
|
||||||
|
|
||||||
|
# ================== Redis ==================
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: 127.0.0.1 # 如果是 docker 跑的 redis,按实际 host / 端口改
|
||||||
|
port: 6379
|
||||||
|
database: 0
|
||||||
|
password: 123456 # 如果有密码就填上,没密码可以去掉这一行
|
||||||
|
|
||||||
|
# ================== SpringDoc ==================
|
||||||
|
springdoc:
|
||||||
|
api-docs:
|
||||||
|
enabled: true
|
||||||
|
path: /v3/api-docs
|
||||||
|
swagger-ui:
|
||||||
|
enabled: true
|
||||||
|
path: /swagger-ui.html
|
||||||
|
group-configs:
|
||||||
|
- group: 'default'
|
||||||
|
display-name: '招投标服务 API'
|
||||||
|
paths-to-match: '/**'
|
||||||
|
|
||||||
|
# ================== Dubbo + Nacos ==================
|
||||||
|
dubbo:
|
||||||
|
application:
|
||||||
|
name: urban-lifeline-bidding
|
||||||
|
qos-enable: false
|
||||||
|
protocol:
|
||||||
|
name: dubbo
|
||||||
|
port: -1
|
||||||
|
registry:
|
||||||
|
address: nacos://127.0.0.1:8848
|
||||||
|
scan:
|
||||||
|
base-packages: org.xyzh.bidding.service.impl
|
||||||
|
|
||||||
|
# ================== MyBatis ==================
|
||||||
|
mybatis-plus:
|
||||||
|
mapper-locations: classpath:mapper/**/*.xml
|
||||||
|
type-aliases-package: org.xyzh.common.dto, org.xyzh.api
|
||||||
|
|
||||||
|
# ================== Logging ==================
|
||||||
|
logging:
|
||||||
|
config: classpath:log4j2.xml
|
||||||
|
charset:
|
||||||
|
console: UTF-8
|
||||||
|
file: UTF-8
|
||||||
85
urbanLifelineServ/bidding/src/main/resources/log4j2.xml
Normal file
85
urbanLifelineServ/bidding/src/main/resources/log4j2.xml
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration status="WARN" monitorInterval="30">
|
||||||
|
<Properties>
|
||||||
|
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
|
||||||
|
<property name="FILE_PATH" value="./logs" />
|
||||||
|
<property name="FILE_NAME" value="bidding-service" />
|
||||||
|
<property name="file.encoding" value="UTF-8" />
|
||||||
|
<property name="console.encoding" value="UTF-8" />
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
<appenders>
|
||||||
|
<console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
</console>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${FILE_NAME}-info.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/${FILE_NAME}-warn.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/${FILE_NAME}-error.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
</appenders>
|
||||||
|
|
||||||
|
<loggers>
|
||||||
|
<logger name="com.alibaba.nacos" level="info" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<logger name="org.mybatis" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<Logger name="org.springframework" level="info" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<Logger name="org.xyzh.bidding" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFileInfo"/>
|
||||||
|
<AppenderRef ref="RollingFileWarn"/>
|
||||||
|
<AppenderRef ref="RollingFileError"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<Logger name="org.xyzh.common" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFileInfo"/>
|
||||||
|
<AppenderRef ref="RollingFileWarn"/>
|
||||||
|
<AppenderRef ref="RollingFileError"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="Console"/>
|
||||||
|
<appender-ref ref="RollingFileInfo"/>
|
||||||
|
<appender-ref ref="RollingFileWarn"/>
|
||||||
|
<appender-ref ref="RollingFileError"/>
|
||||||
|
</root>
|
||||||
|
</loggers>
|
||||||
|
</configuration>
|
||||||
@@ -10,8 +10,8 @@ import org.springframework.web.method.support.ModelAndViewContainer;
|
|||||||
import org.xyzh.common.auth.annotation.HttpLogin;
|
import org.xyzh.common.auth.annotation.HttpLogin;
|
||||||
import org.xyzh.common.auth.token.TokenParser;
|
import org.xyzh.common.auth.token.TokenParser;
|
||||||
import org.xyzh.common.core.domain.LoginDomain;
|
import org.xyzh.common.core.domain.LoginDomain;
|
||||||
|
import org.xyzh.common.redis.service.RedisService;
|
||||||
import org.xyzh.common.utils.NonUtils;
|
import org.xyzh.common.utils.NonUtils;
|
||||||
import org.xyzh.redis.service.RedisService;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import java.util.List;
|
|||||||
* @author yslg
|
* @author yslg
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@ConfigurationProperties(prefix = "urban-lifeline.auth")
|
@ConfigurationProperties(prefix = "auth")
|
||||||
public class AuthProperties {
|
public class AuthProperties {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,16 +7,26 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.xyzh.common.auth.filter.JwtAuthenticationFilter;
|
import org.xyzh.common.auth.filter.JwtAuthenticationFilter;
|
||||||
import org.xyzh.common.auth.token.TokenParser;
|
import org.xyzh.common.auth.token.TokenParser;
|
||||||
import org.xyzh.redis.service.RedisService;
|
import org.xyzh.common.redis.service.RedisService;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableMethodSecurity
|
@EnableMethodSecurity
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码编码器 - 用于用户密码加密(使用 BCrypt)
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWT 认证过滤器 - 仅在非 Gateway 模式下启用
|
* JWT 认证过滤器 - 仅在非 Gateway 模式下启用
|
||||||
* 当 auth.gateway-mode=false 或未配置时使用
|
* 当 auth.gateway-mode=false 或未配置时使用
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import org.xyzh.common.auth.token.TokenParser;
|
|||||||
import org.xyzh.common.core.domain.ResultDomain;
|
import org.xyzh.common.core.domain.ResultDomain;
|
||||||
import org.xyzh.common.core.domain.LoginDomain;
|
import org.xyzh.common.core.domain.LoginDomain;
|
||||||
import org.xyzh.common.dto.sys.TbSysPermissionDTO;
|
import org.xyzh.common.dto.sys.TbSysPermissionDTO;
|
||||||
import org.xyzh.redis.service.RedisService;
|
import org.xyzh.common.redis.service.RedisService;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ import java.util.Map;
|
|||||||
@Component
|
@Component
|
||||||
public class JwtTokenUtil {
|
public class JwtTokenUtil {
|
||||||
|
|
||||||
@Value("${urban-lifeline.auth.jwt-secret:schoolNewsDefaultSecretKeyForJWT2025}")
|
@Value("${auth.jwt-secret:urbanLifelineDefaultSecretKeyForJWT2025}")
|
||||||
private String secret;
|
private String secret;
|
||||||
|
|
||||||
@Value("${urban-lifeline.auth.jwt-expiration:86400}")
|
@Value("${auth.jwt-expiration:86400}")
|
||||||
private Long expiration;
|
private Long expiration;
|
||||||
|
|
||||||
private SecretKey getSigningKey() {
|
private SecretKey getSigningKey() {
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package org.xyzh.common.core.exception;
|
||||||
|
|
||||||
|
public class BaseException extends RuntimeException{
|
||||||
|
private Integer code;
|
||||||
|
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
public BaseException(Integer code, String description, String message) {
|
||||||
|
super(message);
|
||||||
|
this.code = code;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCode(){
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription(){
|
||||||
|
return this.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage(){
|
||||||
|
return super.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -29,9 +29,12 @@ public class TbSysUserDTO extends BaseDTO {
|
|||||||
@Schema(description = "邮箱")
|
@Schema(description = "邮箱")
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
@Schema(description = "手机")
|
@Schema(description = "手机(加密)")
|
||||||
private String phone;
|
private String phone;
|
||||||
|
|
||||||
|
@Schema(description = "手机号哈希")
|
||||||
|
private String phone_hash;
|
||||||
|
|
||||||
@Schema(description = "微信ID")
|
@Schema(description = "微信ID")
|
||||||
private String wechatId;
|
private String wechatId;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.xyzh.redis.config;
|
package org.xyzh.common.redis.config;
|
||||||
|
|
||||||
import org.xyzh.common.core.constant.Constants;
|
import org.xyzh.common.core.constant.Constants;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.xyzh.redis.config;
|
package org.xyzh.common.redis.config;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||||
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.xyzh.redis.service;
|
package org.xyzh.common.redis.service;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
<artifactId>common-utils</artifactId>
|
<artifactId>common-utils</artifactId>
|
||||||
<version>${urban-lifeline.version}</version>
|
<version>${urban-lifeline.version}</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
<maven.compiler.target>21</maven.compiler.target>
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
@@ -30,5 +29,36 @@
|
|||||||
<groupId>org.apache.poi</groupId>
|
<groupId>org.apache.poi</groupId>
|
||||||
<artifactId>poi-ooxml</artifactId>
|
<artifactId>poi-ooxml</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MyBatis (用于类型处理器) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mybatis</groupId>
|
||||||
|
<artifactId>mybatis</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Boot (用于加密工具) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- Spring Mail for Email -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-mail</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Aliyun SMS Service -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aliyun</groupId>
|
||||||
|
<artifactId>dysmsapi20170525</artifactId>
|
||||||
|
<version>4.2.0</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- FastJson2 Spring6 支持 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.fastjson2</groupId>
|
||||||
|
<artifactId>fastjson2-extension-spring6</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package org.xyzh.common.utils.config;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.support.config.FastJsonConfig;
|
||||||
|
import com.alibaba.fastjson2.support.spring6.http.converter.FastJsonHttpMessageConverter;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description FastJson配置类 - 统一处理日期时间格式序列化
|
||||||
|
* @filename FastJsonConfig.java
|
||||||
|
* @author yslg
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-11-28
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class FastJsonConfiguration implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置FastJson消息转换器
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||||
|
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
|
||||||
|
|
||||||
|
// FastJson配置
|
||||||
|
FastJsonConfig config = new FastJsonConfig();
|
||||||
|
|
||||||
|
// 设置日期格式
|
||||||
|
config.setDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
|
// 设置字符集
|
||||||
|
converter.setDefaultCharset(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
// 设置支持的MediaType
|
||||||
|
converter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON));
|
||||||
|
|
||||||
|
// 应用配置
|
||||||
|
converter.setFastJsonConfig(config);
|
||||||
|
|
||||||
|
// 添加到转换器列表(添加到最前面,优先使用)
|
||||||
|
converters.add(0, converter);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
package org.xyzh.common.utils.crypto;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.KeyGenerator;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.GCMParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES-256-GCM 敏感数据加密工具类
|
||||||
|
* 用于加密手机号、身份证号等敏感信息
|
||||||
|
*
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-12-05
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class AesEncryptUtil {
|
||||||
|
|
||||||
|
private static final String ALGORITHM = "AES";
|
||||||
|
private static final String TRANSFORMATION = "AES/GCM/NoPadding";
|
||||||
|
private static final int KEY_SIZE = 256; // AES-256
|
||||||
|
private static final int GCM_IV_LENGTH = 12; // GCM 推荐 IV 长度
|
||||||
|
private static final int GCM_TAG_LENGTH = 128; // GCM 认证标签长度
|
||||||
|
|
||||||
|
private SecretKey secretKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从配置文件读取密钥,如果没有则生成新密钥
|
||||||
|
*/
|
||||||
|
public AesEncryptUtil(@Value("${security.aes.secret-key:}") String secretKeyString) {
|
||||||
|
if (secretKeyString == null || secretKeyString.isEmpty()) {
|
||||||
|
// 生产环境应该从配置中心或密钥管理系统获取
|
||||||
|
this.secretKey = generateKey();
|
||||||
|
System.err.println("警告: 未配置 AES 密钥,使用临时生成的密钥。生产环境请配置 security.aes.secret-key");
|
||||||
|
} else {
|
||||||
|
byte[] decodedKey = Base64.getDecoder().decode(secretKeyString);
|
||||||
|
this.secretKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, ALGORITHM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成 AES-256 密钥
|
||||||
|
*/
|
||||||
|
public static SecretKey generateKey() {
|
||||||
|
try {
|
||||||
|
KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM);
|
||||||
|
keyGen.init(KEY_SIZE, new SecureRandom());
|
||||||
|
return keyGen.generateKey();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("生成 AES 密钥失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将密钥转换为 Base64 字符串(用于配置)
|
||||||
|
*/
|
||||||
|
public static String keyToString(SecretKey key) {
|
||||||
|
return Base64.getEncoder().encodeToString(key.getEncoded());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加密字符串
|
||||||
|
*
|
||||||
|
* @param plaintext 明文
|
||||||
|
* @return Base64 编码的密文(包含 IV)
|
||||||
|
*/
|
||||||
|
public String encrypt(String plaintext) {
|
||||||
|
if (plaintext == null || plaintext.isEmpty()) {
|
||||||
|
return plaintext;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 生成随机 IV
|
||||||
|
byte[] iv = new byte[GCM_IV_LENGTH];
|
||||||
|
new SecureRandom().nextBytes(iv);
|
||||||
|
|
||||||
|
// 初始化加密器
|
||||||
|
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
||||||
|
GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
|
||||||
|
|
||||||
|
// 加密
|
||||||
|
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
// 将 IV 和密文组合在一起:[IV(12字节)][密文]
|
||||||
|
ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + ciphertext.length);
|
||||||
|
byteBuffer.put(iv);
|
||||||
|
byteBuffer.put(ciphertext);
|
||||||
|
|
||||||
|
// Base64 编码
|
||||||
|
return Base64.getEncoder().encodeToString(byteBuffer.array());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("AES 加密失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解密字符串
|
||||||
|
*
|
||||||
|
* @param ciphertext Base64 编码的密文(包含 IV)
|
||||||
|
* @return 明文
|
||||||
|
*/
|
||||||
|
public String decrypt(String ciphertext) {
|
||||||
|
if (ciphertext == null || ciphertext.isEmpty()) {
|
||||||
|
return ciphertext;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Base64 解码
|
||||||
|
byte[] decoded = Base64.getDecoder().decode(ciphertext);
|
||||||
|
|
||||||
|
// 提取 IV 和密文
|
||||||
|
ByteBuffer byteBuffer = ByteBuffer.wrap(decoded);
|
||||||
|
byte[] iv = new byte[GCM_IV_LENGTH];
|
||||||
|
byteBuffer.get(iv);
|
||||||
|
byte[] ciphertextBytes = new byte[byteBuffer.remaining()];
|
||||||
|
byteBuffer.get(ciphertextBytes);
|
||||||
|
|
||||||
|
// 初始化解密器
|
||||||
|
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
||||||
|
GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);
|
||||||
|
|
||||||
|
// 解密
|
||||||
|
byte[] plaintext = cipher.doFinal(ciphertextBytes);
|
||||||
|
return new String(plaintext, StandardCharsets.UTF_8);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("AES 解密失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加密手机号(保留前3后4位)
|
||||||
|
* 例如:13812345678 -> 138****5678
|
||||||
|
* 实际存储:加密后的完整手机号
|
||||||
|
*/
|
||||||
|
public String encryptPhone(String phone) {
|
||||||
|
return encrypt(phone);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解密手机号
|
||||||
|
*/
|
||||||
|
public String decryptPhone(String encryptedPhone) {
|
||||||
|
return decrypt(encryptedPhone);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加密身份证号(保留前6后4位)
|
||||||
|
* 例如:110101199001011234 -> 110101********1234
|
||||||
|
* 实际存储:加密后的完整身份证号
|
||||||
|
*/
|
||||||
|
public String encryptIdCard(String idCard) {
|
||||||
|
return encrypt(idCard);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解密身份证号
|
||||||
|
*/
|
||||||
|
public String decryptIdCard(String encryptedIdCard) {
|
||||||
|
return decrypt(encryptedIdCard);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 脱敏显示手机号
|
||||||
|
*/
|
||||||
|
public static String maskPhone(String phone) {
|
||||||
|
if (phone == null || phone.length() < 11) {
|
||||||
|
return phone;
|
||||||
|
}
|
||||||
|
return phone.substring(0, 3) + "****" + phone.substring(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 脱敏显示身份证号
|
||||||
|
*/
|
||||||
|
public static String maskIdCard(String idCard) {
|
||||||
|
if (idCard == null || idCard.length() < 18) {
|
||||||
|
return idCard;
|
||||||
|
}
|
||||||
|
return idCard.substring(0, 6) + "********" + idCard.substring(14);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package org.xyzh.common.utils.crypto;
|
||||||
|
|
||||||
|
import org.apache.ibatis.type.BaseTypeHandler;
|
||||||
|
import org.apache.ibatis.type.JdbcType;
|
||||||
|
import org.apache.ibatis.type.MappedTypes;
|
||||||
|
|
||||||
|
import java.sql.CallableStatement;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MyBatis 加密字段类型处理器
|
||||||
|
* 自动对数据库字段进行加密/解密
|
||||||
|
*
|
||||||
|
* 使用方式:
|
||||||
|
* 在实体类字段上添加注解:
|
||||||
|
* @TableField(typeHandler = EncryptedStringTypeHandler.class)
|
||||||
|
* private String phone;
|
||||||
|
*
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-12-05
|
||||||
|
*/
|
||||||
|
@MappedTypes({String.class})
|
||||||
|
public class EncryptedStringTypeHandler extends BaseTypeHandler<String> {
|
||||||
|
|
||||||
|
private static AesEncryptUtil aesEncryptUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置加密工具(由 Spring 注入)
|
||||||
|
*/
|
||||||
|
public static void setAesEncryptUtil(AesEncryptUtil util) {
|
||||||
|
aesEncryptUtil = util;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
|
||||||
|
// 写入数据库时加密
|
||||||
|
if (aesEncryptUtil != null && parameter != null) {
|
||||||
|
ps.setString(i, aesEncryptUtil.encrypt(parameter));
|
||||||
|
} else {
|
||||||
|
ps.setString(i, parameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||||
|
// 从数据库读取时解密
|
||||||
|
String encrypted = rs.getString(columnName);
|
||||||
|
return decrypt(encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||||
|
String encrypted = rs.getString(columnIndex);
|
||||||
|
return decrypt(encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||||
|
String encrypted = cs.getString(columnIndex);
|
||||||
|
return decrypt(encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String decrypt(String encrypted) {
|
||||||
|
if (aesEncryptUtil != null && encrypted != null && !encrypted.isEmpty()) {
|
||||||
|
try {
|
||||||
|
return aesEncryptUtil.decrypt(encrypted);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果解密失败,可能是旧数据,返回原值
|
||||||
|
return encrypted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return encrypted;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package org.xyzh.common.utils.crypto;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 敏感数据处理工具类
|
||||||
|
* 支持加密存储 + 哈希查询
|
||||||
|
*
|
||||||
|
* 数据库设计:
|
||||||
|
* - phone_encrypted: 加密后的完整手机号(用于显示和解密)
|
||||||
|
* - phone_hash: 手机号的哈希值(用于查询,建立索引)
|
||||||
|
*
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-12-05
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class SensitiveDataUtil {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AesEncryptUtil aesEncryptUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成 SHA-256 哈希值(用于查询索引)
|
||||||
|
*/
|
||||||
|
public String hash(String plaintext) {
|
||||||
|
if (plaintext == null || plaintext.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||||
|
byte[] hashBytes = digest.digest(plaintext.getBytes(StandardCharsets.UTF_8));
|
||||||
|
return Base64.getEncoder().encodeToString(hashBytes);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("哈希计算失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理手机号:返回加密值和哈希值
|
||||||
|
*/
|
||||||
|
public PhoneData processPhone(String phone) {
|
||||||
|
if (phone == null || phone.isEmpty()) {
|
||||||
|
return new PhoneData(null, null);
|
||||||
|
}
|
||||||
|
return new PhoneData(
|
||||||
|
aesEncryptUtil.encryptPhone(phone),
|
||||||
|
hash(phone)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理身份证号:返回加密值和哈希值
|
||||||
|
*/
|
||||||
|
public IdCardData processIdCard(String idCard) {
|
||||||
|
if (idCard == null || idCard.isEmpty()) {
|
||||||
|
return new IdCardData(null, null);
|
||||||
|
}
|
||||||
|
return new IdCardData(
|
||||||
|
aesEncryptUtil.encryptIdCard(idCard),
|
||||||
|
hash(idCard)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手机号数据对象
|
||||||
|
*/
|
||||||
|
public static class PhoneData {
|
||||||
|
private String encrypted; // 加密后的值(存储在 phone_encrypted)
|
||||||
|
private String hash; // 哈希值(存储在 phone_hash,用于查询)
|
||||||
|
|
||||||
|
public PhoneData(String encrypted, String hash) {
|
||||||
|
this.encrypted = encrypted;
|
||||||
|
this.hash = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEncrypted() {
|
||||||
|
return encrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHash() {
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 身份证号数据对象
|
||||||
|
*/
|
||||||
|
public static class IdCardData {
|
||||||
|
private String encrypted; // 加密后的值
|
||||||
|
private String hash; // 哈希值
|
||||||
|
|
||||||
|
public IdCardData(String encrypted, String hash) {
|
||||||
|
this.encrypted = encrypted;
|
||||||
|
this.hash = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEncrypted() {
|
||||||
|
return encrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHash() {
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
80
urbanLifelineServ/crontab/src/main/resources/application.yml
Normal file
80
urbanLifelineServ/crontab/src/main/resources/application.yml
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# ================== Server ==================
|
||||||
|
server:
|
||||||
|
port: 8189
|
||||||
|
servlet:
|
||||||
|
context-path: /urban-lifeline/crontab
|
||||||
|
|
||||||
|
# ================== Auth ====================
|
||||||
|
urban-lifeline:
|
||||||
|
auth:
|
||||||
|
enabled: false # 定时任务服务通常不需要认证
|
||||||
|
|
||||||
|
security:
|
||||||
|
aes:
|
||||||
|
secret-key: 1234567890qwer
|
||||||
|
|
||||||
|
# ================== Spring ==================
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: crontab-service
|
||||||
|
|
||||||
|
# ================== Spring Cloud Nacos ==================
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
server-addr: 127.0.0.1:8848
|
||||||
|
namespace: dev
|
||||||
|
group: DEFAULT_GROUP
|
||||||
|
|
||||||
|
# ================== DataSource ==================
|
||||||
|
datasource:
|
||||||
|
url: jdbc:postgresql://127.0.0.1:5432/urban_lifeline
|
||||||
|
username: postgres
|
||||||
|
password: postgres
|
||||||
|
driver-class-name: org.postgresql.Driver
|
||||||
|
|
||||||
|
# ================== Redis ==================
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: 127.0.0.1 # 如果是 docker 跑的 redis,按实际 host / 端口改
|
||||||
|
port: 6379
|
||||||
|
database: 0
|
||||||
|
password: 123456 # 如果有密码就填上,没密码可以去掉这一行
|
||||||
|
|
||||||
|
# ================== SpringDoc ==================
|
||||||
|
springdoc:
|
||||||
|
api-docs:
|
||||||
|
enabled: true
|
||||||
|
path: /v3/api-docs
|
||||||
|
swagger-ui:
|
||||||
|
enabled: true
|
||||||
|
path: /swagger-ui.html
|
||||||
|
group-configs:
|
||||||
|
- group: 'default'
|
||||||
|
display-name: '定时任务服务 API'
|
||||||
|
paths-to-match: '/**'
|
||||||
|
|
||||||
|
# ================== Dubbo + Nacos ==================
|
||||||
|
dubbo:
|
||||||
|
application:
|
||||||
|
name: urban-lifeline-crontab
|
||||||
|
qos-enable: false
|
||||||
|
protocol:
|
||||||
|
name: dubbo
|
||||||
|
port: -1
|
||||||
|
registry:
|
||||||
|
address: nacos://127.0.0.1:8848
|
||||||
|
scan:
|
||||||
|
base-packages: org.xyzh.crontab.service.impl
|
||||||
|
|
||||||
|
# ================== MyBatis ==================
|
||||||
|
mybatis-plus:
|
||||||
|
mapper-locations: classpath:mapper/**/*.xml
|
||||||
|
type-aliases-package: org.xyzh.common.dto, org.xyzh.api
|
||||||
|
|
||||||
|
# ================== Logging ==================
|
||||||
|
logging:
|
||||||
|
config: classpath:log4j2.xml
|
||||||
|
charset:
|
||||||
|
console: UTF-8
|
||||||
|
file: UTF-8
|
||||||
85
urbanLifelineServ/crontab/src/main/resources/log4j2.xml
Normal file
85
urbanLifelineServ/crontab/src/main/resources/log4j2.xml
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration status="WARN" monitorInterval="30">
|
||||||
|
<Properties>
|
||||||
|
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
|
||||||
|
<property name="FILE_PATH" value="./logs" />
|
||||||
|
<property name="FILE_NAME" value="crontab-service" />
|
||||||
|
<property name="file.encoding" value="UTF-8" />
|
||||||
|
<property name="console.encoding" value="UTF-8" />
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
<appenders>
|
||||||
|
<console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
</console>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${FILE_NAME}-info.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/${FILE_NAME}-warn.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/${FILE_NAME}-error.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
</appenders>
|
||||||
|
|
||||||
|
<loggers>
|
||||||
|
<logger name="com.alibaba.nacos" level="info" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<logger name="org.mybatis" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<Logger name="org.springframework" level="info" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<Logger name="org.xyzh.crontab" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFileInfo"/>
|
||||||
|
<AppenderRef ref="RollingFileWarn"/>
|
||||||
|
<AppenderRef ref="RollingFileError"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<Logger name="org.xyzh.common" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFileInfo"/>
|
||||||
|
<AppenderRef ref="RollingFileWarn"/>
|
||||||
|
<AppenderRef ref="RollingFileError"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="Console"/>
|
||||||
|
<appender-ref ref="RollingFileInfo"/>
|
||||||
|
<appender-ref ref="RollingFileWarn"/>
|
||||||
|
<appender-ref ref="RollingFileError"/>
|
||||||
|
</root>
|
||||||
|
</loggers>
|
||||||
|
</configuration>
|
||||||
@@ -34,6 +34,18 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-logging</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Log4j2 日志 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-log4j2</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Spring Boot Actuator -->
|
<!-- Spring Boot Actuator -->
|
||||||
|
|||||||
96
urbanLifelineServ/file/src/main/resources/application.yml
Normal file
96
urbanLifelineServ/file/src/main/resources/application.yml
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
# ================== Server ==================
|
||||||
|
server:
|
||||||
|
port: 8184
|
||||||
|
servlet:
|
||||||
|
context-path: /urban-lifeline/file
|
||||||
|
|
||||||
|
# ================== Auth ====================
|
||||||
|
urban-lifeline:
|
||||||
|
auth:
|
||||||
|
enabled: true
|
||||||
|
whitelist:
|
||||||
|
- /swagger-ui/**
|
||||||
|
- /swagger-ui.html
|
||||||
|
- /v3/api-docs/**
|
||||||
|
- /webjars/**
|
||||||
|
- /favicon.ico
|
||||||
|
- /error
|
||||||
|
- /actuator/health
|
||||||
|
- /actuator/info
|
||||||
|
|
||||||
|
security:
|
||||||
|
aes:
|
||||||
|
secret-key: 1234567890qwer
|
||||||
|
|
||||||
|
# ================== Spring ==================
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: file-service
|
||||||
|
|
||||||
|
# ================== Spring Cloud Nacos ==================
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
server-addr: 127.0.0.1:8848
|
||||||
|
namespace: dev
|
||||||
|
group: DEFAULT_GROUP
|
||||||
|
|
||||||
|
# ================== DataSource ==================
|
||||||
|
datasource:
|
||||||
|
url: jdbc:postgresql://127.0.0.1:5432/urban_lifeline
|
||||||
|
username: postgres
|
||||||
|
password: postgres
|
||||||
|
driver-class-name: org.postgresql.Driver
|
||||||
|
|
||||||
|
# ================== Redis ==================
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: 127.0.0.1 # 如果是 docker 跑的 redis,按实际 host / 端口改
|
||||||
|
port: 6379
|
||||||
|
database: 0
|
||||||
|
password: 123456 # 如果有密码就填上,没密码可以去掉这一行
|
||||||
|
|
||||||
|
# ================== 文件上传配置 ==================
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
enabled: true
|
||||||
|
max-file-size: 100MB
|
||||||
|
max-request-size: 100MB
|
||||||
|
|
||||||
|
# ================== SpringDoc ==================
|
||||||
|
springdoc:
|
||||||
|
api-docs:
|
||||||
|
enabled: true
|
||||||
|
path: /v3/api-docs
|
||||||
|
swagger-ui:
|
||||||
|
enabled: true
|
||||||
|
path: /swagger-ui.html
|
||||||
|
group-configs:
|
||||||
|
- group: 'default'
|
||||||
|
display-name: '文件服务 API'
|
||||||
|
paths-to-match: '/**'
|
||||||
|
|
||||||
|
# ================== Dubbo + Nacos ==================
|
||||||
|
dubbo:
|
||||||
|
application:
|
||||||
|
name: urban-lifeline-file
|
||||||
|
qos-enable: false
|
||||||
|
protocol:
|
||||||
|
name: dubbo
|
||||||
|
port: -1
|
||||||
|
registry:
|
||||||
|
address: nacos://127.0.0.1:8848
|
||||||
|
scan:
|
||||||
|
base-packages: org.xyzh.file.service.impl
|
||||||
|
|
||||||
|
# ================== MyBatis ==================
|
||||||
|
mybatis-plus:
|
||||||
|
mapper-locations: classpath:mapper/**/*.xml
|
||||||
|
type-aliases-package: org.xyzh.common.dto, org.xyzh.api
|
||||||
|
|
||||||
|
# ================== Logging ==================
|
||||||
|
logging:
|
||||||
|
config: classpath:log4j2.xml
|
||||||
|
charset:
|
||||||
|
console: UTF-8
|
||||||
|
file: UTF-8
|
||||||
85
urbanLifelineServ/file/src/main/resources/log4j2.xml
Normal file
85
urbanLifelineServ/file/src/main/resources/log4j2.xml
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration status="WARN" monitorInterval="30">
|
||||||
|
<Properties>
|
||||||
|
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
|
||||||
|
<property name="FILE_PATH" value="./logs" />
|
||||||
|
<property name="FILE_NAME" value="file-service" />
|
||||||
|
<property name="file.encoding" value="UTF-8" />
|
||||||
|
<property name="console.encoding" value="UTF-8" />
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
<appenders>
|
||||||
|
<console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
</console>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${FILE_NAME}-info.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/${FILE_NAME}-warn.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/${FILE_NAME}-error.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
</appenders>
|
||||||
|
|
||||||
|
<loggers>
|
||||||
|
<logger name="com.alibaba.nacos" level="info" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<logger name="org.mybatis" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<Logger name="org.springframework" level="info" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<Logger name="org.xyzh.file" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFileInfo"/>
|
||||||
|
<AppenderRef ref="RollingFileWarn"/>
|
||||||
|
<AppenderRef ref="RollingFileError"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<Logger name="org.xyzh.common" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFileInfo"/>
|
||||||
|
<AppenderRef ref="RollingFileWarn"/>
|
||||||
|
<AppenderRef ref="RollingFileError"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="Console"/>
|
||||||
|
<appender-ref ref="RollingFileInfo"/>
|
||||||
|
<appender-ref ref="RollingFileWarn"/>
|
||||||
|
<appender-ref ref="RollingFileError"/>
|
||||||
|
</root>
|
||||||
|
</loggers>
|
||||||
|
</configuration>
|
||||||
@@ -23,18 +23,63 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.cloud</groupId>
|
<groupId>org.springframework.cloud</groupId>
|
||||||
<artifactId>spring-cloud-starter-gateway</artifactId>
|
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-logging</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<!-- 排除旧的 gateway-server,使用新的 webflux 版本 -->
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-gateway-server</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 明确使用新的 WebFlux Gateway Server(推荐) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-gateway-server-webflux</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Nacos 服务注册与发现 -->
|
<!-- Nacos 服务注册与发现 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.cloud</groupId>
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-logging</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.alibaba.nacos</groupId>
|
||||||
|
<artifactId>nacos-logback-adapter-12</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.alibaba.nacos</groupId>
|
||||||
|
<artifactId>logback-adapter</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Nacos 配置中心 -->
|
<!-- Nacos 配置中心 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.cloud</groupId>
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-logging</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.alibaba.nacos</groupId>
|
||||||
|
<artifactId>nacos-logback-adapter-12</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.alibaba.nacos</groupId>
|
||||||
|
<artifactId>logback-adapter</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 负载均衡 -->
|
<!-- 负载均衡 -->
|
||||||
@@ -71,12 +116,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
</dependency>
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
<!-- Log4j2 日志 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-log4j2</artifactId>
|
<artifactId>spring-boot-starter-logging</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- WebFlux(Gateway依赖) -->
|
<!-- WebFlux(Gateway依赖) -->
|
||||||
|
|||||||
@@ -2,15 +2,31 @@ package org.xyzh.gateway;
|
|||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.context.annotation.FilterType;
|
||||||
|
import org.xyzh.common.auth.config.SecurityConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Gateway 网关启动类
|
* @description Gateway 网关启动类
|
||||||
* @author yslg
|
* @author yslg
|
||||||
* @since 2025-12-02
|
* @since 2025-12-02
|
||||||
*/
|
*/
|
||||||
@SpringBootApplication
|
@SpringBootApplication(exclude = {
|
||||||
|
DataSourceAutoConfiguration.class // Gateway 不需要数据源
|
||||||
|
})
|
||||||
@EnableDiscoveryClient
|
@EnableDiscoveryClient
|
||||||
|
@ComponentScan(
|
||||||
|
basePackages = {
|
||||||
|
"org.xyzh.gateway", // 当前 gateway 模块
|
||||||
|
"org.xyzh.common" // 公共模块(包括 common-auth)
|
||||||
|
},
|
||||||
|
excludeFilters = {
|
||||||
|
// 排除 Spring MVC 的 SecurityConfig,Gateway 使用 WebFlux Security
|
||||||
|
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = SecurityConfig.class)
|
||||||
|
}
|
||||||
|
)
|
||||||
public class GatewayApplication {
|
public class GatewayApplication {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(GatewayApplication.class, args);
|
SpringApplication.run(GatewayApplication.class, args);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
spring:
|
spring:
|
||||||
cloud:
|
cloud:
|
||||||
gateway:
|
gateway:
|
||||||
|
server:
|
||||||
|
webflux:
|
||||||
routes:
|
routes:
|
||||||
# 开发环境可以添加更详细的路由配置或测试路由
|
# 开发环境可以添加更详细的路由配置或测试路由
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
server:
|
server:
|
||||||
port: 8080
|
port: 8180
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
application:
|
application:
|
||||||
name: gateway-service
|
name: gateway-service
|
||||||
|
|
||||||
|
# Gateway 必须使用 reactive 模式(WebFlux),不能使用 Spring MVC
|
||||||
|
main:
|
||||||
|
web-application-type: reactive
|
||||||
|
|
||||||
# 配置中心
|
# 配置中心
|
||||||
cloud:
|
cloud:
|
||||||
nacos:
|
nacos:
|
||||||
@@ -18,8 +22,10 @@ spring:
|
|||||||
namespace: dev
|
namespace: dev
|
||||||
group: DEFAULT_GROUP
|
group: DEFAULT_GROUP
|
||||||
|
|
||||||
# Gateway 路由配置
|
# Gateway 路由配置(使用新的 webflux 配置路径)
|
||||||
gateway:
|
gateway:
|
||||||
|
server:
|
||||||
|
webflux:
|
||||||
# 服务发现路由(自动路由)
|
# 服务发现路由(自动路由)
|
||||||
discovery:
|
discovery:
|
||||||
locator:
|
locator:
|
||||||
@@ -33,7 +39,7 @@ spring:
|
|||||||
predicates:
|
predicates:
|
||||||
- Path=/auth/**
|
- Path=/auth/**
|
||||||
filters:
|
filters:
|
||||||
- StripPrefix=1
|
- RewritePath=/auth/(?<segment>.*), /urban-lifeline/auth/$\{segment}
|
||||||
- name: RequestRateLimiter
|
- name: RequestRateLimiter
|
||||||
args:
|
args:
|
||||||
redis-rate-limiter.replenishRate: 100
|
redis-rate-limiter.replenishRate: 100
|
||||||
@@ -45,7 +51,7 @@ spring:
|
|||||||
predicates:
|
predicates:
|
||||||
- Path=/system/**
|
- Path=/system/**
|
||||||
filters:
|
filters:
|
||||||
- StripPrefix=1
|
- RewritePath=/system/(?<segment>.*), /urban-lifeline/system/$\{segment}
|
||||||
|
|
||||||
# ==================== 日志服务路由 ====================
|
# ==================== 日志服务路由 ====================
|
||||||
- id: log-service
|
- id: log-service
|
||||||
@@ -53,7 +59,7 @@ spring:
|
|||||||
predicates:
|
predicates:
|
||||||
- Path=/log/**
|
- Path=/log/**
|
||||||
filters:
|
filters:
|
||||||
- StripPrefix=1
|
- RewritePath=/log/(?<segment>.*), /urban-lifeline/log/$\{segment}
|
||||||
|
|
||||||
# ==================== 文件服务路由 ====================
|
# ==================== 文件服务路由 ====================
|
||||||
- id: file-service
|
- id: file-service
|
||||||
@@ -61,7 +67,55 @@ spring:
|
|||||||
predicates:
|
predicates:
|
||||||
- Path=/file/**
|
- Path=/file/**
|
||||||
filters:
|
filters:
|
||||||
- StripPrefix=1
|
- RewritePath=/file/(?<segment>.*), /urban-lifeline/file/$\{segment}
|
||||||
|
|
||||||
|
# ==================== 消息服务路由 ====================
|
||||||
|
- id: message-service
|
||||||
|
uri: lb://message-service
|
||||||
|
predicates:
|
||||||
|
- Path=/message/**
|
||||||
|
filters:
|
||||||
|
- RewritePath=/message/(?<segment>.*), /urban-lifeline/message/$\{segment}
|
||||||
|
|
||||||
|
# ==================== 招投标服务路由 ====================
|
||||||
|
- id: bidding-service
|
||||||
|
uri: lb://bidding-service
|
||||||
|
predicates:
|
||||||
|
- Path=/bidding/**
|
||||||
|
filters:
|
||||||
|
- RewritePath=/bidding/(?<segment>.*), /urban-lifeline/bidding/$\{segment}
|
||||||
|
|
||||||
|
# ==================== 平台服务路由 ====================
|
||||||
|
- id: platform-service
|
||||||
|
uri: lb://platform-service
|
||||||
|
predicates:
|
||||||
|
- Path=/platform/**
|
||||||
|
filters:
|
||||||
|
- RewritePath=/platform/(?<segment>.*), /urban-lifeline/platform/$\{segment}
|
||||||
|
|
||||||
|
# ==================== 工单服务路由 ====================
|
||||||
|
- id: workcase-service
|
||||||
|
uri: lb://workcase-service
|
||||||
|
predicates:
|
||||||
|
- Path=/workcase/**
|
||||||
|
filters:
|
||||||
|
- RewritePath=/workcase/(?<segment>.*), /urban-lifeline/workcase/$\{segment}
|
||||||
|
|
||||||
|
# ==================== 定时任务服务路由 ====================
|
||||||
|
- id: crontab-service
|
||||||
|
uri: lb://crontab-service
|
||||||
|
predicates:
|
||||||
|
- Path=/crontab/**
|
||||||
|
filters:
|
||||||
|
- RewritePath=/crontab/(?<segment>.*), /urban-lifeline/crontab/$\{segment}
|
||||||
|
|
||||||
|
# ==================== AI Agent 服务路由 ====================
|
||||||
|
- id: agent-service
|
||||||
|
uri: lb://agent-service
|
||||||
|
predicates:
|
||||||
|
- Path=/agent/**
|
||||||
|
filters:
|
||||||
|
- RewritePath=/agent/(?<segment>.*), /urban-lifeline/agent/$\{segment}
|
||||||
|
|
||||||
# 全局跨域配置
|
# 全局跨域配置
|
||||||
globalcors:
|
globalcors:
|
||||||
@@ -77,12 +131,18 @@ spring:
|
|||||||
allowedHeaders: "*"
|
allowedHeaders: "*"
|
||||||
allowCredentials: true
|
allowCredentials: true
|
||||||
maxAge: 3600
|
maxAge: 3600
|
||||||
|
datasource:
|
||||||
|
# 按你的实际库名改一下,比如 urban-lifeline_system
|
||||||
|
url: jdbc:postgresql://127.0.0.1:5432/urban_lifeline # 换成你的 PG 库名
|
||||||
|
username: postgres # PG 用户
|
||||||
|
password: postgres # PG 密码
|
||||||
|
driver-class-name: org.postgresql.Driver
|
||||||
# Redis 配置(用于限流、缓存)
|
# Redis 配置(用于限流、缓存)
|
||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
host: ${REDIS_HOST:localhost}
|
host: ${REDIS_HOST:localhost}
|
||||||
port: ${REDIS_PORT:6379}
|
port: ${REDIS_PORT:6379}
|
||||||
|
password: 123456
|
||||||
database: 0
|
database: 0
|
||||||
timeout: 5000ms
|
timeout: 5000ms
|
||||||
lettuce:
|
lettuce:
|
||||||
@@ -95,14 +155,14 @@ spring:
|
|||||||
# 认证配置
|
# 认证配置
|
||||||
auth:
|
auth:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
gateway-mode: true
|
||||||
token-header: Authorization
|
token-header: Authorization
|
||||||
token-prefix: "Bearer "
|
token-prefix: "Bearer "
|
||||||
# 认证接口白名单(login/logout/captcha/refresh)
|
# 认证接口白名单(login/logout/captcha/refresh)
|
||||||
auth-paths:
|
login-path: /urban-lifeline/auth/login
|
||||||
- /auth/login
|
logout-path: /urban-lifeline/auth/logout
|
||||||
- /auth/logout
|
captcha-path: /urban-lifeline/auth/captcha
|
||||||
- /auth/captcha
|
refresh-path: /urban-lifeline/auth/refresh
|
||||||
- /auth/refresh
|
|
||||||
# 通用白名单(Swagger、健康检查等)
|
# 通用白名单(Swagger、健康检查等)
|
||||||
whitelist:
|
whitelist:
|
||||||
- /actuator/**
|
- /actuator/**
|
||||||
@@ -113,7 +173,9 @@ auth:
|
|||||||
- /doc.html
|
- /doc.html
|
||||||
- /favicon.ico
|
- /favicon.ico
|
||||||
- /error
|
- /error
|
||||||
|
security:
|
||||||
|
aes:
|
||||||
|
secret-key: 1234567890qwer
|
||||||
# Actuator 监控端点
|
# Actuator 监控端点
|
||||||
management:
|
management:
|
||||||
endpoints:
|
endpoints:
|
||||||
@@ -124,10 +186,9 @@ management:
|
|||||||
health:
|
health:
|
||||||
show-details: always
|
show-details: always
|
||||||
|
|
||||||
# 日志配置
|
# 日志配置(详细配置见 log4j2.xml)
|
||||||
logging:
|
logging:
|
||||||
level:
|
config: classpath:log4j2.xml
|
||||||
org.springframework.cloud.gateway: DEBUG
|
charset:
|
||||||
org.xyzh.gateway: DEBUG
|
console: UTF-8
|
||||||
pattern:
|
file: UTF-8
|
||||||
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"
|
|
||||||
66
urbanLifelineServ/gateway/src/main/resources/log4j2.xml
Normal file
66
urbanLifelineServ/gateway/src/main/resources/log4j2.xml
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Configuration status="WARN" monitorInterval="30">
|
||||||
|
<Properties>
|
||||||
|
<Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</Property>
|
||||||
|
<Property name="LOG_PATH">logs</Property>
|
||||||
|
<Property name="APP_NAME">gateway</Property>
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
<Appenders>
|
||||||
|
<!-- 控制台输出 -->
|
||||||
|
<Console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||||
|
</Console>
|
||||||
|
|
||||||
|
<!-- 所有日志文件 -->
|
||||||
|
<RollingFile name="RollingFile" fileName="${LOG_PATH}/${APP_NAME}.log"
|
||||||
|
filePattern="${LOG_PATH}/${APP_NAME}-%d{yyyy-MM-dd}-%i.log.gz">
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<!-- 错误日志文件 -->
|
||||||
|
<RollingFile name="ErrorFile" fileName="${LOG_PATH}/${APP_NAME}-error.log"
|
||||||
|
filePattern="${LOG_PATH}/${APP_NAME}-error-%d{yyyy-MM-dd}-%i.log.gz">
|
||||||
|
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
</Appenders>
|
||||||
|
|
||||||
|
<Loggers>
|
||||||
|
<!-- Nacos 日志配置 -->
|
||||||
|
<Logger name="com.alibaba.nacos" level="INFO" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFile"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<!-- Spring Cloud Gateway 日志 -->
|
||||||
|
<Logger name="org.springframework.cloud.gateway" level="DEBUG" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFile"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<!-- 项目日志 -->
|
||||||
|
<Logger name="org.xyzh" level="DEBUG" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFile"/>
|
||||||
|
<AppenderRef ref="ErrorFile"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<!-- Root Logger -->
|
||||||
|
<Root level="INFO">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFile"/>
|
||||||
|
<AppenderRef ref="ErrorFile"/>
|
||||||
|
</Root>
|
||||||
|
</Loggers>
|
||||||
|
</Configuration>
|
||||||
@@ -35,6 +35,18 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-logging</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Log4j2 日志 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-log4j2</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Dubbo Spring Boot Starter -->
|
<!-- Dubbo Spring Boot Starter -->
|
||||||
|
|||||||
@@ -1,36 +1,97 @@
|
|||||||
|
# ================== Server ==================
|
||||||
server:
|
server:
|
||||||
port: 8083
|
port: 8183
|
||||||
servlet:
|
servlet:
|
||||||
context-path: /urban-lifeline/log
|
context-path: /urban-lifeline/log
|
||||||
|
|
||||||
|
# ================== Auth ====================
|
||||||
|
urban-lifeline:
|
||||||
|
auth:
|
||||||
|
enabled: true
|
||||||
|
whitelist:
|
||||||
|
- /swagger-ui/**
|
||||||
|
- /swagger-ui.html
|
||||||
|
- /v3/api-docs/**
|
||||||
|
- /webjars/**
|
||||||
|
- /favicon.ico
|
||||||
|
- /error
|
||||||
|
- /actuator/health
|
||||||
|
- /actuator/info
|
||||||
|
|
||||||
|
security:
|
||||||
|
aes:
|
||||||
|
secret-key: 1234567890qwer
|
||||||
|
|
||||||
|
# ================== Spring ==================
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: log-service
|
||||||
|
|
||||||
|
# ================== Spring Cloud Nacos ==================
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
server-addr: 127.0.0.1:8848
|
||||||
|
namespace: dev
|
||||||
|
group: DEFAULT_GROUP
|
||||||
|
|
||||||
|
# ================== DataSource ==================
|
||||||
|
datasource:
|
||||||
|
url: jdbc:postgresql://127.0.0.1:5432/urban_lifeline
|
||||||
|
username: postgres
|
||||||
|
password: postgres
|
||||||
|
driver-class-name: org.postgresql.Driver
|
||||||
|
|
||||||
|
# ================== Redis ==================
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: 127.0.0.1 # 如果是 docker 跑的 redis,按实际 host / 端口改
|
||||||
|
port: 6379
|
||||||
|
database: 0
|
||||||
|
password: 123456 # 如果有密码就填上,没密码可以去掉这一行
|
||||||
|
|
||||||
|
# ================== SpringDoc ==================
|
||||||
springdoc:
|
springdoc:
|
||||||
api-docs:
|
api-docs:
|
||||||
# 启用 API 文档
|
|
||||||
enabled: true
|
enabled: true
|
||||||
# API 文档路径
|
|
||||||
path: /v3/api-docs
|
path: /v3/api-docs
|
||||||
swagger-ui:
|
swagger-ui:
|
||||||
# 启用 Swagger UI
|
|
||||||
enabled: true
|
enabled: true
|
||||||
# Swagger UI 路径
|
|
||||||
path: /swagger-ui.html
|
path: /swagger-ui.html
|
||||||
# 尝试请求超时时间(毫秒)
|
|
||||||
try-it-out-enabled: true
|
try-it-out-enabled: true
|
||||||
# 显示请求执行时间
|
|
||||||
show-common-extensions: true
|
show-common-extensions: true
|
||||||
# 显示请求头部
|
|
||||||
show-extensions: true
|
show-extensions: true
|
||||||
# 显示模型
|
|
||||||
show-request-duration: true
|
show-request-duration: true
|
||||||
# 过滤开关
|
|
||||||
filter: true
|
filter: true
|
||||||
# 标签排序
|
|
||||||
tags-sorter: alpha
|
tags-sorter: alpha
|
||||||
# 操作排序
|
|
||||||
operations-sorter: alpha
|
operations-sorter: alpha
|
||||||
# 分组配置(可选)
|
|
||||||
group-configs:
|
group-configs:
|
||||||
- group: 'default'
|
- group: 'default'
|
||||||
display-name: '日志服务 API'
|
display-name: '日志服务 API'
|
||||||
paths-to-match: '/**'
|
paths-to-match: '/**'
|
||||||
|
|
||||||
|
# ================== Dubbo + Nacos ==================
|
||||||
|
dubbo:
|
||||||
|
application:
|
||||||
|
name: urban-lifeline-log
|
||||||
|
qos-enable: false
|
||||||
|
protocol:
|
||||||
|
name: dubbo
|
||||||
|
port: -1
|
||||||
|
registry:
|
||||||
|
address: nacos://127.0.0.1:8848
|
||||||
|
scan:
|
||||||
|
base-packages: org.xyzh.log.service.impl
|
||||||
|
|
||||||
|
# ================== MyBatis ==================
|
||||||
|
mybatis-plus:
|
||||||
|
mapper-locations: classpath:mapper/**/*.xml
|
||||||
|
type-aliases-package: org.xyzh.common.dto, org.xyzh.api
|
||||||
|
|
||||||
|
# ================== Logging ==================
|
||||||
|
logging:
|
||||||
|
config: classpath:log4j2.xml
|
||||||
|
charset:
|
||||||
|
console: UTF-8
|
||||||
|
file: UTF-8
|
||||||
|
|
||||||
|
|||||||
85
urbanLifelineServ/log/src/main/resources/log4j2.xml
Normal file
85
urbanLifelineServ/log/src/main/resources/log4j2.xml
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration status="WARN" monitorInterval="30">
|
||||||
|
<Properties>
|
||||||
|
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
|
||||||
|
<property name="FILE_PATH" value="./logs" />
|
||||||
|
<property name="FILE_NAME" value="log-service" />
|
||||||
|
<property name="file.encoding" value="UTF-8" />
|
||||||
|
<property name="console.encoding" value="UTF-8" />
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
<appenders>
|
||||||
|
<console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
</console>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${FILE_NAME}-info.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/${FILE_NAME}-warn.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/${FILE_NAME}-error.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
</appenders>
|
||||||
|
|
||||||
|
<loggers>
|
||||||
|
<logger name="com.alibaba.nacos" level="info" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<logger name="org.mybatis" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<Logger name="org.springframework" level="info" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<Logger name="org.xyzh.log" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFileInfo"/>
|
||||||
|
<AppenderRef ref="RollingFileWarn"/>
|
||||||
|
<AppenderRef ref="RollingFileError"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<Logger name="org.xyzh.common" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFileInfo"/>
|
||||||
|
<AppenderRef ref="RollingFileWarn"/>
|
||||||
|
<AppenderRef ref="RollingFileError"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="Console"/>
|
||||||
|
<appender-ref ref="RollingFileInfo"/>
|
||||||
|
<appender-ref ref="RollingFileWarn"/>
|
||||||
|
<appender-ref ref="RollingFileError"/>
|
||||||
|
</root>
|
||||||
|
</loggers>
|
||||||
|
</configuration>
|
||||||
@@ -17,5 +17,48 @@
|
|||||||
<maven.compiler.source>21</maven.compiler.source>
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
<maven.compiler.target>21</maven.compiler.target>
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
</properties>
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<!-- 项目内部模块 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.xyzh.common</groupId>
|
||||||
|
<artifactId>common-all</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.xyzh.apis</groupId>
|
||||||
|
<artifactId>api-system</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.xyzh.apis</groupId>
|
||||||
|
<artifactId>api-message</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Boot Actuator -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-logging</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Cloud Nacos Discovery - 用于 Gateway 路由发现 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.alibaba.nacos</groupId>
|
||||||
|
<artifactId>nacos-logback-adapter-12</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.alibaba.nacos</groupId>
|
||||||
|
<artifactId>logback-adapter</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
package org.xyzh.message.config;
|
||||||
|
|
||||||
|
import org.apache.dubbo.config.annotation.DubboReference;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.ApplicationArguments;
|
||||||
|
import org.springframework.boot.ApplicationRunner;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.xyzh.api.system.service.SysConfigService;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 动态配置加载器 - 从数据库加载邮件和短信配置
|
||||||
|
* @filename DynamicConfigLoader.java
|
||||||
|
* @author yslg
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-12-05
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@Order(100) // 确保在其他组件初始化之后再加载配置
|
||||||
|
public class DynamicConfigLoader implements ApplicationRunner {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(DynamicConfigLoader.class);
|
||||||
|
|
||||||
|
@DubboReference(version = "1.0.0", group = "system", timeout = 5000, check = false)
|
||||||
|
private SysConfigService sysConfigService;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private JavaMailSenderImpl mailSender;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private EmailConfigProperties emailConfigProperties;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private SmsConfigProperties smsConfigProperties;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(ApplicationArguments args) throws Exception {
|
||||||
|
logger.info("=== 开始加载动态配置 ===");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 加载邮件配置
|
||||||
|
if (emailConfigProperties != null) {
|
||||||
|
loadEmailConfig();
|
||||||
|
} else {
|
||||||
|
logger.warn("EmailConfigProperties未注入,跳过邮件配置加载");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载短信配置
|
||||||
|
if (smsConfigProperties != null) {
|
||||||
|
loadSmsConfig();
|
||||||
|
} else {
|
||||||
|
logger.warn("SmsConfigProperties未注入,跳过短信配置加载");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("=== 动态配置加载完成 ===");
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("动态配置加载失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载邮件配置
|
||||||
|
*/
|
||||||
|
private void loadEmailConfig() {
|
||||||
|
try {
|
||||||
|
if (sysConfigService == null) {
|
||||||
|
logger.warn("SysConfigService未注入,无法加载邮件配置");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String host = sysConfigService.getStringConfig("email.host");
|
||||||
|
String port = sysConfigService.getStringConfig("email.port");
|
||||||
|
String username = sysConfigService.getStringConfig("email.username");
|
||||||
|
String password = sysConfigService.getStringConfig("email.password");
|
||||||
|
String fromName = sysConfigService.getStringConfig("email.fromName");
|
||||||
|
String sslEnable = sysConfigService.getStringConfig("email.ssl.enable");
|
||||||
|
String timeout = sysConfigService.getStringConfig("email.timeout");
|
||||||
|
|
||||||
|
// 更新配置属性
|
||||||
|
emailConfigProperties.setHost(host);
|
||||||
|
emailConfigProperties.setPort(StringUtils.hasText(port) ? Integer.valueOf(port) : 587);
|
||||||
|
emailConfigProperties.setUsername(username);
|
||||||
|
emailConfigProperties.setPassword(password);
|
||||||
|
emailConfigProperties.setFromName(fromName);
|
||||||
|
emailConfigProperties.setSslEnable("true".equalsIgnoreCase(sslEnable));
|
||||||
|
emailConfigProperties.setTimeout(StringUtils.hasText(timeout) ? Integer.valueOf(timeout) : 30000);
|
||||||
|
|
||||||
|
// 如果邮箱配置完整,则配置JavaMailSender
|
||||||
|
if (mailSender != null && StringUtils.hasText(host) && StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||||
|
mailSender.setHost(host);
|
||||||
|
mailSender.setPort(emailConfigProperties.getPort());
|
||||||
|
mailSender.setUsername(username);
|
||||||
|
mailSender.setPassword(password);
|
||||||
|
|
||||||
|
// 设置邮件属性
|
||||||
|
Properties props = mailSender.getJavaMailProperties();
|
||||||
|
props.put("mail.smtp.auth", "true");
|
||||||
|
props.put("mail.smtp.starttls.enable", emailConfigProperties.getSslEnable() ? "true" : "false");
|
||||||
|
props.put("mail.smtp.starttls.required", emailConfigProperties.getSslEnable() ? "true" : "false");
|
||||||
|
props.put("mail.smtp.timeout", emailConfigProperties.getTimeout());
|
||||||
|
props.put("mail.smtp.connectiontimeout", emailConfigProperties.getTimeout());
|
||||||
|
|
||||||
|
logger.info("邮件配置加载成功: host={}, port={}, username={}", host, emailConfigProperties.getPort(), username);
|
||||||
|
} else {
|
||||||
|
logger.warn("邮件配置不完整,将使用默认配置或模拟模式");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("加载邮件配置失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载短信配置
|
||||||
|
*/
|
||||||
|
private void loadSmsConfig() {
|
||||||
|
try {
|
||||||
|
if (sysConfigService == null) {
|
||||||
|
logger.warn("SysConfigService未注入,无法加载短信配置");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String provider = sysConfigService.getStringConfig("sms.provider");
|
||||||
|
String accessKeyId = sysConfigService.getStringConfig("sms.accessKeyId");
|
||||||
|
String accessKeySecret = sysConfigService.getStringConfig("sms.accessKeySecret");
|
||||||
|
String signName = sysConfigService.getStringConfig("sms.signName");
|
||||||
|
String templateCodeLogin = sysConfigService.getStringConfig("sms.templateCode.login");
|
||||||
|
String templateCodeRegister = sysConfigService.getStringConfig("sms.templateCode.register");
|
||||||
|
String timeout = sysConfigService.getStringConfig("sms.timeout");
|
||||||
|
|
||||||
|
// 更新配置属性
|
||||||
|
smsConfigProperties.setProvider(StringUtils.hasText(provider) ? provider : "aliyun");
|
||||||
|
smsConfigProperties.setAccessKeyId(accessKeyId);
|
||||||
|
smsConfigProperties.setAccessKeySecret(accessKeySecret);
|
||||||
|
smsConfigProperties.setSignName(StringUtils.hasText(signName) ? signName : "校园新闻");
|
||||||
|
smsConfigProperties.setTemplateCodeLogin(templateCodeLogin);
|
||||||
|
smsConfigProperties.setTemplateCodeRegister(templateCodeRegister);
|
||||||
|
smsConfigProperties.setTimeout(StringUtils.hasText(timeout) ? Integer.valueOf(timeout) : 30000);
|
||||||
|
|
||||||
|
if (StringUtils.hasText(accessKeyId) && StringUtils.hasText(accessKeySecret)) {
|
||||||
|
logger.info("短信配置加载成功: provider={}, signName={}", smsConfigProperties.getProvider(), smsConfigProperties.getSignName());
|
||||||
|
} else {
|
||||||
|
logger.warn("短信配置不完整,将使用模拟模式");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("加载短信配置失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package org.xyzh.message.config;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 邮件配置属性
|
||||||
|
* @filename EmailConfigProperties.java
|
||||||
|
* @author yslg
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-11-26
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class EmailConfigProperties {
|
||||||
|
|
||||||
|
/** SMTP服务器地址 */
|
||||||
|
private String host;
|
||||||
|
|
||||||
|
/** SMTP端口 */
|
||||||
|
private Integer port;
|
||||||
|
|
||||||
|
/** 发件人邮箱 */
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
/** 邮箱授权码 */
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
/** 发件人名称 */
|
||||||
|
private String fromName;
|
||||||
|
|
||||||
|
/** 是否启用SSL */
|
||||||
|
private Boolean sslEnable;
|
||||||
|
|
||||||
|
/** 连接超时时间(毫秒) */
|
||||||
|
private Integer timeout;
|
||||||
|
|
||||||
|
public String getHost() {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHost(String host) {
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPort() {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPort(Integer port) {
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFromName() {
|
||||||
|
return fromName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFromName(String fromName) {
|
||||||
|
this.fromName = fromName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getSslEnable() {
|
||||||
|
return sslEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSslEnable(Boolean sslEnable) {
|
||||||
|
this.sslEnable = sslEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getTimeout() {
|
||||||
|
return timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeout(Integer timeout) {
|
||||||
|
this.timeout = timeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.xyzh.message.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
|
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 邮件发送器配置 - 手动创建JavaMailSender bean
|
||||||
|
* @filename MailSenderConfig.java
|
||||||
|
* @author yslg
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-12-05
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class MailSenderConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建JavaMailSender bean
|
||||||
|
* 初始值为默认配置,实际配置将在DynamicConfigLoader中从数据库加载
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public JavaMailSender javaMailSender() {
|
||||||
|
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
|
||||||
|
|
||||||
|
// 设置默认配置(防止未配置时报错)
|
||||||
|
mailSender.setHost("smtp.example.com");
|
||||||
|
mailSender.setPort(587);
|
||||||
|
mailSender.setUsername("default");
|
||||||
|
mailSender.setPassword("default");
|
||||||
|
|
||||||
|
// 设置邮件属性
|
||||||
|
Properties props = mailSender.getJavaMailProperties();
|
||||||
|
props.put("mail.smtp.auth", "true");
|
||||||
|
props.put("mail.smtp.starttls.enable", "true");
|
||||||
|
props.put("mail.smtp.starttls.required", "false");
|
||||||
|
props.put("mail.smtp.timeout", "30000");
|
||||||
|
props.put("mail.smtp.connectiontimeout", "30000");
|
||||||
|
|
||||||
|
return mailSender;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package org.xyzh.message.config;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 短信配置属性
|
||||||
|
* @filename SmsConfigProperties.java
|
||||||
|
* @author yslg
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-11-26
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class SmsConfigProperties {
|
||||||
|
|
||||||
|
/** 短信服务商 */
|
||||||
|
private String provider;
|
||||||
|
|
||||||
|
/** AccessKey ID */
|
||||||
|
private String accessKeyId;
|
||||||
|
|
||||||
|
/** AccessKey Secret */
|
||||||
|
private String accessKeySecret;
|
||||||
|
|
||||||
|
/** 短信签名 */
|
||||||
|
private String signName;
|
||||||
|
|
||||||
|
/** 登录验证码模板 */
|
||||||
|
private String templateCodeLogin;
|
||||||
|
|
||||||
|
/** 注册验证码模板 */
|
||||||
|
private String templateCodeRegister;
|
||||||
|
|
||||||
|
/** 请求超时时间(毫秒) */
|
||||||
|
private Integer timeout;
|
||||||
|
|
||||||
|
public String getProvider() {
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProvider(String provider) {
|
||||||
|
this.provider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAccessKeyId() {
|
||||||
|
return accessKeyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccessKeyId(String accessKeyId) {
|
||||||
|
this.accessKeyId = accessKeyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAccessKeySecret() {
|
||||||
|
return accessKeySecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccessKeySecret(String accessKeySecret) {
|
||||||
|
this.accessKeySecret = accessKeySecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSignName() {
|
||||||
|
return signName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSignName(String signName) {
|
||||||
|
this.signName = signName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTemplateCodeLogin() {
|
||||||
|
return templateCodeLogin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTemplateCodeLogin(String templateCodeLogin) {
|
||||||
|
this.templateCodeLogin = templateCodeLogin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTemplateCodeRegister() {
|
||||||
|
return templateCodeRegister;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTemplateCodeRegister(String templateCodeRegister) {
|
||||||
|
this.templateCodeRegister = templateCodeRegister;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getTimeout() {
|
||||||
|
return timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeout(Integer timeout) {
|
||||||
|
this.timeout = timeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
package org.xyzh.message.service;
|
||||||
|
|
||||||
|
import org.apache.dubbo.config.annotation.DubboService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.xyzh.api.message.dto.TbMessageDTO;
|
||||||
|
import org.xyzh.api.message.service.MessageService;
|
||||||
|
import org.xyzh.api.message.vo.MessageVO;
|
||||||
|
import org.xyzh.common.core.domain.ResultDomain;
|
||||||
|
import org.xyzh.common.core.page.PageParam;
|
||||||
|
import org.xyzh.message.config.DynamicConfigLoader;
|
||||||
|
import org.xyzh.message.utils.EmailUtils;
|
||||||
|
import org.xyzh.message.utils.SmsUtils;
|
||||||
|
|
||||||
|
@DubboService(
|
||||||
|
version = "1.0.0",
|
||||||
|
group = "message",
|
||||||
|
timeout = 3000,
|
||||||
|
retries = 0
|
||||||
|
)
|
||||||
|
public class MessageServiceImpl implements MessageService{
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(MessageServiceImpl.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EmailUtils emailUtils;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SmsUtils smsUtils;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<String> sendSimpleEmail(String to, String subject, String content) {
|
||||||
|
boolean flag = emailUtils.sendSimpleEmail(to, subject, content);
|
||||||
|
if (flag){
|
||||||
|
return ResultDomain.success("发生成功");
|
||||||
|
}else{
|
||||||
|
return ResultDomain.failure("发送失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<String> sendHtmlEmail(String to, String subject, String content) {
|
||||||
|
boolean flag = emailUtils.sendHtmlEmail(to, subject, content);
|
||||||
|
if (flag){
|
||||||
|
return ResultDomain.success("发生成功");
|
||||||
|
}else{
|
||||||
|
return ResultDomain.failure("发送失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<String> sendEmailVerificationCode(String to, String code) {
|
||||||
|
boolean flag = emailUtils.sendVerificationCode(to, code);
|
||||||
|
if (flag){
|
||||||
|
return ResultDomain.success("发生成功");
|
||||||
|
}else{
|
||||||
|
return ResultDomain.failure("发送失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<String> sendPhoneVerificationCode(String phone, String code) {
|
||||||
|
boolean flag = smsUtils.sendVerificationCode(phone, code);
|
||||||
|
if (flag){
|
||||||
|
return ResultDomain.success("发生成功");
|
||||||
|
}else{
|
||||||
|
return ResultDomain.failure("发送失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<TbMessageDTO> createMessage(MessageVO messageVO) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<Boolean> deleteMessage(String messageId) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<MessageVO> getMessageDetail(String messageId) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<TbMessageDTO> getMessageList(TbMessageDTO filter) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<TbMessageDTO> getMessagePage(TbMessageDTO filter, PageParam pageParam) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<TbMessageDTO> getMyMessageDetail(String messageId) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<TbMessageDTO> getMyMessageList(TbMessageDTO filter) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<TbMessageDTO> getMyMessagePage(TbMessageDTO filter, PageParam pageParam) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<TbMessageDTO> handleMessage(String messageId, String status) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<MessageVO> sendMessage(MessageVO messageVO) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<TbMessageDTO> updateMessage(MessageVO messageVO) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultDomain<TbMessageDTO> withdrawMessage(String messageId) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
package org.xyzh.message.utils;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
|
import org.springframework.mail.SimpleMailMessage;
|
||||||
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
|
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.xyzh.message.config.EmailConfigProperties;
|
||||||
|
|
||||||
|
import jakarta.mail.MessagingException;
|
||||||
|
import jakarta.mail.internet.MimeMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 邮件发送工具类
|
||||||
|
* @filename EmailUtils.java
|
||||||
|
* @author yslg
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-11-03
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@ConditionalOnBean(JavaMailSender.class)
|
||||||
|
public class EmailUtils {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(EmailUtils.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JavaMailSender mailSender;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EmailConfigProperties emailConfigProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送简单文本邮件
|
||||||
|
* @param to 收件人邮箱
|
||||||
|
* @param subject 邮件主题
|
||||||
|
* @param content 邮件内容
|
||||||
|
* @return 是否发送成功
|
||||||
|
*/
|
||||||
|
public boolean sendSimpleEmail(String to, String subject, String content) {
|
||||||
|
try {
|
||||||
|
SimpleMailMessage message = new SimpleMailMessage();
|
||||||
|
String from = emailConfigProperties.getUsername();
|
||||||
|
message.setFrom(from);
|
||||||
|
message.setTo(to);
|
||||||
|
message.setSubject(subject);
|
||||||
|
message.setText(content);
|
||||||
|
|
||||||
|
mailSender.send(message);
|
||||||
|
logger.info("简单邮件发送成功,收件人: {}", to);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("简单邮件发送失败,收件人: {}, 错误: {}", to, e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送HTML格式邮件
|
||||||
|
* @param to 收件人邮箱
|
||||||
|
* @param subject 邮件主题
|
||||||
|
* @param content HTML格式的邮件内容
|
||||||
|
* @return 是否发送成功
|
||||||
|
*/
|
||||||
|
public boolean sendHtmlEmail(String to, String subject, String content) {
|
||||||
|
try {
|
||||||
|
MimeMessage message = mailSender.createMimeMessage();
|
||||||
|
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
|
||||||
|
|
||||||
|
String from = emailConfigProperties.getUsername();
|
||||||
|
helper.setFrom(from);
|
||||||
|
helper.setTo(to);
|
||||||
|
helper.setSubject(subject);
|
||||||
|
helper.setText(content, true); // true表示HTML格式
|
||||||
|
|
||||||
|
mailSender.send(message);
|
||||||
|
logger.info("HTML邮件发送成功,收件人: {}", to);
|
||||||
|
return true;
|
||||||
|
} catch (MessagingException e) {
|
||||||
|
logger.error("HTML邮件发送失败,收件人: {}, 错误: {}", to, e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送验证码邮件
|
||||||
|
* @param to 收件人邮箱
|
||||||
|
* @param code 验证码
|
||||||
|
* @return 是否发送成功
|
||||||
|
*/
|
||||||
|
public boolean sendVerificationCode(String to, String code) {
|
||||||
|
String subject = "【红色思政学习平台】邮箱验证码";
|
||||||
|
String content = buildVerificationCodeHtml(code);
|
||||||
|
return sendHtmlEmail(to, subject, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建验证码邮件的HTML内容
|
||||||
|
* @param code 验证码
|
||||||
|
* @return HTML内容
|
||||||
|
*/
|
||||||
|
private String buildVerificationCodeHtml(String code) {
|
||||||
|
return "<!DOCTYPE html>" +
|
||||||
|
"<html>" +
|
||||||
|
"<head>" +
|
||||||
|
"<meta charset=\"UTF-8\">" +
|
||||||
|
"<style>" +
|
||||||
|
"body { font-family: 'Microsoft YaHei', Arial, sans-serif; background-color: #f5f5f5; margin: 0; padding: 20px; }" +
|
||||||
|
".container { max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); overflow: hidden; }" +
|
||||||
|
".header { background: linear-gradient(135deg, #C62828 0%, #E53935 100%); padding: 30px; text-align: center; }" +
|
||||||
|
".header h1 { color: #ffffff; margin: 0; font-size: 24px; }" +
|
||||||
|
".content { padding: 40px 30px; }" +
|
||||||
|
".content p { color: #333333; line-height: 1.8; margin: 10px 0; }" +
|
||||||
|
".code-box { background-color: #f8f9fa; border-left: 4px solid #C62828; padding: 20px; margin: 20px 0; text-align: center; }" +
|
||||||
|
".code { font-size: 32px; font-weight: bold; color: #C62828; letter-spacing: 5px; font-family: 'Courier New', monospace; }" +
|
||||||
|
".tips { color: #666666; font-size: 14px; margin-top: 20px; line-height: 1.6; }" +
|
||||||
|
".footer { background-color: #f8f9fa; padding: 20px; text-align: center; color: #999999; font-size: 12px; }" +
|
||||||
|
"</style>" +
|
||||||
|
"</head>" +
|
||||||
|
"<body>" +
|
||||||
|
"<div class=\"container\">" +
|
||||||
|
"<div class=\"header\">" +
|
||||||
|
"<h1>红色思政学习平台</h1>" +
|
||||||
|
"</div>" +
|
||||||
|
"<div class=\"content\">" +
|
||||||
|
"<p>尊敬的用户,您好!</p>" +
|
||||||
|
"<p>您正在进行邮箱验证,您的验证码为:</p>" +
|
||||||
|
"<div class=\"code-box\">" +
|
||||||
|
"<div class=\"code\">" + code + "</div>" +
|
||||||
|
"</div>" +
|
||||||
|
"<div class=\"tips\">" +
|
||||||
|
"<p>• 验证码有效期为10分钟,请尽快完成验证</p>" +
|
||||||
|
"<p>• 如果这不是您的操作,请忽略此邮件</p>" +
|
||||||
|
"<p>• 为了保护您的账号安全,请勿将验证码告知他人</p>" +
|
||||||
|
"</div>" +
|
||||||
|
"</div>" +
|
||||||
|
"<div class=\"footer\">" +
|
||||||
|
"<p>此邮件由系统自动发送,请勿回复</p>" +
|
||||||
|
"<p>Copyright © 红色思政智能体平台</p>" +
|
||||||
|
"</div>" +
|
||||||
|
"</div>" +
|
||||||
|
"</body>" +
|
||||||
|
"</html>";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成6位数字验证码
|
||||||
|
* @return 验证码
|
||||||
|
*/
|
||||||
|
public static String generateVerificationCode() {
|
||||||
|
return String.valueOf((int)((Math.random() * 9 + 1) * 100000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
package org.xyzh.message.utils;
|
||||||
|
|
||||||
|
import com.aliyun.dysmsapi20170525.Client;
|
||||||
|
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
|
||||||
|
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
|
||||||
|
import com.aliyun.teaopenapi.models.Config;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.xyzh.message.config.SmsConfigProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 短信发送工具类 - 支持多种短信服务商
|
||||||
|
* @filename SmsUtils.java
|
||||||
|
* @author yslg
|
||||||
|
* @copyright xyzh
|
||||||
|
* @since 2025-11-03
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@ConditionalOnBean(SmsConfigProperties.class)
|
||||||
|
public class SmsUtils {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(SmsUtils.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SmsConfigProperties smsConfigProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送短信验证码
|
||||||
|
* @param phone 手机号
|
||||||
|
* @param code 验证码
|
||||||
|
* @return 是否发送成功
|
||||||
|
*/
|
||||||
|
public boolean sendVerificationCode(String phone, String code) {
|
||||||
|
// 如果未启用短信服务,使用模拟模式
|
||||||
|
String accessKeyId = smsConfigProperties.getAccessKeyId();
|
||||||
|
String accessKeySecret = smsConfigProperties.getAccessKeySecret();
|
||||||
|
|
||||||
|
if (!StringUtils.hasText(accessKeyId) || !StringUtils.hasText(accessKeySecret)) {
|
||||||
|
logger.warn("短信服务未配置或未启用,使用模拟模式");
|
||||||
|
logger.info("【模拟发送】短信验证码,手机号: {}, 验证码: {}", phone, code);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据配置的服务商选择发送方式
|
||||||
|
String provider = smsConfigProperties.getProvider();
|
||||||
|
if (provider == null) provider = "aliyun";
|
||||||
|
|
||||||
|
switch (provider.toLowerCase()) {
|
||||||
|
case "aliyun":
|
||||||
|
return sendByAliyun(phone, code, smsConfigProperties.getTemplateCodeLogin());
|
||||||
|
case "tencent":
|
||||||
|
logger.warn("腾讯云短信服务暂未实现,使用模拟模式");
|
||||||
|
logger.info("【模拟发送】短信验证码,手机号: {}, 验证码: {}", phone, code);
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
logger.error("未知的短信服务商: {}", provider);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用阿里云发送短信验证码
|
||||||
|
* @param phone 手机号
|
||||||
|
* @param code 验证码
|
||||||
|
* @param templateCode 短信模板CODE
|
||||||
|
* @return 是否发送成功
|
||||||
|
*/
|
||||||
|
private boolean sendByAliyun(String phone, String code, String templateCode) {
|
||||||
|
try {
|
||||||
|
Client client = createAliyunClient();
|
||||||
|
|
||||||
|
SendSmsRequest request = new SendSmsRequest()
|
||||||
|
.setPhoneNumbers(phone)
|
||||||
|
.setSignName(smsConfigProperties.getSignName())
|
||||||
|
.setTemplateCode(templateCode)
|
||||||
|
.setTemplateParam("{\"code\":\"" + code + "\"}");
|
||||||
|
|
||||||
|
SendSmsResponse response = client.sendSms(request);
|
||||||
|
|
||||||
|
if ("OK".equals(response.getBody().getCode())) {
|
||||||
|
logger.info("阿里云短信发送成功,手机号: {}, BizId: {}", phone, response.getBody().getBizId());
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
logger.error("阿里云短信发送失败,手机号: {}, Code: {}, Message: {}",
|
||||||
|
phone, response.getBody().getCode(), response.getBody().getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("阿里云短信发送异常,手机号: {}, 错误: {}", phone, e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建阿里云短信客户端
|
||||||
|
* @return 短信客户端
|
||||||
|
*/
|
||||||
|
private Client createAliyunClient() throws Exception {
|
||||||
|
Config config = new Config()
|
||||||
|
.setAccessKeyId(smsConfigProperties.getAccessKeyId())
|
||||||
|
.setAccessKeySecret(smsConfigProperties.getAccessKeySecret())
|
||||||
|
.setEndpoint("dysmsapi.aliyuncs.com");
|
||||||
|
return new Client(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送通用短信(支持自定义模板)
|
||||||
|
* @param phone 手机号
|
||||||
|
* @param templateCode 模板CODE
|
||||||
|
* @param templateParam 模板参数(JSON格式)
|
||||||
|
* @return 是否发送成功
|
||||||
|
*/
|
||||||
|
public boolean sendSms(String phone, String templateCode, String templateParam) {
|
||||||
|
// 如果未启用短信服务,使用模拟模式
|
||||||
|
String accessKeyId = smsConfigProperties.getAccessKeyId();
|
||||||
|
String accessKeySecret = smsConfigProperties.getAccessKeySecret();
|
||||||
|
|
||||||
|
if (!StringUtils.hasText(accessKeyId) || !StringUtils.hasText(accessKeySecret)) {
|
||||||
|
logger.warn("短信服务未配置或未启用,使用模拟模式");
|
||||||
|
logger.info("【模拟发送】短信,手机号: {}, 模板: {}, 参数: {}", phone, templateCode, templateParam);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据配置的服务商选择发送方式
|
||||||
|
String provider = smsConfigProperties.getProvider();
|
||||||
|
if (provider == null) provider = "aliyun";
|
||||||
|
|
||||||
|
switch (provider.toLowerCase()) {
|
||||||
|
case "aliyun":
|
||||||
|
return sendSmsAliyun(phone, templateCode, templateParam);
|
||||||
|
case "tencent":
|
||||||
|
logger.warn("腾讯云短信服务暂未实现,使用模拟模式");
|
||||||
|
logger.info("【模拟发送】短信,手机号: {}, 模板: {}, 参数: {}", phone, templateCode, templateParam);
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
logger.error("未知的短信服务商: {}", provider);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用阿里云发送通用短信
|
||||||
|
*/
|
||||||
|
private boolean sendSmsAliyun(String phone, String templateCode, String templateParam) {
|
||||||
|
try {
|
||||||
|
Client client = createAliyunClient();
|
||||||
|
|
||||||
|
SendSmsRequest request = new SendSmsRequest()
|
||||||
|
.setPhoneNumbers(phone)
|
||||||
|
.setSignName(smsConfigProperties.getSignName())
|
||||||
|
.setTemplateCode(templateCode)
|
||||||
|
.setTemplateParam(templateParam);
|
||||||
|
|
||||||
|
SendSmsResponse response = client.sendSms(request);
|
||||||
|
|
||||||
|
if ("OK".equals(response.getBody().getCode())) {
|
||||||
|
logger.info("阿里云短信发送成功,手机号: {}, BizId: {}", phone, response.getBody().getBizId());
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
logger.error("阿里云短信发送失败,手机号: {}, Code: {}, Message: {}",
|
||||||
|
phone, response.getBody().getCode(), response.getBody().getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("阿里云短信发送异常,手机号: {}, 错误: {}", phone, e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量发送短信
|
||||||
|
* @param phones 手机号列表,用逗号分隔
|
||||||
|
* @param templateCode 模板CODE
|
||||||
|
* @param templateParam 模板参数(JSON格式)
|
||||||
|
* @return 是否发送成功
|
||||||
|
*/
|
||||||
|
public boolean sendBatchSms(String phones, String templateCode, String templateParam) {
|
||||||
|
// 如果未启用短信服务,使用模拟模式
|
||||||
|
String accessKeyId = smsConfigProperties.getAccessKeyId();
|
||||||
|
String accessKeySecret = smsConfigProperties.getAccessKeySecret();
|
||||||
|
|
||||||
|
if (!StringUtils.hasText(accessKeyId) || !StringUtils.hasText(accessKeySecret)) {
|
||||||
|
logger.warn("短信服务未配置或未启用,使用模拟模式");
|
||||||
|
logger.info("【模拟发送】批量短信,手机号: {}, 模板: {}, 参数: {}", phones, templateCode, templateParam);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据配置的服务商选择发送方式
|
||||||
|
String provider = smsConfigProperties.getProvider();
|
||||||
|
if (provider == null) provider = "aliyun";
|
||||||
|
|
||||||
|
switch (provider.toLowerCase()) {
|
||||||
|
case "aliyun":
|
||||||
|
return sendBatchSmsAliyun(phones, templateCode, templateParam);
|
||||||
|
case "tencent":
|
||||||
|
logger.warn("腾讯云短信服务暂未实现,使用模拟模式");
|
||||||
|
logger.info("【模拟发送】批量短信,手机号: {}, 模板: {}, 参数: {}", phones, templateCode, templateParam);
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
logger.error("未知的短信服务商: {}", provider);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用阿里云批量发送短信
|
||||||
|
*/
|
||||||
|
private boolean sendBatchSmsAliyun(String phones, String templateCode, String templateParam) {
|
||||||
|
try {
|
||||||
|
Client client = createAliyunClient();
|
||||||
|
|
||||||
|
SendSmsRequest request = new SendSmsRequest()
|
||||||
|
.setPhoneNumbers(phones)
|
||||||
|
.setSignName(smsConfigProperties.getSignName())
|
||||||
|
.setTemplateCode(templateCode)
|
||||||
|
.setTemplateParam(templateParam);
|
||||||
|
|
||||||
|
SendSmsResponse response = client.sendSms(request);
|
||||||
|
|
||||||
|
if ("OK".equals(response.getBody().getCode())) {
|
||||||
|
logger.info("阿里云批量短信发送成功,手机号: {}, BizId: {}", phones, response.getBody().getBizId());
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
logger.error("阿里云批量短信发送失败,手机号: {}, Code: {}, Message: {}",
|
||||||
|
phones, response.getBody().getCode(), response.getBody().getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("阿里云批量短信发送异常,手机号: {}, 错误: {}", phones, e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成6位数字验证码
|
||||||
|
* @return 验证码
|
||||||
|
*/
|
||||||
|
public static String generateVerificationCode() {
|
||||||
|
return String.valueOf((int)((Math.random() * 9 + 1) * 100000));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证手机号格式
|
||||||
|
* @param phone 手机号
|
||||||
|
* @return 是否有效
|
||||||
|
*/
|
||||||
|
public static boolean isValidPhone(String phone) {
|
||||||
|
if (phone == null || phone.trim().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 中国大陆手机号验证
|
||||||
|
String regex = "^1[3-9]\\d{9}$";
|
||||||
|
return phone.matches(regex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
88
urbanLifelineServ/message/src/main/resources/application.yml
Normal file
88
urbanLifelineServ/message/src/main/resources/application.yml
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# ================== Server ==================
|
||||||
|
server:
|
||||||
|
port: 8185
|
||||||
|
servlet:
|
||||||
|
context-path: /urban-lifeline/message
|
||||||
|
|
||||||
|
# ================== Auth ====================
|
||||||
|
urban-lifeline:
|
||||||
|
auth:
|
||||||
|
enabled: true
|
||||||
|
whitelist:
|
||||||
|
- /swagger-ui/**
|
||||||
|
- /swagger-ui.html
|
||||||
|
- /v3/api-docs/**
|
||||||
|
- /webjars/**
|
||||||
|
- /favicon.ico
|
||||||
|
- /error
|
||||||
|
- /actuator/health
|
||||||
|
- /actuator/info
|
||||||
|
|
||||||
|
security:
|
||||||
|
aes:
|
||||||
|
secret-key: 1234567890qwer
|
||||||
|
# ================== Spring ==================
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: message-service
|
||||||
|
|
||||||
|
# ================== Spring Cloud Nacos ==================
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
server-addr: 127.0.0.1:8848
|
||||||
|
namespace: dev
|
||||||
|
group: DEFAULT_GROUP
|
||||||
|
|
||||||
|
# ================== DataSource ==================
|
||||||
|
datasource:
|
||||||
|
url: jdbc:postgresql://127.0.0.1:5432/urban_lifeline
|
||||||
|
username: postgres
|
||||||
|
password: postgres
|
||||||
|
driver-class-name: org.postgresql.Driver
|
||||||
|
|
||||||
|
# ================== Redis ==================
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: 127.0.0.1 # 如果是 docker 跑的 redis,按实际 host / 端口改
|
||||||
|
port: 6379
|
||||||
|
database: 0
|
||||||
|
password: 123456 # 如果有密码就填上,没密码可以去掉这一行
|
||||||
|
|
||||||
|
# ================== SpringDoc ==================
|
||||||
|
springdoc:
|
||||||
|
api-docs:
|
||||||
|
enabled: true
|
||||||
|
path: /v3/api-docs
|
||||||
|
swagger-ui:
|
||||||
|
enabled: true
|
||||||
|
path: /swagger-ui.html
|
||||||
|
group-configs:
|
||||||
|
- group: 'default'
|
||||||
|
display-name: '消息服务 API'
|
||||||
|
paths-to-match: '/**'
|
||||||
|
|
||||||
|
# ================== Dubbo + Nacos ==================
|
||||||
|
dubbo:
|
||||||
|
application:
|
||||||
|
name: urban-lifeline-message
|
||||||
|
qos-enable: false
|
||||||
|
protocol:
|
||||||
|
name: dubbo
|
||||||
|
port: -1
|
||||||
|
registry:
|
||||||
|
address: nacos://127.0.0.1:8848
|
||||||
|
scan:
|
||||||
|
base-packages: org.xyzh.message.service.impl
|
||||||
|
|
||||||
|
# ================== MyBatis ==================
|
||||||
|
mybatis-plus:
|
||||||
|
mapper-locations: classpath:mapper/**/*.xml
|
||||||
|
type-aliases-package: org.xyzh.common.dto, org.xyzh.api
|
||||||
|
|
||||||
|
# ================== Logging ==================
|
||||||
|
logging:
|
||||||
|
config: classpath:log4j2.xml
|
||||||
|
charset:
|
||||||
|
console: UTF-8
|
||||||
|
file: UTF-8
|
||||||
85
urbanLifelineServ/message/src/main/resources/log4j2.xml
Normal file
85
urbanLifelineServ/message/src/main/resources/log4j2.xml
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration status="WARN" monitorInterval="30">
|
||||||
|
<Properties>
|
||||||
|
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
|
||||||
|
<property name="FILE_PATH" value="./logs" />
|
||||||
|
<property name="FILE_NAME" value="message-service" />
|
||||||
|
<property name="file.encoding" value="UTF-8" />
|
||||||
|
<property name="console.encoding" value="UTF-8" />
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
<appenders>
|
||||||
|
<console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
</console>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${FILE_NAME}-info.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/${FILE_NAME}-warn.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/${FILE_NAME}-error.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
</appenders>
|
||||||
|
|
||||||
|
<loggers>
|
||||||
|
<logger name="com.alibaba.nacos" level="info" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<logger name="org.mybatis" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<Logger name="org.springframework" level="info" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<Logger name="org.xyzh.message" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFileInfo"/>
|
||||||
|
<AppenderRef ref="RollingFileWarn"/>
|
||||||
|
<AppenderRef ref="RollingFileError"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<Logger name="org.xyzh.common" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFileInfo"/>
|
||||||
|
<AppenderRef ref="RollingFileWarn"/>
|
||||||
|
<AppenderRef ref="RollingFileError"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="Console"/>
|
||||||
|
<appender-ref ref="RollingFileInfo"/>
|
||||||
|
<appender-ref ref="RollingFileWarn"/>
|
||||||
|
<appender-ref ref="RollingFileError"/>
|
||||||
|
</root>
|
||||||
|
</loggers>
|
||||||
|
</configuration>
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# ================== Server ==================
|
||||||
|
server:
|
||||||
|
port: 8187
|
||||||
|
servlet:
|
||||||
|
context-path: /urban-lifeline/platform
|
||||||
|
|
||||||
|
# ================== Auth ====================
|
||||||
|
urban-lifeline:
|
||||||
|
auth:
|
||||||
|
enabled: true
|
||||||
|
whitelist:
|
||||||
|
- /swagger-ui/**
|
||||||
|
- /swagger-ui.html
|
||||||
|
- /v3/api-docs/**
|
||||||
|
- /webjars/**
|
||||||
|
- /favicon.ico
|
||||||
|
- /error
|
||||||
|
- /actuator/health
|
||||||
|
- /actuator/info
|
||||||
|
|
||||||
|
security:
|
||||||
|
aes:
|
||||||
|
secret-key: 1234567890qwer
|
||||||
|
|
||||||
|
# ================== Spring ==================
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: platform-service
|
||||||
|
|
||||||
|
# ================== Spring Cloud Nacos ==================
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
server-addr: 127.0.0.1:8848
|
||||||
|
namespace: dev
|
||||||
|
group: DEFAULT_GROUP
|
||||||
|
|
||||||
|
# ================== DataSource ==================
|
||||||
|
datasource:
|
||||||
|
url: jdbc:postgresql://127.0.0.1:5432/urban_lifeline
|
||||||
|
username: postgres
|
||||||
|
password: postgres
|
||||||
|
driver-class-name: org.postgresql.Driver
|
||||||
|
|
||||||
|
# ================== Redis ==================
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: 127.0.0.1 # 如果是 docker 跑的 redis,按实际 host / 端口改
|
||||||
|
port: 6379
|
||||||
|
database: 0
|
||||||
|
password: 123456 # 如果有密码就填上,没密码可以去掉这一行
|
||||||
|
|
||||||
|
# ================== SpringDoc ==================
|
||||||
|
springdoc:
|
||||||
|
api-docs:
|
||||||
|
enabled: true
|
||||||
|
path: /v3/api-docs
|
||||||
|
swagger-ui:
|
||||||
|
enabled: true
|
||||||
|
path: /swagger-ui.html
|
||||||
|
group-configs:
|
||||||
|
- group: 'default'
|
||||||
|
display-name: '平台服务 API'
|
||||||
|
paths-to-match: '/**'
|
||||||
|
|
||||||
|
# ================== Dubbo + Nacos ==================
|
||||||
|
dubbo:
|
||||||
|
application:
|
||||||
|
name: urban-lifeline-platform
|
||||||
|
qos-enable: false
|
||||||
|
protocol:
|
||||||
|
name: dubbo
|
||||||
|
port: -1
|
||||||
|
registry:
|
||||||
|
address: nacos://127.0.0.1:8848
|
||||||
|
scan:
|
||||||
|
base-packages: org.xyzh.platform.service.impl
|
||||||
|
|
||||||
|
# ================== MyBatis ==================
|
||||||
|
mybatis-plus:
|
||||||
|
mapper-locations: classpath:mapper/**/*.xml
|
||||||
|
type-aliases-package: org.xyzh.common.dto, org.xyzh.api
|
||||||
|
|
||||||
|
# ================== Logging ==================
|
||||||
|
logging:
|
||||||
|
config: classpath:log4j2.xml
|
||||||
|
charset:
|
||||||
|
console: UTF-8
|
||||||
|
file: UTF-8
|
||||||
85
urbanLifelineServ/platform/src/main/resources/log4j2.xml
Normal file
85
urbanLifelineServ/platform/src/main/resources/log4j2.xml
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration status="WARN" monitorInterval="30">
|
||||||
|
<Properties>
|
||||||
|
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
|
||||||
|
<property name="FILE_PATH" value="./logs" />
|
||||||
|
<property name="FILE_NAME" value="platform-service" />
|
||||||
|
<property name="file.encoding" value="UTF-8" />
|
||||||
|
<property name="console.encoding" value="UTF-8" />
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
<appenders>
|
||||||
|
<console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
</console>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${FILE_NAME}-info.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/${FILE_NAME}-warn.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/${FILE_NAME}-error.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
</appenders>
|
||||||
|
|
||||||
|
<loggers>
|
||||||
|
<logger name="com.alibaba.nacos" level="info" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<logger name="org.mybatis" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<Logger name="org.springframework" level="info" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<Logger name="org.xyzh.platform" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFileInfo"/>
|
||||||
|
<AppenderRef ref="RollingFileWarn"/>
|
||||||
|
<AppenderRef ref="RollingFileError"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<Logger name="org.xyzh.common" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFileInfo"/>
|
||||||
|
<AppenderRef ref="RollingFileWarn"/>
|
||||||
|
<AppenderRef ref="RollingFileError"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="Console"/>
|
||||||
|
<appender-ref ref="RollingFileInfo"/>
|
||||||
|
<appender-ref ref="RollingFileWarn"/>
|
||||||
|
<appender-ref ref="RollingFileError"/>
|
||||||
|
</root>
|
||||||
|
</loggers>
|
||||||
|
</configuration>
|
||||||
@@ -58,6 +58,7 @@
|
|||||||
<!-- SLF4J API version -->
|
<!-- SLF4J API version -->
|
||||||
<slf4j.version>2.0.13</slf4j.version>
|
<slf4j.version>2.0.13</slf4j.version>
|
||||||
<!-- MyBatis / MyBatis-Plus -->
|
<!-- MyBatis / MyBatis-Plus -->
|
||||||
|
<mybatis.version>3.5.16</mybatis.version>
|
||||||
<mybatis.spring.boot.version>3.0.5</mybatis.spring.boot.version>
|
<mybatis.spring.boot.version>3.0.5</mybatis.spring.boot.version>
|
||||||
<mybatis.plus.version>3.5.14</mybatis.plus.version>
|
<mybatis.plus.version>3.5.14</mybatis.plus.version>
|
||||||
<!-- Align mybatis-spring core with Spring Boot 3.x (avoid pulling legacy 2.1.2) -->
|
<!-- Align mybatis-spring core with Spring Boot 3.x (avoid pulling legacy 2.1.2) -->
|
||||||
@@ -168,6 +169,12 @@
|
|||||||
<artifactId>HikariCP</artifactId>
|
<artifactId>HikariCP</artifactId>
|
||||||
<version>${hikaricp.version}</version>
|
<version>${hikaricp.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mybatis</groupId>
|
||||||
|
<artifactId>mybatis</artifactId>
|
||||||
|
<version>${mybatis.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- MyBatis Spring Boot Starter (base) -->
|
<!-- MyBatis Spring Boot Starter (base) -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -201,7 +208,11 @@
|
|||||||
<artifactId>fastjson2</artifactId>
|
<artifactId>fastjson2</artifactId>
|
||||||
<version>${fastjson.version}</version>
|
<version>${fastjson.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.fastjson2</groupId>
|
||||||
|
<artifactId>fastjson2-extension-spring6</artifactId>
|
||||||
|
<version>${fastjson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<!-- urban-lifeline 系统 -->
|
<!-- urban-lifeline 系统 -->
|
||||||
@@ -386,22 +397,6 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-security</artifactId>
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
</dependency>
|
|
||||||
<!-- Spring Boot Web -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- SpringDoc OpenAPI -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springdoc</groupId>
|
|
||||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
|
||||||
<!-- 排除默认的logback依赖 -->
|
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@@ -410,14 +405,58 @@
|
|||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Boot Web -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-logging</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Log4j2 日志(统一配置,所有子模块继承) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-log4j2</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- SpringDoc OpenAPI -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- dubbo依赖管理 -->
|
<!-- dubbo依赖管理 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.dubbo</groupId>
|
<groupId>org.apache.dubbo</groupId>
|
||||||
<artifactId>dubbo-spring-boot-starter</artifactId>
|
<artifactId>dubbo-spring-boot-starter</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-logging</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.dubbo</groupId>
|
<groupId>org.apache.dubbo</groupId>
|
||||||
<artifactId>dubbo-nacos-spring-boot-starter</artifactId>
|
<artifactId>dubbo-nacos-spring-boot-starter</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-logging</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-core</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 数据库连接池 -->
|
<!-- 数据库连接池 -->
|
||||||
@@ -425,11 +464,6 @@
|
|||||||
<groupId>org.postgresql</groupId>
|
<groupId>org.postgresql</groupId>
|
||||||
<artifactId>postgresql</artifactId>
|
<artifactId>postgresql</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- 添加log4j2依赖 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-log4j2</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.fastjson2</groupId>
|
<groupId>com.alibaba.fastjson2</groupId>
|
||||||
<artifactId>fastjson2</artifactId>
|
<artifactId>fastjson2</artifactId>
|
||||||
|
|||||||
@@ -29,66 +29,58 @@
|
|||||||
<groupId>org.xyzh.apis</groupId>
|
<groupId>org.xyzh.apis</groupId>
|
||||||
<artifactId>api-system</artifactId>
|
<artifactId>api-system</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- Spring Boot Actuator(父 pom 未包含) -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
</dependency>
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
<!-- Spring Boot Web -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-logging</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Dubbo Spring Boot Starter -->
|
<!-- Spring Cloud Alibaba Nacos Discovery(父 pom 未包含,用于服务发现) -->
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.dubbo</groupId>
|
|
||||||
<artifactId>dubbo-spring-boot-starter</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Dubbo Nacos Spring Boot Starter (服务注册与发现) -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.dubbo</groupId>
|
|
||||||
<artifactId>dubbo-nacos-spring-boot-starter</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Spring Cloud Alibaba Nacos Discovery (可选,用于服务发现) -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.cloud</groupId>
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.alibaba.nacos</groupId>
|
||||||
|
<artifactId>nacos-logback-adapter-12</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.alibaba.nacos</groupId>
|
||||||
|
<artifactId>logback-adapter</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MyBatis(父 pom 未包含) -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mybatis.spring.boot</groupId>
|
<groupId>org.mybatis.spring.boot</groupId>
|
||||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||||
<version>${mybatis.spring.boot.version}</version>
|
<version>${mybatis.spring.boot.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- MyBatis-Plus (可选,如不需要增强 CRUD 可移除) -->
|
<!-- MyBatis-Plus -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.baomidou</groupId>
|
<groupId>com.baomidou</groupId>
|
||||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||||
<version>${mybatis.plus.version}</version>
|
<version>${mybatis.plus.version}</version>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<!-- 排除旧版 mybatis-spring,防止与 3.x 冲突 -->
|
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>org.mybatis</groupId>
|
<groupId>org.mybatis</groupId>
|
||||||
<artifactId>mybatis-spring</artifactId>
|
<artifactId>mybatis-spring</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- 强制引入与 Spring Boot 3.x 匹配的 mybatis-spring 3.x -->
|
<!-- 强制引入 mybatis-spring 3.x -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mybatis</groupId>
|
<groupId>org.mybatis</groupId>
|
||||||
<artifactId>mybatis-spring</artifactId>
|
<artifactId>mybatis-spring</artifactId>
|
||||||
<version>${mybatis.spring.version}</version>
|
<version>${mybatis.spring.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Spring Security -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-security</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
@@ -20,6 +20,15 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
|||||||
@Mapper
|
@Mapper
|
||||||
public interface TbSysConfigMapper extends BaseMapper<TbSysConfigDTO> {
|
public interface TbSysConfigMapper extends BaseMapper<TbSysConfigDTO> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 从key读取配置
|
||||||
|
* @param
|
||||||
|
* @author yslg
|
||||||
|
* @since 2025-12-05
|
||||||
|
*/
|
||||||
|
TbSysConfigDTO selectSysConfigByKey(@Param("configKey") String configKey);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 插入系统配置
|
* @description 插入系统配置
|
||||||
* @param configDTO 系统配置DTO
|
* @param configDTO 系统配置DTO
|
||||||
|
|||||||
@@ -41,6 +41,190 @@ public class SysConfigServiceImpl implements SysConfigService {
|
|||||||
@Resource
|
@Resource
|
||||||
private TbSysConfigMapper configMapper;
|
private TbSysConfigMapper configMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据key查询配置
|
||||||
|
*/
|
||||||
|
private TbSysConfigDTO getConfigByKey(String key) {
|
||||||
|
if (key == null || key.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return configMapper.selectSysConfigByKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getSysConfig(String key) {
|
||||||
|
try {
|
||||||
|
TbSysConfigDTO config = getConfigByKey(key);
|
||||||
|
if (config == null) {
|
||||||
|
logger.warn("配置项不存在: {}", key);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String configType = config.getConfigType();
|
||||||
|
String configValue = config.getValue();
|
||||||
|
|
||||||
|
if (configValue == null || configValue.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据config_type返回对应的类型
|
||||||
|
if (configType == null || "string".equalsIgnoreCase(configType)) {
|
||||||
|
return configValue;
|
||||||
|
} else if ("number".equalsIgnoreCase(configType) || "integer".equalsIgnoreCase(configType)) {
|
||||||
|
try {
|
||||||
|
// 尝试解析为Integer,如果失败则解析为Long
|
||||||
|
return Integer.parseInt(configValue);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
try {
|
||||||
|
return Long.parseLong(configValue);
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
logger.error("配置项 {} 的值无法转换为数字: {}", key, configValue);
|
||||||
|
return configValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ("boolean".equalsIgnoreCase(configType)) {
|
||||||
|
String value = configValue.toLowerCase().trim();
|
||||||
|
if ("true".equals(value) || "1".equals(value) || "yes".equals(value) || "on".equals(value)) {
|
||||||
|
return true;
|
||||||
|
} else if ("false".equals(value) || "0".equals(value) || "no".equals(value) || "off".equals(value)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
logger.warn("配置项 {} 的值无法识别为Boolean: {}", key, value);
|
||||||
|
return configValue;
|
||||||
|
}
|
||||||
|
} else if ("double".equalsIgnoreCase(configType) || "float".equalsIgnoreCase(configType)) {
|
||||||
|
try {
|
||||||
|
return Double.parseDouble(configValue);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
logger.error("配置项 {} 的值无法转换为Double: {}", key, configValue);
|
||||||
|
return configValue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 未知类型,直接返回字符串
|
||||||
|
return configValue;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("获取配置失败: {}", key, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStringConfig(String key) {
|
||||||
|
try {
|
||||||
|
TbSysConfigDTO config = getConfigByKey(key);
|
||||||
|
if (config == null) {
|
||||||
|
logger.warn("配置项不存在: {}", key);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return config.getValue();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("获取字符串配置失败: {}", key, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getIntConfig(String key) {
|
||||||
|
try {
|
||||||
|
TbSysConfigDTO config = getConfigByKey(key);
|
||||||
|
if (config == null) {
|
||||||
|
logger.warn("配置项不存在: {}", key);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String value = config.getValue();
|
||||||
|
if (value == null || value.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Integer.parseInt(value);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
logger.error("配置项 {} 的值无法转换为Integer: {}", key, e.getMessage());
|
||||||
|
return null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("获取Integer配置失败: {}", key, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean getBooleanConfig(String key) {
|
||||||
|
try {
|
||||||
|
TbSysConfigDTO config = getConfigByKey(key);
|
||||||
|
if (config == null) {
|
||||||
|
logger.warn("配置项不存在: {}", key);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String value = config.getValue();
|
||||||
|
if (value == null || value.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支持多种布尔值表示:true/false, 1/0, yes/no, on/off
|
||||||
|
value = value.toLowerCase().trim();
|
||||||
|
if ("true".equals(value) || "1".equals(value) || "yes".equals(value) || "on".equals(value)) {
|
||||||
|
return true;
|
||||||
|
} else if ("false".equals(value) || "0".equals(value) || "no".equals(value) || "off".equals(value)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
logger.warn("配置项 {} 的值无法识别为Boolean: {}", key, value);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("获取Boolean配置失败: {}", key, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Double getDoubleConfig(String key) {
|
||||||
|
try {
|
||||||
|
TbSysConfigDTO config = getConfigByKey(key);
|
||||||
|
if (config == null) {
|
||||||
|
logger.warn("配置项不存在: {}", key);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String value = config.getValue();
|
||||||
|
if (value == null || value.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Double.parseDouble(value);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
logger.error("配置项 {} 的值无法转换为Double: {}", key, e.getMessage());
|
||||||
|
return null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("获取Double配置失败: {}", key, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getLongConfig(String key) {
|
||||||
|
try {
|
||||||
|
TbSysConfigDTO config = getConfigByKey(key);
|
||||||
|
if (config == null) {
|
||||||
|
logger.warn("配置项不存在: {}", key);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String value = config.getValue();
|
||||||
|
if (value == null || value.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Long.parseLong(value);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
logger.error("配置项 {} 的值无法转换为Long: {}", key, e.getMessage());
|
||||||
|
return null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("获取Long配置失败: {}", key, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResultDomain<TbSysConfigDTO> insertConfig(TbSysConfigDTO configDTO) {
|
public ResultDomain<TbSysConfigDTO> insertConfig(TbSysConfigDTO configDTO) {
|
||||||
if (configDTO == null) {
|
if (configDTO == null) {
|
||||||
|
|||||||
@@ -19,10 +19,15 @@ import org.xyzh.common.core.page.PageParam;
|
|||||||
import org.xyzh.common.core.page.PageDomain;
|
import org.xyzh.common.core.page.PageDomain;
|
||||||
|
|
||||||
import org.xyzh.common.utils.IDUtils;
|
import org.xyzh.common.utils.IDUtils;
|
||||||
|
import org.xyzh.common.utils.NonUtils;
|
||||||
import org.xyzh.common.utils.StringUtils;
|
import org.xyzh.common.utils.StringUtils;
|
||||||
|
import org.xyzh.common.utils.crypto.AesEncryptUtil;
|
||||||
import org.xyzh.system.mapper.user.TbSysUserMapper;
|
import org.xyzh.system.mapper.user.TbSysUserMapper;
|
||||||
import org.xyzh.system.mapper.user.TbSysUserInfoMapper;
|
import org.xyzh.system.mapper.user.TbSysUserInfoMapper;
|
||||||
import org.xyzh.system.mapper.user.TbSysUserRoleMapper;
|
import org.xyzh.system.mapper.user.TbSysUserRoleMapper;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 用户服务实现类
|
* @description 用户服务实现类
|
||||||
@@ -52,6 +57,112 @@ public class SysUserServiceImpl implements SysUserService {
|
|||||||
@Resource
|
@Resource
|
||||||
private TbSysUserRoleMapper userRoleMapper;
|
private TbSysUserRoleMapper userRoleMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AesEncryptUtil aesEncryptUtil;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public ResultDomain<TbSysUserDTO> registerUser(SysUserVO userVO) {
|
||||||
|
try {
|
||||||
|
logger.info("开始注册用户:{}", userVO.getUsername());
|
||||||
|
|
||||||
|
// 检查用户是否已存在
|
||||||
|
if (checkUserExists(userVO)) {
|
||||||
|
return ResultDomain.failure("用户已存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为 DTO
|
||||||
|
TbSysUserDTO dto = SysUserVO.toDTO(userVO);
|
||||||
|
|
||||||
|
// 设置用户基本信息
|
||||||
|
Date now = new Date();
|
||||||
|
if (StringUtils.isBlank(dto.getUserId())) {
|
||||||
|
dto.setUserId(IDUtils.generateID());
|
||||||
|
}
|
||||||
|
dto.setCreateTime(now);
|
||||||
|
dto.setDeleted(false);
|
||||||
|
|
||||||
|
// 加密密码
|
||||||
|
if (StringUtils.isNotBlank(dto.getPassword())) {
|
||||||
|
dto.setPassword(passwordEncoder.encode(dto.getPassword()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插入用户主表
|
||||||
|
int rows = userMapper.insertUser(dto);
|
||||||
|
if (rows <= 0) {
|
||||||
|
logger.warn("插入用户失败, username={}", dto.getUsername());
|
||||||
|
return ResultDomain.failure("插入用户失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建用户信息表
|
||||||
|
TbSysUserInfoDTO userInfo = new TbSysUserInfoDTO();
|
||||||
|
userInfo.setUserId(dto.getUserId());
|
||||||
|
userInfo.setCreateTime(now);
|
||||||
|
userInfo.setAvatar("default");
|
||||||
|
userInfoMapper.insertUserInfo(userInfo);
|
||||||
|
|
||||||
|
// 分配默认角色(role_guest)
|
||||||
|
TbSysUserRoleDTO userRole = new TbSysUserRoleDTO();
|
||||||
|
userRole.setUserId(dto.getUserId());
|
||||||
|
userRole.setRoleId("role_guest");
|
||||||
|
userRole.setCreateTime(now);
|
||||||
|
userRoleMapper.insertUserRole(userRole);
|
||||||
|
|
||||||
|
logger.info("注册用户成功, userId={}, username={}", dto.getUserId(), dto.getUsername());
|
||||||
|
return ResultDomain.success("注册用户成功", dto);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("注册用户失败:{}", userVO.getUsername(), e);
|
||||||
|
return ResultDomain.failure("注册用户失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查用户是否已存在
|
||||||
|
* @param userVO 用户信息
|
||||||
|
* @return true-存在,false-不存在
|
||||||
|
*/
|
||||||
|
private boolean checkUserExists(SysUserVO userVO) {
|
||||||
|
TbSysUserDTO filter = new TbSysUserDTO();
|
||||||
|
|
||||||
|
// 检查用户名是否存在
|
||||||
|
if (StringUtils.isNotBlank(userVO.getUsername())) {
|
||||||
|
filter.setUsername(userVO.getUsername());
|
||||||
|
List<SysUserVO> users = userMapper.getUserByFilter(filter);
|
||||||
|
if (users != null && !users.isEmpty()) {
|
||||||
|
logger.warn("用户名已存在: {}", userVO.getUsername());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查手机号是否存在
|
||||||
|
if (StringUtils.isNotBlank(userVO.getPhone())) {
|
||||||
|
filter = new TbSysUserDTO();
|
||||||
|
filter.setPhone(userVO.getPhone());
|
||||||
|
List<SysUserVO> users = userMapper.getUserByFilter(filter);
|
||||||
|
if (users != null && !users.isEmpty()) {
|
||||||
|
logger.warn("手机号已存在: {}", userVO.getPhone());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查邮箱是否存在
|
||||||
|
if (StringUtils.isNotBlank(userVO.getEmail())) {
|
||||||
|
filter = new TbSysUserDTO();
|
||||||
|
filter.setEmail(userVO.getEmail());
|
||||||
|
List<SysUserVO> users = userMapper.getUserByFilter(filter);
|
||||||
|
if (users != null && !users.isEmpty()) {
|
||||||
|
logger.warn("邮箱已存在: {}", userVO.getEmail());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResultDomain<TbSysUserDTO> insertUser(SysUserVO userVO) {
|
public ResultDomain<TbSysUserDTO> insertUser(SysUserVO userVO) {
|
||||||
if (userVO == null) {
|
if (userVO == null) {
|
||||||
@@ -120,9 +231,12 @@ public class SysUserServiceImpl implements SysUserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResultDomain<SysUserVO> getLoginUser(SysUserVO filter) {
|
public ResultDomain<TbSysUserDTO> getLoginUser(TbSysUserDTO filter) {
|
||||||
// 登录查询语义与 getUser 相同(可根据用户名/手机号/邮箱查询)
|
// 登录查询语义与 getUser 相同(可根据用户名/手机号/邮箱查询)
|
||||||
return getUser(filter);
|
if(NonUtils.isNotNull(filter.getPhone())){
|
||||||
|
filter.setPhone_hash(aesEncryptUtil.encrypt(filter.getPhone()));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ spring:
|
|||||||
# ================== DataSource ==================
|
# ================== DataSource ==================
|
||||||
datasource:
|
datasource:
|
||||||
# 按你的实际库名改一下,比如 urban-lifeline_system
|
# 按你的实际库名改一下,比如 urban-lifeline_system
|
||||||
url: jdbc:postgresql://127.0.0.1:5432/urban-lifeline # 换成你的 PG 库名
|
url: jdbc:postgresql://127.0.0.1:5432/urban_lifeline # 换成你的 PG 库名
|
||||||
username: postgres # PG 用户
|
username: postgres # PG 用户
|
||||||
password: "123456" # PG 密码
|
password: postgres # PG 密码
|
||||||
driver-class-name: org.postgresql.Driver
|
driver-class-name: org.postgresql.Driver
|
||||||
|
|
||||||
# ================== Redis ==================
|
# ================== Redis ==================
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# ================== Server ==================
|
# ================== Server ==================
|
||||||
server:
|
server:
|
||||||
port: 8082
|
port: 8182
|
||||||
servlet:
|
servlet:
|
||||||
context-path: /urban-lifeline/system
|
context-path: /urban-lifeline/system
|
||||||
# ================== Auth ====================
|
# ================== Auth ====================
|
||||||
@@ -34,15 +34,30 @@ urban-lifeline:
|
|||||||
# - /public/**
|
# - /public/**
|
||||||
# - /api/public/**
|
# - /api/public/**
|
||||||
|
|
||||||
|
security:
|
||||||
|
aes:
|
||||||
|
secret-key: 1234567890qwer
|
||||||
|
|
||||||
|
|
||||||
# ================== Spring ==================
|
# ================== Spring ==================
|
||||||
spring:
|
spring:
|
||||||
|
application:
|
||||||
|
name: system-service
|
||||||
|
|
||||||
|
# ================== Spring Cloud Nacos ==================
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
server-addr: 127.0.0.1:8848
|
||||||
|
namespace: dev
|
||||||
|
group: DEFAULT_GROUP
|
||||||
|
|
||||||
# ================== DataSource ==================
|
# ================== DataSource ==================
|
||||||
datasource:
|
datasource:
|
||||||
# 按你的实际库名改一下,比如 urban-lifeline_system
|
# 按你的实际库名改一下,比如 urban-lifeline_system
|
||||||
url: jdbc:postgresql://127.0.0.1:5432/urban-lifeline # 换成你的 PG 库名
|
url: jdbc:postgresql://127.0.0.1:5432/urban_lifeline # 换成你的 PG 库名
|
||||||
username: postgres # PG 用户
|
username: postgres # PG 用户
|
||||||
password: "123456" # PG 密码
|
password: postgres # PG 密码
|
||||||
driver-class-name: org.postgresql.Driver
|
driver-class-name: org.postgresql.Driver
|
||||||
|
|
||||||
# ================== Redis ==================
|
# ================== Redis ==================
|
||||||
@@ -51,7 +66,7 @@ spring:
|
|||||||
host: 127.0.0.1 # 如果是 docker 跑的 redis,按实际 host / 端口改
|
host: 127.0.0.1 # 如果是 docker 跑的 redis,按实际 host / 端口改
|
||||||
port: 6379
|
port: 6379
|
||||||
database: 0
|
database: 0
|
||||||
# password: "" # 如果有密码就填上,没密码可以去掉这一行
|
password: 123456 # 如果有密码就填上,没密码可以去掉这一行
|
||||||
# ================== SpringDoc ==================
|
# ================== SpringDoc ==================
|
||||||
springdoc:
|
springdoc:
|
||||||
api-docs:
|
api-docs:
|
||||||
@@ -97,3 +112,10 @@ dubbo:
|
|||||||
mybatis-plus:
|
mybatis-plus:
|
||||||
mapper-locations: classpath:mapper/*.xml
|
mapper-locations: classpath:mapper/*.xml
|
||||||
type-aliases-package: org.xyzh.common.dto, org.xyzh.api
|
type-aliases-package: org.xyzh.common.dto, org.xyzh.api
|
||||||
|
|
||||||
|
# ================== Logging ==================
|
||||||
|
logging:
|
||||||
|
config: classpath:log4j2.xml
|
||||||
|
charset:
|
||||||
|
console: UTF-8
|
||||||
|
file: UTF-8
|
||||||
|
|||||||
85
urbanLifelineServ/system/src/main/resources/log4j2.xml
Normal file
85
urbanLifelineServ/system/src/main/resources/log4j2.xml
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration status="WARN" monitorInterval="30">
|
||||||
|
<Properties>
|
||||||
|
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
|
||||||
|
<property name="FILE_PATH" value="./logs" />
|
||||||
|
<property name="FILE_NAME" value="system-service" />
|
||||||
|
<property name="file.encoding" value="UTF-8" />
|
||||||
|
<property name="console.encoding" value="UTF-8" />
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
<appenders>
|
||||||
|
<console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
</console>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${FILE_NAME}-info.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/${FILE_NAME}-warn.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/${FILE_NAME}-error.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
</appenders>
|
||||||
|
|
||||||
|
<loggers>
|
||||||
|
<logger name="com.alibaba.nacos" level="info" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<logger name="org.mybatis" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<Logger name="org.springframework" level="info" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<Logger name="org.xyzh.system" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFileInfo"/>
|
||||||
|
<AppenderRef ref="RollingFileWarn"/>
|
||||||
|
<AppenderRef ref="RollingFileError"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<Logger name="org.xyzh.common" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFileInfo"/>
|
||||||
|
<AppenderRef ref="RollingFileWarn"/>
|
||||||
|
<AppenderRef ref="RollingFileError"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="Console"/>
|
||||||
|
<appender-ref ref="RollingFileInfo"/>
|
||||||
|
<appender-ref ref="RollingFileWarn"/>
|
||||||
|
<appender-ref ref="RollingFileError"/>
|
||||||
|
</root>
|
||||||
|
</loggers>
|
||||||
|
</configuration>
|
||||||
@@ -67,6 +67,15 @@
|
|||||||
optsn, creator, updater, dept_path, remark, create_time, update_time, delete_time, deleted
|
optsn, creator, updater, dept_path, remark, create_time, update_time, delete_time, deleted
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
|
<!-- selectSysConfigByKey -->
|
||||||
|
|
||||||
|
<select id="selectSysConfigByKey">
|
||||||
|
SELECT <include refid="Base_Column_List" />
|
||||||
|
FROM config.tb_sys_config
|
||||||
|
WHERE config_key = #{configKey}
|
||||||
|
AND deleted = 0
|
||||||
|
</select>
|
||||||
|
|
||||||
<!-- 插入系统配置 -->
|
<!-- 插入系统配置 -->
|
||||||
<insert id="insertConfig" parameterType="TbSysConfigDTO">
|
<insert id="insertConfig" parameterType="TbSysConfigDTO">
|
||||||
INSERT INTO config.tb_sys_config
|
INSERT INTO config.tb_sys_config
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>org.xyzh</groupId>
|
<groupId>org.xyzh</groupId>
|
||||||
<artifactId>case</artifactId>
|
<artifactId>workcase</artifactId>
|
||||||
<version>1.0.0</version>
|
<version>1.0.0</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# ================== Server ==================
|
||||||
|
server:
|
||||||
|
port: 8188
|
||||||
|
servlet:
|
||||||
|
context-path: /urban-lifeline/workcase
|
||||||
|
|
||||||
|
# ================== Auth ====================
|
||||||
|
urban-lifeline:
|
||||||
|
auth:
|
||||||
|
enabled: true
|
||||||
|
whitelist:
|
||||||
|
- /swagger-ui/**
|
||||||
|
- /swagger-ui.html
|
||||||
|
- /v3/api-docs/**
|
||||||
|
- /webjars/**
|
||||||
|
- /favicon.ico
|
||||||
|
- /error
|
||||||
|
- /actuator/health
|
||||||
|
- /actuator/info
|
||||||
|
|
||||||
|
security:
|
||||||
|
aes:
|
||||||
|
secret-key: 1234567890qwer
|
||||||
|
|
||||||
|
# ================== Spring ==================
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: workcase-service
|
||||||
|
|
||||||
|
# ================== Spring Cloud Nacos ==================
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
server-addr: 127.0.0.1:8848
|
||||||
|
namespace: dev
|
||||||
|
group: DEFAULT_GROUP
|
||||||
|
|
||||||
|
# ================== DataSource ==================
|
||||||
|
datasource:
|
||||||
|
url: jdbc:postgresql://127.0.0.1:5432/urban_lifeline
|
||||||
|
username: postgres
|
||||||
|
password: postgres
|
||||||
|
driver-class-name: org.postgresql.Driver
|
||||||
|
|
||||||
|
# ================== Redis ==================
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: 127.0.0.1 # 如果是 docker 跑的 redis,按实际 host / 端口改
|
||||||
|
port: 6379
|
||||||
|
database: 0
|
||||||
|
password: 123456 # 如果有密码就填上,没密码可以去掉这一行
|
||||||
|
|
||||||
|
# ================== SpringDoc ==================
|
||||||
|
springdoc:
|
||||||
|
api-docs:
|
||||||
|
enabled: true
|
||||||
|
path: /v3/api-docs
|
||||||
|
swagger-ui:
|
||||||
|
enabled: true
|
||||||
|
path: /swagger-ui.html
|
||||||
|
group-configs:
|
||||||
|
- group: 'default'
|
||||||
|
display-name: '工单服务 API'
|
||||||
|
paths-to-match: '/**'
|
||||||
|
|
||||||
|
# ================== Dubbo + Nacos ==================
|
||||||
|
dubbo:
|
||||||
|
application:
|
||||||
|
name: urban-lifeline-workcase
|
||||||
|
qos-enable: false
|
||||||
|
protocol:
|
||||||
|
name: dubbo
|
||||||
|
port: -1
|
||||||
|
registry:
|
||||||
|
address: nacos://127.0.0.1:8848
|
||||||
|
scan:
|
||||||
|
base-packages: org.xyzh.workcase.service.impl
|
||||||
|
|
||||||
|
# ================== MyBatis ==================
|
||||||
|
mybatis-plus:
|
||||||
|
mapper-locations: classpath:mapper/**/*.xml
|
||||||
|
type-aliases-package: org.xyzh.common.dto, org.xyzh.api
|
||||||
|
|
||||||
|
# ================== Logging ==================
|
||||||
|
logging:
|
||||||
|
config: classpath:log4j2.xml
|
||||||
|
charset:
|
||||||
|
console: UTF-8
|
||||||
|
file: UTF-8
|
||||||
85
urbanLifelineServ/workcase/src/main/resources/log4j2.xml
Normal file
85
urbanLifelineServ/workcase/src/main/resources/log4j2.xml
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration status="WARN" monitorInterval="30">
|
||||||
|
<Properties>
|
||||||
|
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
|
||||||
|
<property name="FILE_PATH" value="./logs" />
|
||||||
|
<property name="FILE_NAME" value="workcase-service" />
|
||||||
|
<property name="file.encoding" value="UTF-8" />
|
||||||
|
<property name="console.encoding" value="UTF-8" />
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
<appenders>
|
||||||
|
<console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
</console>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${FILE_NAME}-info.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/${FILE_NAME}-warn.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
|
||||||
|
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/${FILE_NAME}-error.log"
|
||||||
|
filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
|
||||||
|
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
|
||||||
|
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy interval="1"/>
|
||||||
|
<SizeBasedTriggeringPolicy size="100MB"/>
|
||||||
|
</Policies>
|
||||||
|
<DefaultRolloverStrategy max="30"/>
|
||||||
|
</RollingFile>
|
||||||
|
</appenders>
|
||||||
|
|
||||||
|
<loggers>
|
||||||
|
<logger name="com.alibaba.nacos" level="info" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<logger name="org.mybatis" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<Logger name="org.springframework" level="info" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<Logger name="org.xyzh.workcase" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFileInfo"/>
|
||||||
|
<AppenderRef ref="RollingFileWarn"/>
|
||||||
|
<AppenderRef ref="RollingFileError"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<Logger name="org.xyzh.common" level="debug" additivity="false">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
<AppenderRef ref="RollingFileInfo"/>
|
||||||
|
<AppenderRef ref="RollingFileWarn"/>
|
||||||
|
<AppenderRef ref="RollingFileError"/>
|
||||||
|
</Logger>
|
||||||
|
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="Console"/>
|
||||||
|
<appender-ref ref="RollingFileInfo"/>
|
||||||
|
<appender-ref ref="RollingFileWarn"/>
|
||||||
|
<appender-ref ref="RollingFileError"/>
|
||||||
|
</root>
|
||||||
|
</loggers>
|
||||||
|
</configuration>
|
||||||
50
urbanLifelineWeb/.vscode/launch.json
vendored
Normal file
50
urbanLifelineWeb/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Launch Chrome (Shared)",
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"url": "http://localhost:5000",
|
||||||
|
"webRoot": "${workspaceFolder}/packages/shared",
|
||||||
|
"runtimeExecutable": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
||||||
|
"sourceMaps": true,
|
||||||
|
"sourceMapPathOverrides": {
|
||||||
|
"/@/*": "${workspaceFolder}/packages/shared/*",
|
||||||
|
"/src/*": "${workspaceFolder}/packages/shared/src/*"
|
||||||
|
},
|
||||||
|
"userDataDir": "${workspaceFolder}/.vscode/chrome-debug-profile",
|
||||||
|
"trace": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "启动 Shared 开发服务器",
|
||||||
|
"type": "node-terminal",
|
||||||
|
"request": "launch",
|
||||||
|
"command": "npm run dev",
|
||||||
|
"cwd": "${workspaceFolder}/packages/shared",
|
||||||
|
"presentation": {
|
||||||
|
"echo": true,
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": false,
|
||||||
|
"panel": "new",
|
||||||
|
"showReuseMessage": true,
|
||||||
|
"clear": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compounds": [
|
||||||
|
{
|
||||||
|
"name": "启动 Shared 并打开 Chrome",
|
||||||
|
"configurations": [
|
||||||
|
"启动 Shared 开发服务器",
|
||||||
|
"Launch Chrome (Shared)"
|
||||||
|
],
|
||||||
|
"stopAll": true,
|
||||||
|
"presentation": {
|
||||||
|
"hidden": false,
|
||||||
|
"group": "",
|
||||||
|
"order": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user