Compare commits

...

3 Commits

Author SHA1 Message Date
a8233ceb72 服务启动 2025-12-05 15:34:02 +08:00
ab8be1a832 权限注解 2025-12-05 11:41:21 +08:00
917e9a517a 部分dto、vo 2025-12-05 11:05:27 +08:00
112 changed files with 6061 additions and 970 deletions

View File

@@ -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: 不需要,这些表的数据在系统运行时动态产生
## 联系支持
如有问题,请联系技术支持团队。

View File

@@ -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'

View File

@@ -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:禁用

View File

@@ -0,0 +1,153 @@
-- ====================================================
-- 定时任务表
-- ====================================================
CREATE SCHEMA IF NOT EXISTS crontab;
DROP TABLE IF EXISTS crontab.tb_crontab_task CASCADE;
CREATE TABLE crontab.tb_crontab_task (
id VARCHAR(64) NOT NULL,
task_id VARCHAR(64) NOT NULL,
task_name VARCHAR(100) NOT NULL,
task_group VARCHAR(50) NOT NULL DEFAULT 'DEFAULT',
meta_id VARCHAR(64) NOT NULL,
default_recipient SMALLINT NOT NULL DEFAULT 0, -- 是否使用默认接收人0:否 1:是)
bean_name VARCHAR(100) NOT NULL,
method_name VARCHAR(100) NOT NULL,
method_params VARCHAR(500) DEFAULT NULL,
cron_expression VARCHAR(100) NOT NULL,
status SMALLINT NOT NULL DEFAULT 0, -- 任务状态0:暂停 1:运行中)
description VARCHAR(500) DEFAULT NULL,
concurrent SMALLINT NOT NULL DEFAULT 0, -- 是否允许并发执行0:否 1:是)
misfire_policy SMALLINT NOT NULL DEFAULT 1, -- 错过执行策略1:立即执行 2:执行一次 3:放弃执行)
creator VARCHAR(64) DEFAULT NULL,
updater VARCHAR(64) DEFAULT NULL,
create_time TIMESTAMPTZ NOT NULL DEFAULT now(),
update_time TIMESTAMPTZ DEFAULT NULL,
delete_time TIMESTAMPTZ DEFAULT NULL,
deleted SMALLINT NOT NULL DEFAULT 0, -- 是否删除0:否 1:是)
PRIMARY KEY (id)
);
CREATE INDEX IF NOT EXISTS idx_task_name ON crontab.tb_crontab_task(task_name);
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 crontab.tb_crontab_log CASCADE;
CREATE TABLE crontab.tb_crontab_log (
id VARCHAR(64) NOT NULL,
task_id VARCHAR(64) NOT NULL,
task_name VARCHAR(100) NOT NULL,
task_group VARCHAR(50) NOT NULL DEFAULT 'DEFAULT',
bean_name VARCHAR(100) NOT NULL,
method_name VARCHAR(100) NOT NULL,
method_params VARCHAR(500) DEFAULT NULL,
execute_status SMALLINT NOT NULL, -- 执行状态0:失败 1:成功)
execute_message TEXT DEFAULT NULL,
exception_info TEXT DEFAULT NULL,
start_time TIMESTAMPTZ NOT NULL,
end_time TIMESTAMPTZ DEFAULT NULL,
execute_duration INT DEFAULT NULL,
create_time TIMESTAMPTZ NOT NULL DEFAULT now(),
update_time TIMESTAMPTZ DEFAULT NULL,
delete_time TIMESTAMPTZ DEFAULT NULL,
deleted SMALLINT NOT NULL DEFAULT 0, -- 是否删除0:否 1:是)
PRIMARY KEY (id)
);
CREATE INDEX IF NOT EXISTS idx_task_id ON crontab.tb_crontab_log(task_id);
CREATE INDEX IF NOT EXISTS idx_log_task_name ON crontab.tb_crontab_log(task_name);
CREATE INDEX IF NOT EXISTS idx_execute_status ON crontab.tb_crontab_log(execute_status);
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 crontab.tb_crontab_task_meta CASCADE;
CREATE TABLE crontab.tb_crontab_task_meta (
id VARCHAR(64) NOT NULL,
meta_id VARCHAR(64) NOT NULL,
name VARCHAR(100) NOT NULL,
description VARCHAR(500) DEFAULT NULL,
category VARCHAR(50) NOT NULL,
bean_name VARCHAR(100) NOT NULL,
method_name VARCHAR(100) NOT NULL,
script_path VARCHAR(255) DEFAULT NULL,
param_schema TEXT DEFAULT NULL,
auto_publish SMALLINT NOT NULL DEFAULT 0, -- 是否自动发布
sort_order INT DEFAULT 0,
creator VARCHAR(64) DEFAULT NULL,
updater VARCHAR(64) DEFAULT NULL,
create_time TIMESTAMPTZ NOT NULL DEFAULT now(),
update_time TIMESTAMPTZ DEFAULT NULL,
delete_time TIMESTAMPTZ DEFAULT NULL,
deleted SMALLINT NOT NULL DEFAULT 0, -- 是否删除0:否 1:是)
PRIMARY KEY (id),
UNIQUE (meta_id)
);
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:是)';

View File

@@ -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;

View File

@@ -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密钥、服务器地址等

View File

@@ -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, -- 更新时间(由触发器维护)

View File

@@ -1,12 +1,12 @@
-- ============================= -- =============================
-- 智能客服系统业务模块 -- 智能客服系统业务模块(工单系统)
-- 支持微信小程序客户咨询、智能问答、工单管理、CRM集成 -- 支持微信小程序客户咨询、智能问答、工单管理、CRM集成
-- ============================= -- =============================
CREATE SCHEMA IF NOT EXISTS customer_service; CREATE SCHEMA IF NOT EXISTS workcase;
-- 客户信息表 -- 客户信息表
DROP TABLE IF EXISTS customer_service.tb_customer CASCADE; DROP TABLE IF EXISTS workcase.tb_customer CASCADE;
CREATE TABLE customer_service.tb_customer ( CREATE TABLE workcase.tb_customer (
optsn VARCHAR(50) NOT NULL, -- 流水号 optsn VARCHAR(50) NOT NULL, -- 流水号
customer_id VARCHAR(50) NOT NULL, -- 客户ID customer_id VARCHAR(50) NOT NULL, -- 客户ID
customer_no VARCHAR(100), -- 客户编号 customer_no VARCHAR(100), -- 客户编号
@@ -45,16 +45,16 @@ CREATE TABLE customer_service.tb_customer (
UNIQUE (email) UNIQUE (email)
); );
CREATE INDEX idx_customer_type ON customer_service.tb_customer(customer_type) WHERE deleted = false; CREATE INDEX idx_customer_type ON workcase.tb_customer(customer_type) WHERE deleted = false;
CREATE INDEX idx_customer_level ON customer_service.tb_customer(customer_level) WHERE deleted = false; CREATE INDEX idx_customer_level ON workcase.tb_customer(customer_level) WHERE deleted = false;
CREATE INDEX idx_customer_wechat ON customer_service.tb_customer(wechat_openid) WHERE deleted = false; CREATE INDEX idx_customer_wechat ON workcase.tb_customer(wechat_openid) WHERE deleted = false;
COMMENT ON TABLE customer_service.tb_customer IS '客户信息表'; COMMENT ON TABLE workcase.tb_customer IS '客户信息表';
COMMENT ON COLUMN customer_service.tb_customer.customer_level IS '客户等级vip/important/normal/potential'; COMMENT ON COLUMN workcase.tb_customer.customer_level IS '客户等级vip/important/normal/potential';
-- 会话表 -- 会话表
DROP TABLE IF EXISTS customer_service.tb_conversation CASCADE; DROP TABLE IF EXISTS workcase.tb_conversation CASCADE;
CREATE TABLE customer_service.tb_conversation ( CREATE TABLE workcase.tb_conversation (
optsn VARCHAR(50) NOT NULL, -- 流水号 optsn VARCHAR(50) NOT NULL, -- 流水号
conversation_id VARCHAR(50) NOT NULL, -- 会话ID conversation_id VARCHAR(50) NOT NULL, -- 会话ID
customer_id VARCHAR(50) NOT NULL, -- 客户ID customer_id VARCHAR(50) NOT NULL, -- 客户ID
@@ -81,19 +81,19 @@ CREATE TABLE customer_service.tb_conversation (
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除 deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
PRIMARY KEY (conversation_id), PRIMARY KEY (conversation_id),
UNIQUE (optsn), UNIQUE (optsn),
FOREIGN KEY (customer_id) REFERENCES customer_service.tb_customer(customer_id) FOREIGN KEY (customer_id) REFERENCES workcase.tb_customer(customer_id)
); );
CREATE INDEX idx_conv_customer ON customer_service.tb_conversation(customer_id, session_start_time DESC) WHERE deleted = false; CREATE INDEX idx_conv_customer ON workcase.tb_conversation(customer_id, session_start_time DESC) WHERE deleted = false;
CREATE INDEX idx_conv_status ON customer_service.tb_conversation(conversation_status) WHERE deleted = false; CREATE INDEX idx_conv_status ON workcase.tb_conversation(conversation_status) WHERE deleted = false;
CREATE INDEX idx_conv_agent ON customer_service.tb_conversation(agent_id) WHERE deleted = false; CREATE INDEX idx_conv_agent ON workcase.tb_conversation(agent_id) WHERE deleted = false;
COMMENT ON TABLE customer_service.tb_conversation IS '会话表'; COMMENT ON TABLE workcase.tb_conversation IS '会话表';
COMMENT ON COLUMN customer_service.tb_conversation.conversation_type IS '会话类型ai/human/transfer'; COMMENT ON COLUMN workcase.tb_conversation.conversation_type IS '会话类型ai/human/transfer';
-- 会话消息表 -- 会话消息表
DROP TABLE IF EXISTS customer_service.tb_conversation_message CASCADE; DROP TABLE IF EXISTS workcase.tb_conversation_message CASCADE;
CREATE TABLE customer_service.tb_conversation_message ( CREATE TABLE workcase.tb_conversation_message (
optsn VARCHAR(50) NOT NULL, -- 流水号 optsn VARCHAR(50) NOT NULL, -- 流水号
message_id VARCHAR(50) NOT NULL, -- 消息ID message_id VARCHAR(50) NOT NULL, -- 消息ID
conversation_id VARCHAR(50) NOT NULL, -- 所属会话ID conversation_id VARCHAR(50) NOT NULL, -- 所属会话ID
@@ -117,18 +117,18 @@ CREATE TABLE customer_service.tb_conversation_message (
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除 deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
PRIMARY KEY (message_id), PRIMARY KEY (message_id),
UNIQUE (optsn), UNIQUE (optsn),
FOREIGN KEY (conversation_id) REFERENCES customer_service.tb_conversation(conversation_id) FOREIGN KEY (conversation_id) REFERENCES workcase.tb_conversation(conversation_id)
); );
CREATE INDEX idx_msg_conversation ON customer_service.tb_conversation_message(conversation_id, create_time) WHERE deleted = false; CREATE INDEX idx_msg_conversation ON workcase.tb_conversation_message(conversation_id, create_time) WHERE deleted = false;
CREATE INDEX idx_msg_sender ON customer_service.tb_conversation_message(sender_id) WHERE deleted = false; CREATE INDEX idx_msg_sender ON workcase.tb_conversation_message(sender_id) WHERE deleted = false;
COMMENT ON TABLE customer_service.tb_conversation_message IS '会话消息表'; COMMENT ON TABLE workcase.tb_conversation_message IS '会话消息表';
COMMENT ON COLUMN customer_service.tb_conversation_message.sentiment IS '情感分析positive/neutral/negative'; COMMENT ON COLUMN workcase.tb_conversation_message.sentiment IS '情感分析positive/neutral/negative';
-- 工单表 -- 工单表
DROP TABLE IF EXISTS customer_service.tb_ticket CASCADE; DROP TABLE IF EXISTS workcase.tb_ticket CASCADE;
CREATE TABLE customer_service.tb_ticket ( CREATE TABLE workcase.tb_ticket (
optsn VARCHAR(50) NOT NULL, -- 流水号 optsn VARCHAR(50) NOT NULL, -- 流水号
ticket_id VARCHAR(50) NOT NULL, -- 工单ID ticket_id VARCHAR(50) NOT NULL, -- 工单ID
ticket_no VARCHAR(100) NOT NULL, -- 工单编号 ticket_no VARCHAR(100) NOT NULL, -- 工单编号
@@ -166,21 +166,21 @@ CREATE TABLE customer_service.tb_ticket (
PRIMARY KEY (ticket_id), PRIMARY KEY (ticket_id),
UNIQUE (optsn), UNIQUE (optsn),
UNIQUE (ticket_no), UNIQUE (ticket_no),
FOREIGN KEY (customer_id) REFERENCES customer_service.tb_customer(customer_id) FOREIGN KEY (customer_id) REFERENCES workcase.tb_customer(customer_id)
); );
CREATE INDEX idx_ticket_customer ON customer_service.tb_ticket(customer_id) WHERE deleted = false; CREATE INDEX idx_ticket_customer ON workcase.tb_ticket(customer_id) WHERE deleted = false;
CREATE INDEX idx_ticket_status ON customer_service.tb_ticket(ticket_status) WHERE deleted = false; CREATE INDEX idx_ticket_status ON workcase.tb_ticket(ticket_status) WHERE deleted = false;
CREATE INDEX idx_ticket_assigned ON customer_service.tb_ticket(assigned_to) WHERE deleted = false; CREATE INDEX idx_ticket_assigned ON workcase.tb_ticket(assigned_to) WHERE deleted = false;
CREATE INDEX idx_ticket_priority ON customer_service.tb_ticket(priority) WHERE deleted = false; CREATE INDEX idx_ticket_priority ON workcase.tb_ticket(priority) WHERE deleted = false;
CREATE INDEX idx_ticket_sla ON customer_service.tb_ticket(sla_deadline) WHERE deleted = false AND is_overdue = false; CREATE INDEX idx_ticket_sla ON workcase.tb_ticket(sla_deadline) WHERE deleted = false AND is_overdue = false;
COMMENT ON TABLE customer_service.tb_ticket IS '工单表'; COMMENT ON TABLE workcase.tb_ticket IS '工单表';
COMMENT ON COLUMN customer_service.tb_ticket.ticket_type IS '工单类型consultation/complaint/suggestion/repair/installation/other'; COMMENT ON COLUMN workcase.tb_ticket.ticket_type IS '工单类型consultation/complaint/suggestion/repair/installation/other';
-- 工单处理记录表 -- 工单处理记录表
DROP TABLE IF EXISTS customer_service.tb_ticket_log CASCADE; DROP TABLE IF EXISTS workcase.tb_ticket_log CASCADE;
CREATE TABLE customer_service.tb_ticket_log ( CREATE TABLE workcase.tb_ticket_log (
optsn VARCHAR(50) NOT NULL, -- 流水号 optsn VARCHAR(50) NOT NULL, -- 流水号
log_id VARCHAR(50) NOT NULL, -- 日志ID log_id VARCHAR(50) NOT NULL, -- 日志ID
ticket_id VARCHAR(50) NOT NULL, -- 工单ID ticket_id VARCHAR(50) NOT NULL, -- 工单ID
@@ -195,16 +195,16 @@ CREATE TABLE customer_service.tb_ticket_log (
create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间
PRIMARY KEY (log_id), PRIMARY KEY (log_id),
UNIQUE (optsn), UNIQUE (optsn),
FOREIGN KEY (ticket_id) REFERENCES customer_service.tb_ticket(ticket_id) FOREIGN KEY (ticket_id) REFERENCES workcase.tb_ticket(ticket_id)
); );
CREATE INDEX idx_ticket_log_ticket ON customer_service.tb_ticket_log(ticket_id, create_time DESC); CREATE INDEX idx_ticket_log_ticket ON workcase.tb_ticket_log(ticket_id, create_time DESC);
COMMENT ON TABLE customer_service.tb_ticket_log IS '工单处理记录表'; COMMENT ON TABLE workcase.tb_ticket_log IS '工单处理记录表';
-- FAQ表常见问题 -- FAQ表常见问题
DROP TABLE IF EXISTS customer_service.tb_faq CASCADE; DROP TABLE IF EXISTS workcase.tb_faq CASCADE;
CREATE TABLE customer_service.tb_faq ( CREATE TABLE workcase.tb_faq (
optsn VARCHAR(50) NOT NULL, -- 流水号 optsn VARCHAR(50) NOT NULL, -- 流水号
faq_id VARCHAR(50) NOT NULL, -- FAQ ID faq_id VARCHAR(50) NOT NULL, -- FAQ ID
knowledge_id VARCHAR(50), -- 关联知识库ID knowledge_id VARCHAR(50), -- 关联知识库ID
@@ -229,14 +229,14 @@ CREATE TABLE customer_service.tb_faq (
UNIQUE (optsn) UNIQUE (optsn)
); );
CREATE INDEX idx_faq_category ON customer_service.tb_faq(category) WHERE deleted = false; CREATE INDEX idx_faq_category ON workcase.tb_faq(category) WHERE deleted = false;
CREATE INDEX idx_faq_published ON customer_service.tb_faq(is_published) WHERE deleted = false AND is_published = true; CREATE INDEX idx_faq_published ON workcase.tb_faq(is_published) WHERE deleted = false AND is_published = true;
COMMENT ON TABLE customer_service.tb_faq IS 'FAQ常见问题表'; COMMENT ON TABLE workcase.tb_faq IS 'FAQ常见问题表';
-- 客服评价表 -- 客服评价表
DROP TABLE IF EXISTS customer_service.tb_service_evaluation CASCADE; DROP TABLE IF EXISTS workcase.tb_service_evaluation CASCADE;
CREATE TABLE customer_service.tb_service_evaluation ( CREATE TABLE workcase.tb_service_evaluation (
optsn VARCHAR(50) NOT NULL, -- 流水号 optsn VARCHAR(50) NOT NULL, -- 流水号
evaluation_id VARCHAR(50) NOT NULL, -- 评价ID evaluation_id VARCHAR(50) NOT NULL, -- 评价ID
customer_id VARCHAR(50) NOT NULL, -- 客户ID customer_id VARCHAR(50) NOT NULL, -- 客户ID
@@ -253,17 +253,17 @@ CREATE TABLE customer_service.tb_service_evaluation (
deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除 deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除
PRIMARY KEY (evaluation_id), PRIMARY KEY (evaluation_id),
UNIQUE (optsn), UNIQUE (optsn),
FOREIGN KEY (customer_id) REFERENCES customer_service.tb_customer(customer_id) FOREIGN KEY (customer_id) REFERENCES workcase.tb_customer(customer_id)
); );
CREATE INDEX idx_eval_customer ON customer_service.tb_service_evaluation(customer_id) WHERE deleted = false; CREATE INDEX idx_eval_customer ON workcase.tb_service_evaluation(customer_id) WHERE deleted = false;
CREATE INDEX idx_eval_rating ON customer_service.tb_service_evaluation(rating) WHERE deleted = false; CREATE INDEX idx_eval_rating ON workcase.tb_service_evaluation(rating) WHERE deleted = false;
COMMENT ON TABLE customer_service.tb_service_evaluation IS '客服评价表'; COMMENT ON TABLE workcase.tb_service_evaluation IS '客服评价表';
-- CRM集成配置表 -- CRM集成配置表
DROP TABLE IF EXISTS customer_service.tb_crm_config CASCADE; DROP TABLE IF EXISTS workcase.tb_crm_config CASCADE;
CREATE TABLE customer_service.tb_crm_config ( CREATE TABLE workcase.tb_crm_config (
optsn VARCHAR(50) NOT NULL, -- 流水号 optsn VARCHAR(50) NOT NULL, -- 流水号
config_id VARCHAR(50) NOT NULL, -- 配置ID config_id VARCHAR(50) NOT NULL, -- 配置ID
crm_system VARCHAR(50) NOT NULL, -- CRM系统名称 crm_system VARCHAR(50) NOT NULL, -- CRM系统名称
@@ -286,4 +286,4 @@ CREATE TABLE customer_service.tb_crm_config (
UNIQUE (optsn) UNIQUE (optsn)
); );
COMMENT ON TABLE customer_service.tb_crm_config IS 'CRM集成配置表'; COMMENT ON TABLE workcase.tb_crm_config IS 'CRM集成配置表';

View File

@@ -0,0 +1 @@
1028

View File

@@ -0,0 +1 @@
1003

View File

@@ -0,0 +1 @@
1053

View File

@@ -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
}
} }
] ]
} }

View File

@@ -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
View 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

View File

@@ -0,0 +1,77 @@
# ================== 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
# ================== Spring ==================
spring:
application:
name: agent-service
# ================== 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

View 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>

View File

@@ -0,0 +1,9 @@
package org.xyzh.api.agent;
/**
* Agent服务接口
* 用于知识库和文档管理
*/
public interface AgentService {
}

View File

@@ -0,0 +1,51 @@
package org.xyzh.api.agent.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.fasterxml.jackson.databind.JsonNode;
/**
* 知识库DTO
* 用于创建和更新知识库
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "知识库DTO")
public class KnowledgeBaseDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Schema(description = "知识库ID更新时需要")
private String knowledgeId;
@Schema(description = "智能体ID")
private String agentId;
@Schema(description = "知识库名称")
private String name;
@Schema(description = "知识库类型bidding-招投标/customer_service-客服/internal-内部协同")
private String kbType;
@Schema(description = "访问级别public-公开/private-私有/internal-内部")
private String accessLevel;
@Schema(description = "知识库描述")
private String description;
@Schema(description = "存储路径")
private String storagePath;
@Schema(description = "当前版本号")
private String version;
@Schema(description = "知识库配置")
private JsonNode config;
@Schema(description = "服务类型")
private String serviceType;
@Schema(description = "状态active-激活/inactive-停用/archived-归档")
private String status;
}

View File

@@ -0,0 +1,48 @@
package org.xyzh.api.agent.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.fasterxml.jackson.databind.JsonNode;
/**
* 知识文档片段DTO
* 用于创建和更新知识文档片段
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "知识文档片段DTO")
public class KnowledgeChunkDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Schema(description = "片段ID更新时需要")
private String chunkId;
@Schema(description = "所属文档ID")
private String docId;
@Schema(description = "所属知识库ID")
private String knowledgeId;
@Schema(description = "片段索引")
private Integer chunkIndex;
@Schema(description = "片段内容")
private String content;
@Schema(description = "片段类型text-文本/table-表格/image-图片")
private String chunkType;
@Schema(description = "根chunk ID")
private String rootChunkId;
@Schema(description = "是否当前版本", defaultValue = "true")
private Boolean isCurrent;
@Schema(description = "位置信息")
private JsonNode positionInfo;
@Schema(description = "片段元数据")
private JsonNode metadata;
}

View File

@@ -0,0 +1,67 @@
package org.xyzh.api.agent.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.List;
/**
* 知识文档DTO
* 用于创建和更新知识文档
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "知识文档DTO")
public class KnowledgeDocumentDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Schema(description = "文档ID更新时需要")
private String docId;
@Schema(description = "所属知识库ID")
private String knowledgeId;
@Schema(description = "文档标题")
private String title;
@Schema(description = "文档类型text-文本/pdf/word/excel/image/video")
private String docType;
@Schema(description = "文档分类")
private String category;
@Schema(description = "文档内容(文本类型)")
private String content;
@Schema(description = "关联文件ID")
private String fileId;
@Schema(description = "文件路径")
private String filePath;
@Schema(description = "文件大小")
private Long fileSize;
@Schema(description = "MIME类型")
private String mimeType;
@Schema(description = "根文档ID")
private String rootDocId;
@Schema(description = "文档标签")
private List<String> tags;
@Schema(description = "来源URL")
private String sourceUrl;
@Schema(description = "服务类型")
private String serviceType;
@Schema(description = "文档元数据")
private JsonNode metadata;
@Schema(description = "状态active-激活/inactive-停用/archived-归档")
private String status;
}

View File

@@ -0,0 +1,93 @@
package org.xyzh.api.agent.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.Date;
/**
* 知识库VO
* 用于前端展示知识库信息
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "知识库VO")
public class KnowledgeBaseVO extends BaseVO {
private static final long serialVersionUID = 1L;
@Schema(description = "知识库ID")
private String knowledgeId;
@Schema(description = "智能体ID")
private String agentId;
@Schema(description = "智能体名称")
private String agentName;
@Schema(description = "知识库名称")
private String name;
@Schema(description = "知识库类型")
private String kbType;
@Schema(description = "知识库类型名称")
private String kbTypeName;
@Schema(description = "访问级别")
private String accessLevel;
@Schema(description = "访问级别名称")
private String accessLevelName;
@Schema(description = "知识库描述")
private String description;
@Schema(description = "存储路径")
private String storagePath;
@Schema(description = "当前版本号")
private String version;
@Schema(description = "知识库配置")
private JsonNode config;
@Schema(description = "服务类型")
private String serviceType;
@Schema(description = "部门名称")
private String deptName;
@Schema(description = "状态")
private String status;
@Schema(description = "状态名称")
private String statusName;
@Schema(description = "状态颜色")
private String statusColor;
@Schema(description = "文档总数")
private Long documentCount;
@Schema(description = "已向量化文档数")
private Long embeddedDocCount;
@Schema(description = "chunk总数")
private Long chunkCount;
@Schema(description = "总存储大小(字节)")
private Long totalSize;
@Schema(description = "总存储大小格式化显示")
private String totalSizeFormatted;
@Schema(description = "最后同步时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date lastSyncTime;
@Schema(description = "创建者姓名")
private String creatorName;
}

View File

@@ -0,0 +1,68 @@
package org.xyzh.api.agent.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.Date;
/**
* 知识文档片段VO
* 用于前端展示知识文档片段信息
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "知识文档片段VO")
public class KnowledgeChunkVO extends BaseVO {
private static final long serialVersionUID = 1L;
@Schema(description = "片段ID")
private String chunkId;
@Schema(description = "所属文档ID")
private String docId;
@Schema(description = "文档标题")
private String docTitle;
@Schema(description = "所属知识库ID")
private String knowledgeId;
@Schema(description = "知识库名称")
private String knowledgeName;
@Schema(description = "片段索引")
private Integer chunkIndex;
@Schema(description = "片段内容")
private String content;
@Schema(description = "内容长度")
private Integer contentLength;
@Schema(description = "片段类型")
private String chunkType;
@Schema(description = "片段类型名称")
private String chunkTypeName;
@Schema(description = "版本号")
private Integer version;
@Schema(description = "根chunk ID")
private String rootChunkId;
@Schema(description = "是否当前版本", defaultValue = "false")
private Boolean isCurrent;
@Schema(description = "位置信息")
private JsonNode positionInfo;
@Schema(description = "片段元数据")
private JsonNode metadata;
@Schema(description = "部门名称")
private String deptName;
}

View File

@@ -0,0 +1,114 @@
package org.xyzh.api.agent.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.Date;
import java.util.List;
/**
* 知识文档VO
* 用于前端展示知识文档信息
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "知识文档VO")
public class KnowledgeDocumentVO extends BaseVO {
private static final long serialVersionUID = 1L;
@Schema(description = "文档ID")
private String docId;
@Schema(description = "所属知识库ID")
private String knowledgeId;
@Schema(description = "知识库名称")
private String knowledgeName;
@Schema(description = "文档标题")
private String title;
@Schema(description = "文档类型")
private String docType;
@Schema(description = "文档类型名称")
private String docTypeName;
@Schema(description = "文档分类")
private String category;
@Schema(description = "文档内容")
private String content;
@Schema(description = "内容摘要")
private String contentSummary;
@Schema(description = "关联文件ID")
private String fileId;
@Schema(description = "文件路径")
private String filePath;
@Schema(description = "文件下载URL")
private String fileUrl;
@Schema(description = "文件大小")
private Long fileSize;
@Schema(description = "文件大小格式化显示")
private String fileSizeFormatted;
@Schema(description = "MIME类型")
private String mimeType;
@Schema(description = "版本号")
private Integer version;
@Schema(description = "根文档ID")
private String rootDocId;
@Schema(description = "文档标签")
private List<String> tags;
@Schema(description = "关键词")
private List<String> keywords;
@Schema(description = "向量化状态")
private String embeddingStatus;
@Schema(description = "向量化状态名称")
private String embeddingStatusName;
@Schema(description = "使用的向量化模型")
private String embeddingModel;
@Schema(description = "切片数量")
private Integer chunkCount;
@Schema(description = "文档元数据")
private JsonNode metadata;
@Schema(description = "来源URL")
private String sourceUrl;
@Schema(description = "服务类型")
private String serviceType;
@Schema(description = "部门名称")
private String deptName;
@Schema(description = "状态")
private String status;
@Schema(description = "状态名称")
private String statusName;
@Schema(description = "创建者姓名")
private String creatorName;
@Schema(description = "更新者姓名")
private String updaterName;
}

View File

@@ -1,7 +0,0 @@
package org.xyzh;
public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
}
}

View File

@@ -0,0 +1,5 @@
package org.xyzh.api.bidding;
public interface BiddingService {
}

View File

@@ -0,0 +1,56 @@
package org.xyzh.api.bidding.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* 投标文件DTO
* 用于创建和更新投标文件
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "投标文件DTO")
public class BidResponseDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Schema(description = "响应文件ID更新时需要")
private String responseId;
@Schema(description = "所属项目ID")
private String projectId;
@Schema(description = "响应类型technical-技术标/commercial-商务标/comprehensive-综合标")
private String responseType;
@Schema(description = "文档名称")
private String docName;
@Schema(description = "文档大纲")
private String outline;
@Schema(description = "文档内容")
private String content;
@Schema(description = "生成方式ai-AI生成/template-模板生成/manual-人工编写")
private String generationMethod;
@Schema(description = "使用的模板ID")
private String templateId;
@Schema(description = "使用的AI模型")
private String aiModel;
@Schema(description = "生成状态draft-草稿/reviewing-审核中/approved-已批准/rejected-已拒绝/submitted-已提交")
private String generationStatus;
@Schema(description = "版本号")
private String version;
@Schema(description = "父版本ID")
private String parentVersionId;
@Schema(description = "审核意见")
private String reviewComments;
}

View File

@@ -0,0 +1,47 @@
package org.xyzh.api.bidding.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* 招标文档DTO
* 用于创建和更新招标文档
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "招标文档DTO")
public class BiddingDocumentDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Schema(description = "文档ID更新时需要")
private String docId;
@Schema(description = "所属项目ID")
private String projectId;
@Schema(description = "文档类型tender-招标文件/technical-技术标/commercial-商务标/clarification-澄清文件/other-其他")
private String docType;
@Schema(description = "文档名称")
private String docName;
@Schema(description = "关联文件ID")
private String fileId;
@Schema(description = "文件路径")
private String filePath;
@Schema(description = "文件大小")
private Long fileSize;
@Schema(description = "MIME类型")
private String mimeType;
@Schema(description = "版本号")
private String version;
@Schema(description = "语言")
private String language;
}

View File

@@ -0,0 +1,93 @@
package org.xyzh.api.bidding.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* 招标项目DTO
* 用于创建和更新招标项目
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "招标项目DTO")
public class BiddingProjectDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Schema(description = "项目ID更新时需要")
private String projectId;
@Schema(description = "项目编号")
private String projectNo;
@Schema(description = "项目名称")
private String projectName;
@Schema(description = "项目类型public-公开招标/invitation-邀请招标/competitive_negotiation-竞争性谈判")
private String projectType;
@Schema(description = "所属行业")
private String industry;
@Schema(description = "来源平台")
private String sourcePlatform;
@Schema(description = "来源URL")
private String sourceUrl;
@Schema(description = "发布日期", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date publishDate;
@Schema(description = "投标截止日期", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date deadline;
@Schema(description = "开标日期", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date openingDate;
@Schema(description = "预算金额")
private BigDecimal budgetAmount;
@Schema(description = "货币单位")
private String currency;
@Schema(description = "项目状态")
private String projectStatus;
@Schema(description = "中标状态")
private String winningStatus;
@Schema(description = "中标金额")
private BigDecimal winningAmount;
@Schema(description = "客户名称")
private String clientName;
@Schema(description = "客户联系方式")
private String clientContact;
@Schema(description = "联系人")
private String contactPerson;
@Schema(description = "项目地点")
private String projectLocation;
@Schema(description = "项目描述")
private String description;
@Schema(description = "关键词")
private List<String> keywords;
@Schema(description = "负责人")
private String responsibleUser;
@Schema(description = "团队成员")
private List<String> teamMembers;
}

View File

@@ -0,0 +1,67 @@
package org.xyzh.api.bidding.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.fasterxml.jackson.databind.JsonNode;
import java.math.BigDecimal;
/**
* 招标要素DTO
* 用于创建和更新招标要素
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "招标要素DTO")
public class BiddingRequirementDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Schema(description = "要素ID更新时需要")
private String reqId;
@Schema(description = "所属项目ID")
private String projectId;
@Schema(description = "来源文档ID")
private String docId;
@Schema(description = "要素类别commercial-商务要素/technical-技术参数/veto-否决项/qualification-资质要求/delivery-交付要求/payment-付款条件/scoring-评分标准")
private String reqCategory;
@Schema(description = "要素名称")
private String reqName;
@Schema(description = "要素内容")
private String reqContent;
@Schema(description = "要素值")
private String reqValue;
@Schema(description = "是否必填", defaultValue = "false")
private Boolean isMandatory;
@Schema(description = "是否为否决项", defaultValue = "false")
private Boolean isVeto;
@Schema(description = "优先级")
private Integer priority;
@Schema(description = "提取方式ai-AI提取/manual-人工录入")
private String extractionMethod;
@Schema(description = "置信度分数")
private BigDecimal confidenceScore;
@Schema(description = "来源位置")
private JsonNode sourceLocation;
@Schema(description = "合规状态compliant-符合/non_compliant-不符合/pending-待确认")
private String complianceStatus;
@Schema(description = "响应内容")
private String responseContent;
@Schema(description = "备注")
private String notes;
}

View File

@@ -0,0 +1,66 @@
package org.xyzh.api.bidding.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import java.util.Date;
import java.util.List;
/**
* 项目流程节点DTO
* 用于创建和更新流程节点
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "项目流程节点DTO")
public class ProcessNodeDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Schema(description = "流程节点ID更新时需要")
private String processId;
@Schema(description = "所属项目ID")
private String projectId;
@Schema(description = "节点名称")
private String nodeName;
@Schema(description = "节点类型collection-文件收集/analysis-需求分析/preparation-文件准备/review-内部审核/submission-投标提交/opening-开标/result-结果通知")
private String nodeType;
@Schema(description = "节点顺序")
private Integer nodeOrder;
@Schema(description = "节点状态pending-待处理/in_progress-进行中/completed-已完成/skipped-已跳过")
private String nodeStatus;
@Schema(description = "计划开始时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date plannedStartTime;
@Schema(description = "计划结束时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date plannedEndTime;
@Schema(description = "实际开始时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date actualStartTime;
@Schema(description = "实际结束时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date actualEndTime;
@Schema(description = "负责人")
private String responsibleUser;
@Schema(description = "参与人员")
private List<String> participants;
@Schema(description = "节点备注")
private String notes;
@Schema(description = "附件ID数组")
private List<String> attachments;
}

View File

@@ -0,0 +1,97 @@
package org.xyzh.api.bidding.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import java.util.Date;
/**
* 投标文件VO
* 用于前端展示投标文件信息
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "投标文件VO")
public class BidResponseVO extends BaseVO {
private static final long serialVersionUID = 1L;
@Schema(description = "响应文件ID")
private String responseId;
@Schema(description = "所属项目ID")
private String projectId;
@Schema(description = "项目名称")
private String projectName;
@Schema(description = "响应类型")
private String responseType;
@Schema(description = "响应类型名称(中文)")
private String responseTypeName;
@Schema(description = "文档名称")
private String docName;
@Schema(description = "文档大纲")
private String outline;
@Schema(description = "文档内容")
private String content;
@Schema(description = "内容字数")
private Integer contentWordCount;
@Schema(description = "生成方式")
private String generationMethod;
@Schema(description = "生成方式名称")
private String generationMethodName;
@Schema(description = "使用的模板ID")
private String templateId;
@Schema(description = "使用的模板名称")
private String templateName;
@Schema(description = "使用的AI模型")
private String aiModel;
@Schema(description = "生成状态")
private String generationStatus;
@Schema(description = "生成状态名称")
private String generationStatusName;
@Schema(description = "生成状态颜色标签")
private String statusColor;
@Schema(description = "关联文件ID")
private String fileId;
@Schema(description = "文件路径")
private String filePath;
@Schema(description = "文件下载URL")
private String fileUrl;
@Schema(description = "版本号")
private String version;
@Schema(description = "父版本ID")
private String parentVersionId;
@Schema(description = "是否有历史版本", defaultValue = "false")
private Boolean hasHistory;
@Schema(description = "审核意见")
private String reviewComments;
@Schema(description = "创建者姓名")
private String creatorName;
@Schema(description = "更新者姓名")
private String updaterName;
}

View File

@@ -0,0 +1,90 @@
package org.xyzh.api.bidding.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.Date;
/**
* 招标文档VO
* 用于前端展示招标文档信息
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "招标文档VO")
public class BiddingDocumentVO extends BaseVO {
private static final long serialVersionUID = 1L;
@Schema(description = "文档ID")
private String docId;
@Schema(description = "所属项目ID")
private String projectId;
@Schema(description = "项目名称")
private String projectName;
@Schema(description = "文档类型")
private String docType;
@Schema(description = "文档类型名称(中文)")
private String docTypeName;
@Schema(description = "文档名称")
private String docName;
@Schema(description = "关联文件ID")
private String fileId;
@Schema(description = "文件路径")
private String filePath;
@Schema(description = "文件下载URL")
private String fileUrl;
@Schema(description = "文件大小")
private Long fileSize;
@Schema(description = "文件大小格式化显示")
private String fileSizeFormatted;
@Schema(description = "MIME类型")
private String mimeType;
@Schema(description = "文件类型图标")
private String fileIcon;
@Schema(description = "版本号")
private String version;
@Schema(description = "语言")
private String language;
@Schema(description = "页数")
private Integer pageCount;
@Schema(description = "解析状态")
private String parseStatus;
@Schema(description = "解析状态名称")
private String parseStatusName;
@Schema(description = "解析结果")
private JsonNode parseResult;
@Schema(description = "提取的结构化数据")
private JsonNode extractionData;
@Schema(description = "AI分析结果")
private String aiAnalysis;
@Schema(description = "上传日期", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date uploadDate;
@Schema(description = "创建者姓名")
private String creatorName;
}

View File

@@ -0,0 +1,135 @@
package org.xyzh.api.bidding.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* 招标项目VO
* 用于前端展示招标项目详细信息
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "招标项目VO")
public class BiddingProjectVO extends BaseVO {
private static final long serialVersionUID = 1L;
@Schema(description = "项目ID")
private String projectId;
@Schema(description = "项目编号")
private String projectNo;
@Schema(description = "项目名称")
private String projectName;
@Schema(description = "项目类型")
private String projectType;
@Schema(description = "项目类型名称(中文)")
private String projectTypeName;
@Schema(description = "所属行业")
private String industry;
@Schema(description = "来源平台")
private String sourcePlatform;
@Schema(description = "来源URL")
private String sourceUrl;
@Schema(description = "发布日期", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date publishDate;
@Schema(description = "投标截止日期", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date deadline;
@Schema(description = "开标日期", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date openingDate;
@Schema(description = "距离截止日期剩余天数")
private Long daysUntilDeadline;
@Schema(description = "预算金额")
private BigDecimal budgetAmount;
@Schema(description = "货币单位")
private String currency;
@Schema(description = "预算金额格式化显示")
private String budgetAmountFormatted;
@Schema(description = "项目状态")
private String projectStatus;
@Schema(description = "项目状态名称(中文)")
private String projectStatusName;
@Schema(description = "中标状态")
private String winningStatus;
@Schema(description = "中标状态名称(中文)")
private String winningStatusName;
@Schema(description = "中标金额")
private BigDecimal winningAmount;
@Schema(description = "中标金额格式化显示")
private String winningAmountFormatted;
@Schema(description = "客户名称")
private String clientName;
@Schema(description = "客户联系方式")
private String clientContact;
@Schema(description = "联系人")
private String contactPerson;
@Schema(description = "项目地点")
private String projectLocation;
@Schema(description = "项目描述")
private String description;
@Schema(description = "关键词")
private List<String> keywords;
@Schema(description = "负责人ID")
private String responsibleUser;
@Schema(description = "负责人姓名")
private String responsibleUserName;
@Schema(description = "团队成员")
private List<String> teamMembers;
@Schema(description = "团队成员姓名列表")
private List<String> teamMemberNames;
@Schema(description = "部门名称")
private String deptName;
@Schema(description = "文档数量")
private Integer documentCount;
@Schema(description = "要素数量")
private Integer requirementCount;
@Schema(description = "否决项数量")
private Integer vetoCount;
@Schema(description = "投标文件数量")
private Integer responseCount;
@Schema(description = "完成进度(百分比)")
private Integer progressPercentage;
}

View File

@@ -0,0 +1,102 @@
package org.xyzh.api.bidding.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import com.fasterxml.jackson.databind.JsonNode;
import java.math.BigDecimal;
import java.util.Date;
/**
* 招标要素VO
* 用于前端展示招标要素信息
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "招标要素VO")
public class BiddingRequirementVO extends BaseVO {
private static final long serialVersionUID = 1L;
@Schema(description = "要素ID")
private String reqId;
@Schema(description = "所属项目ID")
private String projectId;
@Schema(description = "项目名称")
private String projectName;
@Schema(description = "来源文档ID")
private String docId;
@Schema(description = "来源文档名称")
private String docName;
@Schema(description = "要素类别")
private String reqCategory;
@Schema(description = "要素类别名称(中文)")
private String reqCategoryName;
@Schema(description = "要素名称")
private String reqName;
@Schema(description = "要素内容")
private String reqContent;
@Schema(description = "要素值")
private String reqValue;
@Schema(description = "是否必填", defaultValue = "false")
private Boolean isMandatory;
@Schema(description = "是否为否决项", defaultValue = "false")
private Boolean isVeto;
@Schema(description = "优先级")
private Integer priority;
@Schema(description = "优先级标签(高/中/低)")
private String priorityLabel;
@Schema(description = "提取方式")
private String extractionMethod;
@Schema(description = "提取方式名称")
private String extractionMethodName;
@Schema(description = "置信度分数")
private BigDecimal confidenceScore;
@Schema(description = "置信度百分比")
private Integer confidencePercentage;
@Schema(description = "来源位置")
private JsonNode sourceLocation;
@Schema(description = "来源位置描述")
private String sourceLocationDesc;
@Schema(description = "合规状态")
private String complianceStatus;
@Schema(description = "合规状态名称")
private String complianceStatusName;
@Schema(description = "合规状态标签颜色")
private String complianceStatusColor;
@Schema(description = "响应内容")
private String responseContent;
@Schema(description = "是否已响应", defaultValue = "false")
private Boolean hasResponse;
@Schema(description = "备注")
private String notes;
@Schema(description = "创建者姓名")
private String creatorName;
}

View File

@@ -0,0 +1,102 @@
package org.xyzh.api.bidding.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import java.util.Date;
import java.util.List;
/**
* 项目流程节点VO
* 用于前端展示流程节点信息
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "项目流程节点VO")
public class ProcessNodeVO extends BaseVO {
private static final long serialVersionUID = 1L;
@Schema(description = "流程节点ID")
private String processId;
@Schema(description = "所属项目ID")
private String projectId;
@Schema(description = "项目名称")
private String projectName;
@Schema(description = "节点名称")
private String nodeName;
@Schema(description = "节点类型")
private String nodeType;
@Schema(description = "节点类型名称")
private String nodeTypeName;
@Schema(description = "节点顺序")
private Integer nodeOrder;
@Schema(description = "节点状态")
private String nodeStatus;
@Schema(description = "节点状态名称")
private String nodeStatusName;
@Schema(description = "节点状态颜色")
private String statusColor;
@Schema(description = "节点图标")
private String nodeIcon;
@Schema(description = "计划开始时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date plannedStartTime;
@Schema(description = "计划结束时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date plannedEndTime;
@Schema(description = "实际开始时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date actualStartTime;
@Schema(description = "实际结束时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date actualEndTime;
@Schema(description = "计划耗时(天)")
private Integer plannedDuration;
@Schema(description = "实际耗时(天)")
private Integer actualDuration;
@Schema(description = "是否延期", defaultValue = "false")
private Boolean isDelayed;
@Schema(description = "延期天数")
private Integer delayedDays;
@Schema(description = "负责人ID")
private String responsibleUser;
@Schema(description = "负责人姓名")
private String responsibleUserName;
@Schema(description = "参与人员ID列表")
private List<String> participants;
@Schema(description = "参与人员姓名列表")
private List<String> participantNames;
@Schema(description = "节点备注")
private String notes;
@Schema(description = "附件ID数组")
private List<String> attachments;
@Schema(description = "附件数量")
private Integer attachmentCount;
}

View File

@@ -0,0 +1,70 @@
package org.xyzh.api.bidding.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* 招标项目列表VO简化版
* 用于前端列表展示
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "招标项目列表VO")
public class ProjectListVO extends BaseVO {
private static final long serialVersionUID = 1L;
@Schema(description = "项目ID")
private String projectId;
@Schema(description = "项目编号")
private String projectNo;
@Schema(description = "项目名称")
private String projectName;
@Schema(description = "项目类型")
private String projectType;
@Schema(description = "项目类型名称")
private String projectTypeName;
@Schema(description = "所属行业")
private String industry;
@Schema(description = "投标截止日期", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date deadline;
@Schema(description = "距离截止日期剩余天数")
private Long daysUntilDeadline;
@Schema(description = "是否即将截止(<3天", defaultValue = "false")
private Boolean isUrgent;
@Schema(description = "预算金额格式化显示")
private String budgetAmountFormatted;
@Schema(description = "项目状态")
private String projectStatus;
@Schema(description = "项目状态名称")
private String projectStatusName;
@Schema(description = "中标状态")
private String winningStatus;
@Schema(description = "客户名称")
private String clientName;
@Schema(description = "负责人姓名")
private String responsibleUserName;
@Schema(description = "完成进度(百分比)")
private Integer progressPercentage;
}

View File

@@ -0,0 +1,67 @@
package org.xyzh.api.bidding.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import java.math.BigDecimal;
import java.util.Map;
/**
* 项目统计VO
* 用于前端展示项目统计数据
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "项目统计VO")
public class ProjectStatisticsVO extends BaseVO {
private static final long serialVersionUID = 1L;
@Schema(description = "总项目数")
private Long totalProjects;
@Schema(description = "进行中的项目数")
private Long ongoingProjects;
@Schema(description = "已提交的项目数")
private Long submittedProjects;
@Schema(description = "已中标的项目数")
private Long wonProjects;
@Schema(description = "未中标的项目数")
private Long lostProjects;
@Schema(description = "中标率(百分比)")
private BigDecimal winRate;
@Schema(description = "即将截止的项目数(<3天")
private Long urgentProjects;
@Schema(description = "总预算金额")
private BigDecimal totalBudget;
@Schema(description = "总中标金额")
private BigDecimal totalWinningAmount;
@Schema(description = "按状态分组统计")
private Map<String, Long> projectsByStatus;
@Schema(description = "按类型分组统计")
private Map<String, Long> projectsByType;
@Schema(description = "按行业分组统计")
private Map<String, Long> projectsByIndustry;
@Schema(description = "按月份分组统计最近12个月")
private Map<String, Long> projectsByMonth;
@Schema(description = "按负责人分组统计")
private Map<String, Long> projectsByUser;
@Schema(description = "平均项目周期(天)")
private Integer avgProjectCycle;
@Schema(description = "最近7天新增项目数")
private Long recentNewProjects;
}

View File

@@ -1,7 +0,0 @@
package org.xyzh;
public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
}
}

View File

@@ -0,0 +1,9 @@
package org.xyzh.api.crontab;
/**
* 定时任务服务接口
* 用于定时任务管理
*/
public interface CrontabService {
}

View File

@@ -0,0 +1,57 @@
package org.xyzh.api.crontab.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import java.util.Date;
/**
* 定时任务执行日志DTO
* 用于记录任务执行情况
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "定时任务执行日志DTO")
public class TbCrontabLogDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Schema(description = "任务ID")
private String taskId;
@Schema(description = "任务名称")
private String taskName;
@Schema(description = "任务分组")
private String taskGroup;
@Schema(description = "Bean名称")
private String beanName;
@Schema(description = "方法名称")
private String methodName;
@Schema(description = "方法参数")
private String methodParams;
@Schema(description = "执行状态0-失败/1-成功")
private Integer executeStatus;
@Schema(description = "执行结果信息")
private String executeMessage;
@Schema(description = "异常信息")
private String exceptionInfo;
@Schema(description = "开始时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date startTime;
@Schema(description = "结束时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
@Schema(description = "执行时长(毫秒)")
private Integer executeDuration;
}

View File

@@ -0,0 +1,56 @@
package org.xyzh.api.crontab.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* 定时任务DTO
* 用于创建和更新定时任务
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "定时任务DTO")
public class TbCrontabTaskDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Schema(description = "任务ID更新时需要")
private String taskId;
@Schema(description = "任务名称")
private String taskName;
@Schema(description = "任务分组", defaultValue = "DEFAULT")
private String taskGroup;
@Schema(description = "任务元数据ID")
private String metaId;
@Schema(description = "是否使用默认接收人", defaultValue = "false")
private Boolean defaultRecipient;
@Schema(description = "Bean名称")
private String beanName;
@Schema(description = "方法名称")
private String methodName;
@Schema(description = "方法参数")
private String methodParams;
@Schema(description = "Cron表达式")
private String cronExpression;
@Schema(description = "任务状态0-暂停/1-运行中", defaultValue = "0")
private Integer status;
@Schema(description = "任务描述")
private String description;
@Schema(description = "是否允许并发执行", defaultValue = "false")
private Boolean concurrent;
@Schema(description = "错过执行策略1-立即执行/2-执行一次/3-放弃执行", defaultValue = "1")
private Integer misfirePolicy;
}

View File

@@ -0,0 +1,47 @@
package org.xyzh.api.crontab.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* 定时任务元数据DTO
* 用于创建和更新任务元数据配置
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "定时任务元数据DTO")
public class TbCrontabTaskMetaDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Schema(description = "元数据ID更新时需要")
private String metaId;
@Schema(description = "任务名称")
private String name;
@Schema(description = "任务描述")
private String description;
@Schema(description = "任务分类")
private String category;
@Schema(description = "Bean名称执行器类名")
private String beanName;
@Schema(description = "执行方法名")
private String methodName;
@Schema(description = "Python脚本路径")
private String scriptPath;
@Schema(description = "参数模板JSON格式")
private String paramSchema;
@Schema(description = "是否自动发布", defaultValue = "false")
private Boolean autoPublish;
@Schema(description = "排序号", defaultValue = "0")
private Integer sortOrder;
}

View File

@@ -0,0 +1,72 @@
package org.xyzh.api.crontab.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import java.util.Date;
/**
* 定时任务执行日志VO
* 用于前端展示任务执行日志
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "定时任务执行日志VO")
public class CrontabLogVO extends BaseVO {
private static final long serialVersionUID = 1L;
@Schema(description = "日志ID")
private String logId;
@Schema(description = "任务ID")
private String taskId;
@Schema(description = "任务名称")
private String taskName;
@Schema(description = "任务分组")
private String taskGroup;
@Schema(description = "Bean名称")
private String beanName;
@Schema(description = "方法名称")
private String methodName;
@Schema(description = "方法参数")
private String methodParams;
@Schema(description = "执行状态0-失败/1-成功")
private Integer executeStatus;
@Schema(description = "执行状态名称")
private String executeStatusName;
@Schema(description = "执行状态颜色")
private String executeStatusColor;
@Schema(description = "执行结果信息")
private String executeMessage;
@Schema(description = "异常信息")
private String exceptionInfo;
@Schema(description = "是否有异常", defaultValue = "false")
private Boolean hasException;
@Schema(description = "开始时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date startTime;
@Schema(description = "结束时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
@Schema(description = "执行时长(毫秒)")
private Integer executeDuration;
@Schema(description = "执行时长格式化显示")
private String executeDurationFormatted;
}

View File

@@ -0,0 +1,54 @@
package org.xyzh.api.crontab.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import java.util.Date;
/**
* 定时任务列表VO
* 用于前端列表展示(简化版)
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "定时任务列表VO")
public class CrontabTaskListVO extends BaseVO {
private static final long serialVersionUID = 1L;
@Schema(description = "任务ID")
private String taskId;
@Schema(description = "任务名称")
private String taskName;
@Schema(description = "任务分组")
private String taskGroup;
@Schema(description = "Cron表达式")
private String cronExpression;
@Schema(description = "Cron表达式描述")
private String cronDescription;
@Schema(description = "任务状态")
private Integer status;
@Schema(description = "任务状态名称")
private String statusName;
@Schema(description = "下次执行时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date nextExecuteTime;
@Schema(description = "最后执行时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date lastExecuteTime;
@Schema(description = "最后执行状态")
private Integer lastExecuteStatus;
@Schema(description = "创建者姓名")
private String creatorName;
}

View File

@@ -0,0 +1,56 @@
package org.xyzh.api.crontab.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* 定时任务元数据VO
* 用于前端展示任务元数据信息
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "定时任务元数据VO")
public class CrontabTaskMetaVO extends BaseVO {
private static final long serialVersionUID = 1L;
@Schema(description = "元数据ID")
private String metaId;
@Schema(description = "任务名称")
private String name;
@Schema(description = "任务描述")
private String description;
@Schema(description = "任务分类")
private String category;
@Schema(description = "Bean名称执行器类名")
private String beanName;
@Schema(description = "执行方法名")
private String methodName;
@Schema(description = "Python脚本路径")
private String scriptPath;
@Schema(description = "参数模板JSON格式")
private String paramSchema;
@Schema(description = "是否自动发布", defaultValue = "false")
private Boolean autoPublish;
@Schema(description = "排序号")
private Integer sortOrder;
@Schema(description = "关联的任务数量")
private Integer taskCount;
@Schema(description = "创建者姓名")
private String creatorName;
@Schema(description = "更新者姓名")
private String updaterName;
}

View File

@@ -0,0 +1,87 @@
package org.xyzh.api.crontab.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import java.util.Date;
/**
* 定时任务VO
* 用于前端展示定时任务信息
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "定时任务VO")
public class CrontabTaskVO extends BaseVO {
private static final long serialVersionUID = 1L;
@Schema(description = "任务ID")
private String taskId;
@Schema(description = "任务名称")
private String taskName;
@Schema(description = "任务分组")
private String taskGroup;
@Schema(description = "任务元数据ID")
private String metaId;
@Schema(description = "元数据名称")
private String metaName;
@Schema(description = "是否使用默认接收人", defaultValue = "false")
private Boolean defaultRecipient;
@Schema(description = "Bean名称")
private String beanName;
@Schema(description = "方法名称")
private String methodName;
@Schema(description = "方法参数")
private String methodParams;
@Schema(description = "Cron表达式")
private String cronExpression;
@Schema(description = "Cron表达式描述中文")
private String cronDescription;
@Schema(description = "任务状态")
private Integer status;
@Schema(description = "任务状态名称")
private String statusName;
@Schema(description = "任务描述")
private String description;
@Schema(description = "是否允许并发执行", defaultValue = "false")
private Boolean concurrent;
@Schema(description = "错过执行策略")
private Integer misfirePolicy;
@Schema(description = "错过执行策略名称")
private String misfirePolicyName;
@Schema(description = "下次执行时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date nextExecuteTime;
@Schema(description = "最后执行时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date lastExecuteTime;
@Schema(description = "最后执行状态0-失败/1-成功")
private Integer lastExecuteStatus;
@Schema(description = "创建者姓名")
private String creatorName;
@Schema(description = "更新者姓名")
private String updaterName;
}

View File

@@ -0,0 +1,9 @@
package org.xyzh.api.message;
/**
* Message服务接口
* 用于消息管理
*/
public interface MessageService {
}

View File

@@ -0,0 +1,63 @@
package org.xyzh.api.message.vo;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import java.util.Date;
import org.xyzh.common.vo.BaseVO;
/**
* 用户消息接收记录VO
* 用于前端展示用户消息信息
*/
@Data
@Schema(description = "用户消息接收记录VO")
public class MessageReceiverVO extends BaseVO {
@Schema(description = "消息ID")
private String messageId;
@Schema(description = "消息标题")
private String title;
@Schema(description = "消息内容")
private String content;
@Schema(description = "消息类型")
private String type;
@Schema(description = "消息类型名称")
private String typeName;
@Schema(description = "用户ID")
private String userId;
@Schema(description = "用户姓名")
private String userName;
@Schema(description = "接收渠道")
private String channel;
@Schema(description = "渠道名称")
private String channelName;
@Schema(description = "消息状态unread-未读/read-已读/handled-已处理/deleted-已删除")
private String status;
@Schema(description = "状态名称")
private String statusName;
@Schema(description = "状态颜色")
private String statusColor;
@Schema(description = "阅读时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date readTime;
@Schema(description = "处理时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date handleTime;
@Schema(description = "服务类型")
private String serviceType;
}

View File

@@ -0,0 +1,118 @@
package org.xyzh.api.system.service;
import org.xyzh.api.system.vo.AclVO;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageRequest;
import org.xyzh.common.dto.sys.TbSysAclDTO;
import org.xyzh.common.dto.sys.TbSysAclPolicyDTO;
/**
* @description 访问控制列表服务接口
* @filename AclService.java
* @author yslg
* @copyright yslg
* @since 2025-12-05
*/
public interface AclService {
// ================= ACL 管理 =================
/**
* @description 插入访问控制列表
* @param aclDTO 访问控制列表DTO
* @return ResultDomain<TbSysAclDTO> 插入结果
* @author yslg
* @since 2025-12-05
*/
ResultDomain<TbSysAclDTO> insertAcl(TbSysAclDTO aclDTO);
/**
* @description 更新访问控制列表
* @param aclDTO 访问控制列表DTO
* @return ResultDomain<TbSysAclDTO> 更新结果
* @author yslg
* @since 2025-12-05
*/
ResultDomain<TbSysAclDTO> updateAcl(TbSysAclDTO aclDTO);
/**
* @description 删除访问控制列表
* @param aclDTO 访问控制列表DTO
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-12-05
*/
ResultDomain<Boolean> deleteAcl(TbSysAclDTO aclDTO);
/**
* @description 根据条件查询访问控制列表分页数据
* @param pageRequest 分页请求
* @return ResultDomain<AclVO> 分页结果
* @author yslg
* @since 2025-12-05
*/
ResultDomain<AclVO> getAclPage(PageRequest<AclVO> pageRequest);
/**
* @description 根据条件查询访问控制列表
* @param filter 过滤条件
* @return ResultDomain<AclVO> 查询结果
* @author yslg
* @since 2025-12-05
*/
ResultDomain<AclVO> getAclList(AclVO filter);
/**
* @description 根据对象ID查询访问控制列表
* @param objectId 对象ID
* @return ResultDomain<AclVO> 查询结果
* @author yslg
* @since 2025-12-05
*/
ResultDomain<AclVO> getAclByObjectId(String objectId);
// ================= ACL Policy 管理 =================
/**
* @description 插入访问控制策略
* @param aclPolicyDTO 访问控制策略DTO
* @return ResultDomain<TbSysAclPolicyDTO> 插入结果
* @author yslg
* @since 2025-12-05
*/
ResultDomain<TbSysAclPolicyDTO> insertAclPolicy(TbSysAclPolicyDTO aclPolicyDTO);
/**
* @description 更新访问控制策略
* @param aclPolicyDTO 访问控制策略DTO
* @return ResultDomain<TbSysAclPolicyDTO> 更新结果
* @author yslg
* @since 2025-12-05
*/
ResultDomain<TbSysAclPolicyDTO> updateAclPolicy(TbSysAclPolicyDTO aclPolicyDTO);
/**
* @description 删除访问控制策略
* @param aclPolicyDTO 访问控制策略DTO
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-12-05
*/
ResultDomain<Boolean> deleteAclPolicy(TbSysAclPolicyDTO aclPolicyDTO);
/**
* @description 根据条件查询访问控制策略分页数据
* @param pageRequest 分页请求
* @return ResultDomain<AclVO> 分页结果
* @author yslg
* @since 2025-12-05
*/
ResultDomain<AclVO> getAclPolicyPage(PageRequest<AclVO> pageRequest);
/**
* @description 根据条件查询访问控制策略列表
* @param filter 过滤条件
* @return ResultDomain<AclVO> 查询结果
* @author yslg
* @since 2025-12-05
*/
ResultDomain<AclVO> getAclPolicyList(AclVO filter);
}

View File

@@ -154,60 +154,6 @@ public interface DeptRoleService {
*/ */
ResultDomain<UserDeptRoleVO> getRoleListByUserId(String userId); ResultDomain<UserDeptRoleVO> getRoleListByUserId(String userId);
// ================= 部门角色关联管理 =================
/**
* @description 插入部门角色关联
* @param deptRoleDTO 部门角色DTO
* @return ResultDomain<TbSysDeptRoleDTO> 插入结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbSysDeptRoleDTO> insertDeptRole(TbSysDeptRoleDTO deptRoleDTO);
/**
* @description 更新部门角色关联
* @param deptRoleDTO 部门角色DTO
* @return ResultDomain<TbSysDeptRoleDTO> 更新结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<TbSysDeptRoleDTO> updateDeptRole(TbSysDeptRoleDTO deptRoleDTO);
/**
* @description 根据ID删除部门角色关联
* @param deptRoleDTO 部门角色DTO
* @return ResultDomain<Boolean> 删除结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<Boolean> deleteDeptRole(TbSysDeptRoleDTO deptRoleDTO);
/**
* @description 根据ID查询部门角色关联
* @param filter 部门角色VO
* @return ResultDomain<UserDeptRoleVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<UserDeptRoleVO> getDeptRole(UserDeptRoleVO filter);
/**
* @description 根据条件查询部门角色关联列表
* @param filter 部门角色VO
* @return ResultDomain<UserDeptRoleVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<UserDeptRoleVO> getDeptRoleList(UserDeptRoleVO filter);
/**
* @description 根据条件查询部门角色关联分页列表
* @param pageRequest 部门角色VO
* @return ResultDomain<UserDeptRoleVO> 查询结果
* @author yslg
* @since 2025-11-05
*/
ResultDomain<UserDeptRoleVO> getDeptRolePage(PageRequest<UserDeptRoleVO> pageRequest);
// ==================== 角色权限关联 ================================ // ==================== 角色权限关联 ================================
/** /**

View File

@@ -4,7 +4,6 @@ import org.xyzh.api.system.vo.PermissionVO;
import org.xyzh.common.core.domain.ResultDomain; import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.dto.sys.TbSysModuleDTO; import org.xyzh.common.dto.sys.TbSysModuleDTO;
import org.xyzh.common.dto.sys.TbSysPermissionDTO; import org.xyzh.common.dto.sys.TbSysPermissionDTO;
import org.xyzh.common.dto.sys.TbSysRolePermissionDTO;
import org.xyzh.common.core.page.PageRequest; import org.xyzh.common.core.page.PageRequest;
/** /**

View File

@@ -24,9 +24,6 @@ public class UserDeptRoleVO extends BaseVO {
@Schema(description = "用户ID") @Schema(description = "用户ID")
private String userId; private String userId;
@Schema(description = "用户名")
private String username;
@Schema(description = "密码") @Schema(description = "密码")
private String password; private String password;
@@ -49,18 +46,12 @@ public class UserDeptRoleVO extends BaseVO {
@Schema(description = "头像") @Schema(description = "头像")
private String avatar; private String avatar;
@Schema(description = "用户名")
private String username;
@Schema(description = "性别") @Schema(description = "性别")
private Integer gender; private Integer gender;
@Schema(description = "")
private String familyName;
@Schema(description = "")
private String givenName;
@Schema(description = "全名")
private String fullName;
@Schema(description = "等级") @Schema(description = "等级")
private Integer level; private Integer level;

View File

@@ -0,0 +1,9 @@
package org.xyzh.api.workcase;
/**
* 工单服务接口
* 用于客服工单管理
*/
public interface WorkcaseService {
}

View File

@@ -0,0 +1,71 @@
package org.xyzh.api.workcase.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.Date;
import java.util.List;
/**
* 会话DTO
* 用于创建和更新会话
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "会话DTO")
public class TbConversationDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Schema(description = "会话ID更新时需要")
private String conversationId;
@Schema(description = "客户ID")
private String customerId;
@Schema(description = "会话类型ai-AI客服/human-人工客服/transfer-转接", defaultValue = "ai")
private String conversationType;
@Schema(description = "渠道wechat-微信/web-网页/app-应用/phone-电话", defaultValue = "wechat")
private String channel;
@Schema(description = "智能体ID或客服人员ID")
private String agentId;
@Schema(description = "座席类型ai-AI/human-人工", defaultValue = "ai")
private String agentType;
@Schema(description = "会话开始时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date sessionStartTime;
@Schema(description = "会话结束时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date sessionEndTime;
@Schema(description = "会话时长(秒)")
private Integer durationSeconds;
@Schema(description = "消息数量", defaultValue = "0")
private Integer messageCount;
@Schema(description = "会话状态active-进行中/closed-已结束/transferred-已转接/timeout-超时", defaultValue = "active")
private String conversationStatus;
@Schema(description = "满意度评分1-5星")
private Integer satisfactionRating;
@Schema(description = "满意度反馈")
private String satisfactionFeedback;
@Schema(description = "会话摘要AI生成")
private String summary;
@Schema(description = "会话标签")
private List<String> tags;
@Schema(description = "会话元数据")
private JsonNode metadata;
}

View File

@@ -0,0 +1,91 @@
package org.xyzh.api.workcase.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* 客户信息DTO
* 用于创建和更新客户信息
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "客户信息DTO")
public class TbCustomerDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Schema(description = "客户ID更新时需要")
private String customerId;
@Schema(description = "客户编号")
private String customerNo;
@Schema(description = "客户姓名")
private String customerName;
@Schema(description = "客户类型individual-个人/enterprise-企业", defaultValue = "individual")
private String customerType;
@Schema(description = "公司名称")
private String companyName;
@Schema(description = "电话")
private String phone;
@Schema(description = "邮箱")
private String email;
@Schema(description = "微信OpenID")
private String wechatOpenid;
@Schema(description = "微信UnionID")
private String wechatUnionid;
@Schema(description = "头像URL")
private String avatar;
@Schema(description = "性别0-未知/1-男/2-女", defaultValue = "0")
private Integer gender;
@Schema(description = "地址")
private String address;
@Schema(description = "客户等级vip/important/normal/potential", defaultValue = "normal")
private String customerLevel;
@Schema(description = "客户来源wechat-微信/web-网站/phone-电话/referral-推荐")
private String customerSource;
@Schema(description = "客户标签数组")
private List<String> tags;
@Schema(description = "备注")
private String notes;
@Schema(description = "CRM系统客户ID")
private String crmCustomerId;
@Schema(description = "最后联系时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date lastContactTime;
@Schema(description = "咨询总次数", defaultValue = "0")
private Integer totalConsultations;
@Schema(description = "订单总数", defaultValue = "0")
private Integer totalOrders;
@Schema(description = "总消费金额", defaultValue = "0")
private BigDecimal totalAmount;
@Schema(description = "满意度评分1-5")
private BigDecimal satisfactionScore;
@Schema(description = "状态active-活跃/inactive-非活跃/blacklist-黑名单", defaultValue = "active")
private String status;
}

View File

@@ -0,0 +1,103 @@
package org.xyzh.api.workcase.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.Date;
import java.util.List;
/**
* 工单DTO
* 用于创建和更新工单
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "工单DTO")
public class TbTicketDTO extends BaseDTO {
private static final long serialVersionUID = 1L;
@Schema(description = "工单ID更新时需要")
private String ticketId;
@Schema(description = "工单编号")
private String ticketNo;
@Schema(description = "客户ID")
private String customerId;
@Schema(description = "关联会话ID")
private String conversationId;
@Schema(description = "工单类型consultation-咨询/complaint-投诉/suggestion-建议/repair-维修/installation-安装/other-其他")
private String ticketType;
@Schema(description = "工单分类")
private String ticketCategory;
@Schema(description = "优先级urgent-紧急/high-高/normal-普通/low-低", defaultValue = "normal")
private String priority;
@Schema(description = "工单标题")
private String title;
@Schema(description = "问题描述")
private String description;
@Schema(description = "附件ID数组")
private List<String> attachments;
@Schema(description = "工单来源ai-AI生成/manual-人工创建/system-系统自动", defaultValue = "ai")
private String ticketSource;
@Schema(description = "分配给(处理人)")
private String assignedTo;
@Schema(description = "分配部门")
private String assignedDept;
@Schema(description = "工单状态pending-待处理/processing-处理中/resolved-已解决/closed-已关闭/cancelled-已取消", defaultValue = "pending")
private String ticketStatus;
@Schema(description = "解决方案")
private String resolution;
@Schema(description = "解决时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date resolutionTime;
@Schema(description = "关闭时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date closeTime;
@Schema(description = "首次响应时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date responseTime;
@Schema(description = "SLA截止时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date slaDeadline;
@Schema(description = "是否逾期", defaultValue = "false")
private Boolean isOverdue;
@Schema(description = "客户评分1-5星")
private Integer customerRating;
@Schema(description = "客户反馈")
private String customerFeedback;
@Schema(description = "CRM系统工单ID")
private String crmTicketId;
@Schema(description = "同步状态pending-待同步/synced-已同步/failed-失败", defaultValue = "pending")
private String syncStatus;
@Schema(description = "工单标签")
private List<String> tags;
@Schema(description = "工单元数据")
private JsonNode metadata;
}

View File

@@ -0,0 +1,108 @@
package org.xyzh.api.workcase.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.Date;
import java.util.List;
/**
* 会话VO
* 用于前端展示会话信息
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "会话VO")
public class ConversationVO extends BaseVO {
private static final long serialVersionUID = 1L;
@Schema(description = "会话ID")
private String conversationId;
@Schema(description = "客户ID")
private String customerId;
@Schema(description = "客户姓名")
private String customerName;
@Schema(description = "客户头像")
private String customerAvatar;
@Schema(description = "会话类型")
private String conversationType;
@Schema(description = "会话类型名称")
private String conversationTypeName;
@Schema(description = "渠道")
private String channel;
@Schema(description = "渠道名称")
private String channelName;
@Schema(description = "智能体ID或客服人员ID")
private String agentId;
@Schema(description = "座席名称")
private String agentName;
@Schema(description = "座席类型")
private String agentType;
@Schema(description = "座席类型名称")
private String agentTypeName;
@Schema(description = "会话开始时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date sessionStartTime;
@Schema(description = "会话结束时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date sessionEndTime;
@Schema(description = "会话时长(秒)")
private Integer durationSeconds;
@Schema(description = "会话时长格式化显示")
private String durationFormatted;
@Schema(description = "消息数量")
private Integer messageCount;
@Schema(description = "会话状态")
private String conversationStatus;
@Schema(description = "会话状态名称")
private String conversationStatusName;
@Schema(description = "会话状态颜色")
private String statusColor;
@Schema(description = "满意度评分1-5星")
private Integer satisfactionRating;
@Schema(description = "满意度反馈")
private String satisfactionFeedback;
@Schema(description = "会话摘要")
private String summary;
@Schema(description = "会话标签")
private List<String> tags;
@Schema(description = "会话元数据")
private JsonNode metadata;
@Schema(description = "最后一条消息内容")
private String lastMessageContent;
@Schema(description = "最后一条消息时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date lastMessageTime;
@Schema(description = "创建者姓名")
private String creatorName;
}

View File

@@ -0,0 +1,112 @@
package org.xyzh.api.workcase.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* 客户信息VO
* 用于前端展示客户信息
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "客户信息VO")
public class CustomerVO extends BaseVO {
private static final long serialVersionUID = 1L;
@Schema(description = "客户ID")
private String customerId;
@Schema(description = "客户编号")
private String customerNo;
@Schema(description = "客户姓名")
private String customerName;
@Schema(description = "客户类型")
private String customerType;
@Schema(description = "客户类型名称")
private String customerTypeName;
@Schema(description = "公司名称")
private String companyName;
@Schema(description = "电话")
private String phone;
@Schema(description = "邮箱")
private String email;
@Schema(description = "微信OpenID")
private String wechatOpenid;
@Schema(description = "头像URL")
private String avatar;
@Schema(description = "性别")
private Integer gender;
@Schema(description = "性别名称")
private String genderName;
@Schema(description = "地址")
private String address;
@Schema(description = "客户等级")
private String customerLevel;
@Schema(description = "客户等级名称")
private String customerLevelName;
@Schema(description = "客户来源")
private String customerSource;
@Schema(description = "客户来源名称")
private String customerSourceName;
@Schema(description = "客户标签")
private List<String> tags;
@Schema(description = "备注")
private String notes;
@Schema(description = "CRM系统客户ID")
private String crmCustomerId;
@Schema(description = "最后联系时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date lastContactTime;
@Schema(description = "咨询总次数")
private Integer totalConsultations;
@Schema(description = "订单总数")
private Integer totalOrders;
@Schema(description = "总消费金额")
private BigDecimal totalAmount;
@Schema(description = "满意度评分")
private BigDecimal satisfactionScore;
@Schema(description = "状态")
private String status;
@Schema(description = "状态名称")
private String statusName;
@Schema(description = "状态颜色")
private String statusColor;
@Schema(description = "创建者姓名")
private String creatorName;
@Schema(description = "更新者姓名")
private String updaterName;
}

View File

@@ -0,0 +1,59 @@
package org.xyzh.api.workcase.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import java.util.Date;
/**
* 工单列表VO
* 用于前端列表展示(简化版)
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "工单列表VO")
public class TicketListVO extends BaseVO {
private static final long serialVersionUID = 1L;
@Schema(description = "工单ID")
private String ticketId;
@Schema(description = "工单编号")
private String ticketNo;
@Schema(description = "客户姓名")
private String customerName;
@Schema(description = "工单标题")
private String title;
@Schema(description = "工单类型名称")
private String ticketTypeName;
@Schema(description = "优先级")
private String priority;
@Schema(description = "优先级名称")
private String priorityName;
@Schema(description = "工单状态")
private String ticketStatus;
@Schema(description = "工单状态名称")
private String ticketStatusName;
@Schema(description = "处理人姓名")
private String assignedToName;
@Schema(description = "SLA截止时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date slaDeadline;
@Schema(description = "是否逾期", defaultValue = "false")
private Boolean isOverdue;
@Schema(description = "创建者姓名")
private String creatorName;
}

View File

@@ -0,0 +1,151 @@
package org.xyzh.api.workcase.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.xyzh.common.vo.BaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import com.alibaba.fastjson2.annotation.JSONField;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.Date;
import java.util.List;
/**
* 工单VO
* 用于前端展示工单信息
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "工单VO")
public class TicketVO extends BaseVO {
private static final long serialVersionUID = 1L;
@Schema(description = "工单ID")
private String ticketId;
@Schema(description = "工单编号")
private String ticketNo;
@Schema(description = "客户ID")
private String customerId;
@Schema(description = "客户姓名")
private String customerName;
@Schema(description = "客户电话")
private String customerPhone;
@Schema(description = "关联会话ID")
private String conversationId;
@Schema(description = "工单类型")
private String ticketType;
@Schema(description = "工单类型名称")
private String ticketTypeName;
@Schema(description = "工单分类")
private String ticketCategory;
@Schema(description = "优先级")
private String priority;
@Schema(description = "优先级名称")
private String priorityName;
@Schema(description = "优先级颜色")
private String priorityColor;
@Schema(description = "工单标题")
private String title;
@Schema(description = "问题描述")
private String description;
@Schema(description = "附件ID数组")
private List<String> attachments;
@Schema(description = "附件数量")
private Integer attachmentCount;
@Schema(description = "工单来源")
private String ticketSource;
@Schema(description = "工单来源名称")
private String ticketSourceName;
@Schema(description = "分配给处理人ID")
private String assignedTo;
@Schema(description = "处理人姓名")
private String assignedToName;
@Schema(description = "分配部门")
private String assignedDept;
@Schema(description = "分配部门名称")
private String assignedDeptName;
@Schema(description = "工单状态")
private String ticketStatus;
@Schema(description = "工单状态名称")
private String ticketStatusName;
@Schema(description = "工单状态颜色")
private String statusColor;
@Schema(description = "解决方案")
private String resolution;
@Schema(description = "解决时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date resolutionTime;
@Schema(description = "关闭时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date closeTime;
@Schema(description = "首次响应时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date responseTime;
@Schema(description = "SLA截止时间", format = "date-time")
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date slaDeadline;
@Schema(description = "是否逾期", defaultValue = "false")
private Boolean isOverdue;
@Schema(description = "距离SLA截止的剩余时间分钟")
private Integer slaRemainingMinutes;
@Schema(description = "客户评分1-5星")
private Integer customerRating;
@Schema(description = "客户反馈")
private String customerFeedback;
@Schema(description = "CRM系统工单ID")
private String crmTicketId;
@Schema(description = "同步状态")
private String syncStatus;
@Schema(description = "同步状态名称")
private String syncStatusName;
@Schema(description = "工单标签")
private List<String> tags;
@Schema(description = "工单元数据")
private JsonNode metadata;
@Schema(description = "处理记录数量")
private Integer logCount;
@Schema(description = "创建者姓名")
private String creatorName;
@Schema(description = "更新者姓名")
private String updaterName;
}

View File

@@ -1,36 +1,84 @@
# ================== 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:
- /** # 认证服务的所有接口都放行
# ================== Spring ==================
spring:
application:
name: auth-service
# ================== 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

View 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>

View File

@@ -0,0 +1,77 @@
# ================== 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
# ================== Spring ==================
spring:
application:
name: bidding-service
# ================== 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

View 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>

View File

@@ -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;

View File

@@ -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 或未配置时使用

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -30,5 +30,19 @@
<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>
</dependencies> </dependencies>
</project> </project>

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,68 @@
# ================== Server ==================
server:
port: 8189
servlet:
context-path: /urban-lifeline/crontab
# ================== Auth ====================
urban-lifeline:
auth:
enabled: false # 定时任务服务通常不需要认证
# ================== Spring ==================
spring:
application:
name: crontab-service
# ================== 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

View 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>

View File

@@ -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 -->

View File

@@ -0,0 +1,84 @@
# ================== 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
# ================== Spring ==================
spring:
application:
name: file-service
# ================== 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

View 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>

View File

@@ -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 日志 --> <groupId>org.springframework.boot</groupId>
<dependency> <artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId> </exclusion>
<artifactId>spring-boot-starter-log4j2</artifactId> </exclusions>
</dependency> </dependency>
<!-- WebFluxGateway依赖 --> <!-- WebFluxGateway依赖 -->

View File

@@ -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 的 SecurityConfigGateway 使用 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);

View File

@@ -1,14 +1,16 @@
spring: spring:
cloud: cloud:
gateway: gateway:
routes: server:
# 开发环境可以添加更详细的路由配置或测试路由 webflux:
routes:
# Nacos 管理界面路由(开发专用) # 开发环境可以添加更详细的路由配置或测试路由
- id: nacos-console
uri: http://${NACOS_SERVER_ADDR:localhost:8848} # Nacos 管理界面路由(开发专用)
predicates: - id: nacos-console
- Path=/nacos/** uri: http://${NACOS_SERVER_ADDR:localhost:8848}
predicates:
- Path=/nacos/**
# 开发环境日志 # 开发环境日志
logging: logging:

View File

@@ -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,71 +22,127 @@ spring:
namespace: dev namespace: dev
group: DEFAULT_GROUP group: DEFAULT_GROUP
# Gateway 路由配置 # Gateway 路由配置(使用新的 webflux 配置路径)
gateway: gateway:
# 服务发现路由(自动路由) server:
discovery: webflux:
locator: # 服务发现路由(自动路由)
enabled: false # 关闭自动路由,使用手动配置 discovery:
locator:
# 手动配置路由 enabled: false # 关闭自动路由,使用手动配置
routes:
# ==================== 认证服务路由 ==================== # 手动配置路由
- id: auth-service routes:
uri: lb://auth-service # ==================== 认证服务路由 ====================
predicates: - id: auth-service
- Path=/auth/** uri: lb://auth-service
filters: predicates:
- StripPrefix=1 - Path=/auth/**
- name: RequestRateLimiter filters:
args: - RewritePath=/auth/(?<segment>.*), /urban-lifeline/auth/$\{segment}
redis-rate-limiter.replenishRate: 100 - name: RequestRateLimiter
redis-rate-limiter.burstCapacity: 200 args:
redis-rate-limiter.replenishRate: 100
# ==================== 系统服务路由 ==================== redis-rate-limiter.burstCapacity: 200
- id: system-service
uri: lb://system-service # ==================== 系统服务路由 ====================
predicates: - id: system-service
- Path=/system/** uri: lb://system-service
filters: predicates:
- StripPrefix=1 - Path=/system/**
filters:
# ==================== 日志服务路由 ==================== - RewritePath=/system/(?<segment>.*), /urban-lifeline/system/$\{segment}
- id: log-service
uri: lb://log-service # ==================== 日志服务路由 ====================
predicates: - id: log-service
- Path=/log/** uri: lb://log-service
filters: predicates:
- StripPrefix=1 - Path=/log/**
filters:
# ==================== 文件服务路由 ==================== - RewritePath=/log/(?<segment>.*), /urban-lifeline/log/$\{segment}
- id: file-service
uri: lb://file-service # ==================== 文件服务路由 ====================
predicates: - id: file-service
- Path=/file/** uri: lb://file-service
filters: predicates:
- StripPrefix=1 - Path=/file/**
filters:
# 全局跨域配置 - RewritePath=/file/(?<segment>.*), /urban-lifeline/file/$\{segment}
globalcors:
cors-configurations: # ==================== 消息服务路由 ====================
'[/**]': - id: message-service
allowedOriginPatterns: "*" uri: lb://message-service
allowedMethods: predicates:
- GET - Path=/message/**
- POST filters:
- PUT - RewritePath=/message/(?<segment>.*), /urban-lifeline/message/$\{segment}
- DELETE
- OPTIONS # ==================== 招投标服务路由 ====================
allowedHeaders: "*" - id: bidding-service
allowCredentials: true uri: lb://bidding-service
maxAge: 3600 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:
cors-configurations:
'[/**]':
allowedOriginPatterns: "*"
allowedMethods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowedHeaders: "*"
allowCredentials: true
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,6 +155,7 @@ 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
@@ -124,10 +185,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"

View 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>

View File

@@ -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 -->

View File

@@ -1,36 +1,85 @@
# ================== 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
# ================== Spring ==================
spring:
application:
name: log-service
# ================== 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

View 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>

View File

@@ -0,0 +1,77 @@
# ================== 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
# ================== Spring ==================
spring:
application:
name: message-service
# ================== 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

View 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>

View File

@@ -0,0 +1,77 @@
# ================== 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
# ================== Spring ==================
spring:
application:
name: platform-service
# ================== 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

View 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>

View File

@@ -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>
@@ -386,22 +393,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>
@@ -409,15 +400,59 @@
</exclusion> </exclusion>
</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 +460,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>

View File

@@ -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>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<!-- Spring Boot Web --> <!-- Spring Cloud Alibaba Nacos Discovery父 pom 未包含,用于服务发现) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Dubbo Spring Boot Starter -->
<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>

View File

@@ -3,6 +3,7 @@ package org.xyzh.system.controller;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -36,6 +37,7 @@ public class DeptRoleController {
* @since 2025-11-10 * @since 2025-11-10
*/ */
@PostMapping("/dept") @PostMapping("/dept")
@PreAuthorize("@ss.hasPermi('system:dept:create')")
public ResultDomain<TbSysDeptDTO> createDept(@RequestBody TbSysDeptDTO deptDTO) { public ResultDomain<TbSysDeptDTO> createDept(@RequestBody TbSysDeptDTO deptDTO) {
return deptRoleService.insertDept(deptDTO); return deptRoleService.insertDept(deptDTO);
} }
@@ -49,6 +51,7 @@ public class DeptRoleController {
* @since 2025-11-10 * @since 2025-11-10
*/ */
@PutMapping("/dept") @PutMapping("/dept")
@PreAuthorize("@ss.hasPermi('system:dept:edit')")
public ResultDomain<TbSysDeptDTO> updateDept(@RequestBody TbSysDeptDTO deptDTO) { public ResultDomain<TbSysDeptDTO> updateDept(@RequestBody TbSysDeptDTO deptDTO) {
return deptRoleService.updateDept(deptDTO); return deptRoleService.updateDept(deptDTO);
} }
@@ -61,6 +64,7 @@ public class DeptRoleController {
* @since 2025-11-10 * @since 2025-11-10
*/ */
@DeleteMapping("/dept") @DeleteMapping("/dept")
@PreAuthorize("@ss.hasPermi('system:dept:delete')")
public ResultDomain<Boolean> deleteDept(@RequestBody TbSysDeptDTO deptDTO) { public ResultDomain<Boolean> deleteDept(@RequestBody TbSysDeptDTO deptDTO) {
return deptRoleService.deleteDept(deptDTO); return deptRoleService.deleteDept(deptDTO);
} }
@@ -73,6 +77,7 @@ public class DeptRoleController {
* @since 2025-11-10 * @since 2025-11-10
*/ */
@GetMapping("/dept/page") @GetMapping("/dept/page")
@PreAuthorize("@ss.hasPermi('system:dept:view')")
public ResultDomain<UserDeptRoleVO> getDeptPage(@RequestBody PageRequest<UserDeptRoleVO> pageRequest) { public ResultDomain<UserDeptRoleVO> getDeptPage(@RequestBody PageRequest<UserDeptRoleVO> pageRequest) {
return deptRoleService.getDeptPage(pageRequest); return deptRoleService.getDeptPage(pageRequest);
} }
@@ -85,6 +90,7 @@ public class DeptRoleController {
* @since 2025-11-10 * @since 2025-11-10
*/ */
@GetMapping("/dept/list") @GetMapping("/dept/list")
@PreAuthorize("@ss.hasPermi('system:dept:view')")
public ResultDomain<UserDeptRoleVO> getDeptList(@RequestBody UserDeptRoleVO filter) { public ResultDomain<UserDeptRoleVO> getDeptList(@RequestBody UserDeptRoleVO filter) {
return deptRoleService.getDeptList(filter); return deptRoleService.getDeptList(filter);
} }
@@ -98,6 +104,7 @@ public class DeptRoleController {
* @since 2025-11-10 * @since 2025-11-10
*/ */
@PostMapping("/role") @PostMapping("/role")
@PreAuthorize("@ss.hasPermi('system:role:create')")
public ResultDomain<TbSysRoleDTO> createRole(@RequestBody TbSysRoleDTO roleDTO) { public ResultDomain<TbSysRoleDTO> createRole(@RequestBody TbSysRoleDTO roleDTO) {
return deptRoleService.insertRole(roleDTO); return deptRoleService.insertRole(roleDTO);
} }
@@ -110,6 +117,7 @@ public class DeptRoleController {
* @since 2025-11-10 * @since 2025-11-10
*/ */
@PutMapping("/role") @PutMapping("/role")
@PreAuthorize("@ss.hasPermi('system:role:edit')")
public ResultDomain<TbSysRoleDTO> updateRole(@RequestBody TbSysRoleDTO roleDTO) { public ResultDomain<TbSysRoleDTO> updateRole(@RequestBody TbSysRoleDTO roleDTO) {
return deptRoleService.updateRole(roleDTO); return deptRoleService.updateRole(roleDTO);
} }
@@ -122,6 +130,7 @@ public class DeptRoleController {
* @since 2025-11-10 * @since 2025-11-10
*/ */
@DeleteMapping("/role") @DeleteMapping("/role")
@PreAuthorize("@ss.hasPermi('system:role:delete')")
public ResultDomain<Boolean> deleteRole(@RequestBody TbSysRoleDTO roleDTO) { public ResultDomain<Boolean> deleteRole(@RequestBody TbSysRoleDTO roleDTO) {
return deptRoleService.deleteRole(roleDTO); return deptRoleService.deleteRole(roleDTO);
} }
@@ -134,6 +143,7 @@ public class DeptRoleController {
* @since 2025-11-10 * @since 2025-11-10
*/ */
@GetMapping("/role/page") @GetMapping("/role/page")
@PreAuthorize("@ss.hasPermi('system:role:view')")
public ResultDomain<UserDeptRoleVO> getRolePage(@RequestBody PageRequest<UserDeptRoleVO> pageRequest) { public ResultDomain<UserDeptRoleVO> getRolePage(@RequestBody PageRequest<UserDeptRoleVO> pageRequest) {
return deptRoleService.getRolePage(pageRequest); return deptRoleService.getRolePage(pageRequest);
} }
@@ -146,35 +156,11 @@ public class DeptRoleController {
* @since 2025-11-10 * @since 2025-11-10
*/ */
@GetMapping("/role/list") @GetMapping("/role/list")
@PreAuthorize("@ss.hasPermi('system:role:view')")
public ResultDomain<UserDeptRoleVO> getRoleList(@RequestBody UserDeptRoleVO filter) { public ResultDomain<UserDeptRoleVO> getRoleList(@RequestBody UserDeptRoleVO filter) {
return deptRoleService.getRoleList(filter); return deptRoleService.getRoleList(filter);
} }
// ================= 部门角色信息相关接口 ==================
/**
* @description 根据条件查询部门角色关联分页列表
* @param PageRequest<UserDeptRoleVO> pageRequest 分页请求
* @return ResultDomain<UserDeptRoleVO> 分页列表
* @author yslg
* @since 2025-11-10
*/
@GetMapping("/deptRole/page")
public ResultDomain<UserDeptRoleVO> getDeptRolePage(@RequestBody PageRequest<UserDeptRoleVO> pageRequest) {
return deptRoleService.getDeptRolePage(pageRequest);
}
/**
* @description 根据条件查询部门角色关联列表
* @param filter 部门角色VO
* @return ResultDomain<UserDeptRoleVO> 部门角色关联列表
* @author yslg
* @since 2025-11-10
*/
@GetMapping("/deptRole/list")
public ResultDomain<UserDeptRoleVO> getDeptRoleList(@RequestBody UserDeptRoleVO filter) {
return deptRoleService.getDeptRoleList(filter);
}
// ================== 角色权限相关接口 ================== // ================== 角色权限相关接口 ==================
/** /**
* @description 给一个角色设置权限 * @description 给一个角色设置权限
@@ -184,6 +170,7 @@ public class DeptRoleController {
* @since 2025-11-11 * @since 2025-11-11
*/ */
@PostMapping("/rolePermission/bind") @PostMapping("/rolePermission/bind")
@PreAuthorize("@ss.hasPermi('system:permission:manage')")
public ResultDomain<PermissionVO> getRolePermission(@RequestBody PermissionVO permissionVO) { public ResultDomain<PermissionVO> getRolePermission(@RequestBody PermissionVO permissionVO) {
return deptRoleService.setRolePermission(permissionVO); return deptRoleService.setRolePermission(permissionVO);
} }
@@ -196,6 +183,7 @@ public class DeptRoleController {
* @since 2025-11-11 * @since 2025-11-11
*/ */
@PostMapping("/rolePermission/list") @PostMapping("/rolePermission/list")
@PreAuthorize("@ss.hasPermi('system:permission:view')")
public ResultDomain<PermissionVO> getRolePermissionList(@RequestBody PermissionVO permissionVO) { public ResultDomain<PermissionVO> getRolePermissionList(@RequestBody PermissionVO permissionVO) {
return deptRoleService.getRolePermissionList(permissionVO); return deptRoleService.getRolePermissionList(permissionVO);
} }

View File

@@ -3,6 +3,7 @@ package org.xyzh.system.controller;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
@@ -35,6 +36,7 @@ public class PermissionController {
* @since 2025-11-10 * @since 2025-11-10
*/ */
@PostMapping("/module") @PostMapping("/module")
@PreAuthorize("@ss.hasPermi('system:permission:manage')")
public ResultDomain<TbSysModuleDTO> createModule(@RequestBody TbSysModuleDTO moduleDTO) { public ResultDomain<TbSysModuleDTO> createModule(@RequestBody TbSysModuleDTO moduleDTO) {
return modulePermissionService.insertModule(moduleDTO); return modulePermissionService.insertModule(moduleDTO);
} }
@@ -47,6 +49,7 @@ public class PermissionController {
* @since 2025-11-10 * @since 2025-11-10
*/ */
@PutMapping("/moudule") @PutMapping("/moudule")
@PreAuthorize("@ss.hasPermi('system:permission:manage')")
public ResultDomain<TbSysModuleDTO> updateModule(@RequestBody TbSysModuleDTO moduleDTO) { public ResultDomain<TbSysModuleDTO> updateModule(@RequestBody TbSysModuleDTO moduleDTO) {
return modulePermissionService.updateModule(moduleDTO); return modulePermissionService.updateModule(moduleDTO);
} }
@@ -59,6 +62,7 @@ public class PermissionController {
* @since 2025-11-10 * @since 2025-11-10
*/ */
@DeleteMapping("/module") @DeleteMapping("/module")
@PreAuthorize("@ss.hasPermi('system:permission:manage')")
public ResultDomain<Boolean> deleteModule(@RequestBody TbSysModuleDTO moduleDTO) { public ResultDomain<Boolean> deleteModule(@RequestBody TbSysModuleDTO moduleDTO) {
return modulePermissionService.deleteModule(moduleDTO); return modulePermissionService.deleteModule(moduleDTO);
} }
@@ -71,6 +75,7 @@ public class PermissionController {
* @since 2025-11-10 * @since 2025-11-10
*/ */
@PostMapping("/module/page") @PostMapping("/module/page")
@PreAuthorize("@ss.hasPermi('system:permission:view')")
public ResultDomain<PermissionVO> getModulePage(@RequestBody PageRequest<PermissionVO> pageRequest) { public ResultDomain<PermissionVO> getModulePage(@RequestBody PageRequest<PermissionVO> pageRequest) {
return modulePermissionService.getModulePage(pageRequest); return modulePermissionService.getModulePage(pageRequest);
} }
@@ -83,6 +88,7 @@ public class PermissionController {
* @since 2025-11-10 * @since 2025-11-10
*/ */
@PostMapping("/module/list") @PostMapping("/module/list")
@PreAuthorize("@ss.hasPermi('system:permission:view')")
public ResultDomain<PermissionVO> getModuleList(@RequestBody PermissionVO filter) { public ResultDomain<PermissionVO> getModuleList(@RequestBody PermissionVO filter) {
return modulePermissionService.getModuleList(filter); return modulePermissionService.getModuleList(filter);
} }
@@ -96,6 +102,7 @@ public class PermissionController {
* @since 2025-11-10 * @since 2025-11-10
*/ */
@PostMapping @PostMapping
@PreAuthorize("@ss.hasPermi('system:permission:manage')")
public ResultDomain<TbSysPermissionDTO> createPermission(@RequestBody TbSysPermissionDTO permissionDTO) { public ResultDomain<TbSysPermissionDTO> createPermission(@RequestBody TbSysPermissionDTO permissionDTO) {
return modulePermissionService.insertPermission(permissionDTO); return modulePermissionService.insertPermission(permissionDTO);
} }
@@ -108,6 +115,7 @@ public class PermissionController {
* @since 2025-11-10 * @since 2025-11-10
*/ */
@PutMapping @PutMapping
@PreAuthorize("@ss.hasPermi('system:permission:manage')")
public ResultDomain<TbSysPermissionDTO> updatePermission(@RequestBody TbSysPermissionDTO permissionDTO) { public ResultDomain<TbSysPermissionDTO> updatePermission(@RequestBody TbSysPermissionDTO permissionDTO) {
return modulePermissionService.updatePermission(permissionDTO); return modulePermissionService.updatePermission(permissionDTO);
} }
@@ -120,6 +128,7 @@ public class PermissionController {
* @since 2025-11-10 * @since 2025-11-10
*/ */
@DeleteMapping @DeleteMapping
@PreAuthorize("@ss.hasPermi('system:permission:manage')")
public ResultDomain<Boolean> deletePermission(@RequestBody TbSysPermissionDTO permissionDTO) { public ResultDomain<Boolean> deletePermission(@RequestBody TbSysPermissionDTO permissionDTO) {
return modulePermissionService.deletePermission(permissionDTO); return modulePermissionService.deletePermission(permissionDTO);
} }
@@ -132,6 +141,7 @@ public class PermissionController {
* @since 2025-11-10 * @since 2025-11-10
*/ */
@PostMapping("/page") @PostMapping("/page")
@PreAuthorize("@ss.hasPermi('system:permission:view')")
public ResultDomain<PermissionVO> getModulePermissionPage(@RequestBody PageRequest<PermissionVO> pageRequest) { public ResultDomain<PermissionVO> getModulePermissionPage(@RequestBody PageRequest<PermissionVO> pageRequest) {
return modulePermissionService.getModulePermissionPage(pageRequest); return modulePermissionService.getModulePermissionPage(pageRequest);
} }
@@ -144,6 +154,7 @@ public class PermissionController {
* @since 2025-11-10 * @since 2025-11-10
*/ */
@PostMapping("/list") @PostMapping("/list")
@PreAuthorize("@ss.hasPermi('system:permission:view')")
public ResultDomain<PermissionVO> getModulePermissionList(@RequestBody PermissionVO filter) { public ResultDomain<PermissionVO> getModulePermissionList(@RequestBody PermissionVO filter) {
return modulePermissionService.getModulePermissionList(filter); return modulePermissionService.getModulePermissionList(filter);
} }

View File

@@ -3,6 +3,7 @@ package org.xyzh.system.controller;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
@@ -28,26 +29,31 @@ public class SysConfigController {
// ================= 系统配置相关接口 ================= // ================= 系统配置相关接口 =================
@PostMapping @PostMapping
@PreAuthorize("@ss.hasPermi('config:config:edit')")
public ResultDomain<TbSysConfigDTO> createConfig(@RequestBody TbSysConfigDTO configDTO) { public ResultDomain<TbSysConfigDTO> createConfig(@RequestBody TbSysConfigDTO configDTO) {
return sysConfigService.insertConfig(configDTO); return sysConfigService.insertConfig(configDTO);
} }
@PutMapping @PutMapping
@PreAuthorize("@ss.hasPermi('config:config:edit')")
public ResultDomain<TbSysConfigDTO> updateConfig(@RequestBody TbSysConfigDTO configDTO) { public ResultDomain<TbSysConfigDTO> updateConfig(@RequestBody TbSysConfigDTO configDTO) {
return sysConfigService.updateConfig(configDTO); return sysConfigService.updateConfig(configDTO);
} }
@DeleteMapping @DeleteMapping
@PreAuthorize("@ss.hasPermi('config:config:edit')")
public ResultDomain<Boolean> deleteConfig(@RequestBody TbSysConfigDTO configDTO) { public ResultDomain<Boolean> deleteConfig(@RequestBody TbSysConfigDTO configDTO) {
return sysConfigService.deleteConfig(configDTO); return sysConfigService.deleteConfig(configDTO);
} }
@PostMapping("/page") @PostMapping("/page")
@PreAuthorize("@ss.hasPermi('config:config:view')")
public ResultDomain<SysConfigVO> getConfigPage(@RequestBody PageRequest<SysConfigVO> pageRequest) { public ResultDomain<SysConfigVO> getConfigPage(@RequestBody PageRequest<SysConfigVO> pageRequest) {
return sysConfigService.getConfigPage(pageRequest); return sysConfigService.getConfigPage(pageRequest);
} }
@PostMapping("/list") @PostMapping("/list")
@PreAuthorize("@ss.hasPermi('config:config:view')")
public ResultDomain<SysConfigVO> getConfigList(@RequestBody SysConfigVO filter) { public ResultDomain<SysConfigVO> getConfigList(@RequestBody SysConfigVO filter) {
return sysConfigService.getConfigList(filter); return sysConfigService.getConfigList(filter);
} }

View File

@@ -31,31 +31,31 @@ public class UserController {
// ================= 用户相关接口 ================= // ================= 用户相关接口 =================
@PostMapping @PostMapping
@PreAuthorize("hasAuthority('system:user:create')") @PreAuthorize("@ss.hasPermi('system:user:create')")
public ResultDomain<TbSysUserDTO> createUser(@RequestBody SysUserVO userVO) { public ResultDomain<TbSysUserDTO> createUser(@RequestBody SysUserVO userVO) {
return sysUserService.insertUser(userVO); return sysUserService.insertUser(userVO);
} }
@PutMapping @PutMapping
@PreAuthorize("hasAuthority('system:user:update')") @PreAuthorize("@ss.hasPermi('system:user:edit')")
public ResultDomain<TbSysUserDTO> updateUser(@RequestBody SysUserVO userVO) { public ResultDomain<TbSysUserDTO> updateUser(@RequestBody SysUserVO userVO) {
return sysUserService.updateUser(userVO); return sysUserService.updateUser(userVO);
} }
@DeleteMapping @DeleteMapping
@PreAuthorize("hasAuthority('system:user:delete')") @PreAuthorize("@ss.hasPermi('system:user:delete')")
public ResultDomain<Boolean> deleteUser(@RequestBody TbSysUserDTO userDTO) { public ResultDomain<Boolean> deleteUser(@RequestBody TbSysUserDTO userDTO) {
return sysUserService.deleteUser(userDTO); return sysUserService.deleteUser(userDTO);
} }
@PostMapping("/page") @PostMapping("/page")
@PreAuthorize("hasAuthority('system:user:query')") @PreAuthorize("@ss.hasPermi('system:user:view')")
public ResultDomain<SysUserVO> getUserPage(@RequestBody PageRequest<SysUserVO> pageRequest) { public ResultDomain<SysUserVO> getUserPage(@RequestBody PageRequest<SysUserVO> pageRequest) {
return sysUserService.getUserPage(pageRequest); return sysUserService.getUserPage(pageRequest);
} }
@PostMapping("/list") @PostMapping("/list")
@PreAuthorize("hasAuthority('system:user:query')") @PreAuthorize("@ss.hasPermi('system:user:view')")
public ResultDomain<SysUserVO> getUserList(@RequestBody SysUserVO filter) { public ResultDomain<SysUserVO> getUserList(@RequestBody SysUserVO filter) {
return sysUserService.getUserList(filter); return sysUserService.getUserList(filter);
} }
@@ -63,13 +63,13 @@ public class UserController {
// ================= 用户信息相关接口 ================== // ================= 用户信息相关接口 ==================
@PutMapping("/info") @PutMapping("/info")
@PreAuthorize("hasAuthority('system:userinfo:update')") @PreAuthorize("@ss.hasPermi('system:user:edit')")
public ResultDomain<TbSysUserInfoDTO> updateUserInfo(@RequestBody TbSysUserInfoDTO userInfoDTO) { public ResultDomain<TbSysUserInfoDTO> updateUserInfo(@RequestBody TbSysUserInfoDTO userInfoDTO) {
return sysUserService.updateUserInfo(userInfoDTO); return sysUserService.updateUserInfo(userInfoDTO);
} }
@GetMapping("/info/{userId}") @GetMapping("/info/{userId}")
@PreAuthorize("hasAuthority('system:userinfo:read')") @PreAuthorize("@ss.hasPermi('system:user:view')")
public ResultDomain<SysUserVO> getUserInfo(@PathVariable("userId") String userId) { public ResultDomain<SysUserVO> getUserInfo(@PathVariable("userId") String userId) {
return sysUserService.getUserInfo(userId); return sysUserService.getUserInfo(userId);
} }

View File

@@ -3,6 +3,7 @@ package org.xyzh.system.controller;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
@@ -26,37 +27,44 @@ public class ViewController {
// ================= 视图相关接口 ================= // ================= 视图相关接口 =================
@PostMapping @PostMapping
@PreAuthorize("@ss.hasPermi('system:permission:manage')")
public ResultDomain<TbSysViewDTO> createView(@RequestBody TbSysViewDTO viewDTO) { public ResultDomain<TbSysViewDTO> createView(@RequestBody TbSysViewDTO viewDTO) {
return viewService.insertView(viewDTO); return viewService.insertView(viewDTO);
} }
@PutMapping @PutMapping
@PreAuthorize("@ss.hasPermi('system:permission:manage')")
public ResultDomain<TbSysViewDTO> updateView(@RequestBody TbSysViewDTO viewDTO) { public ResultDomain<TbSysViewDTO> updateView(@RequestBody TbSysViewDTO viewDTO) {
return viewService.updateView(viewDTO); return viewService.updateView(viewDTO);
} }
@DeleteMapping @DeleteMapping
@PreAuthorize("@ss.hasPermi('system:permission:manage')")
public ResultDomain<Boolean> deleteView(@RequestBody TbSysViewDTO viewDTO) { public ResultDomain<Boolean> deleteView(@RequestBody TbSysViewDTO viewDTO) {
return viewService.deleteView(viewDTO); return viewService.deleteView(viewDTO);
} }
@PostMapping("/page") @PostMapping("/page")
@PreAuthorize("@ss.hasPermi('system:permission:view')")
public ResultDomain<PermissionVO> getViewPage(@RequestBody PageRequest<PermissionVO> pageRequest) { public ResultDomain<PermissionVO> getViewPage(@RequestBody PageRequest<PermissionVO> pageRequest) {
return viewService.getViewPage(pageRequest); return viewService.getViewPage(pageRequest);
} }
@PostMapping("/list") @PostMapping("/list")
@PreAuthorize("@ss.hasPermi('system:permission:view')")
public ResultDomain<PermissionVO> getViewList(@RequestBody PermissionVO filter) { public ResultDomain<PermissionVO> getViewList(@RequestBody PermissionVO filter) {
return viewService.getViewList(filter); return viewService.getViewList(filter);
} }
// ================= 视图权限相关接口 ================== // ================= 视图权限相关接口 ==================
@PostMapping("/permission/bind") @PostMapping("/permission/bind")
@PreAuthorize("@ss.hasPermi('system:permission:manage')")
public ResultDomain<PermissionVO> bindViewPermission(@RequestBody PermissionVO permissionVO) { public ResultDomain<PermissionVO> bindViewPermission(@RequestBody PermissionVO permissionVO) {
return viewService.setViewPermissions(permissionVO); return viewService.setViewPermissions(permissionVO);
} }
@PostMapping("/permission/list") @PostMapping("/permission/list")
@PreAuthorize("@ss.hasPermi('system:permission:view')")
public ResultDomain<PermissionVO> getViewPermissions(@RequestBody PermissionVO permissionVO) { public ResultDomain<PermissionVO> getViewPermissions(@RequestBody PermissionVO permissionVO) {
return viewService.getViewPermissionList(permissionVO); return viewService.getViewPermissionList(permissionVO);
} }

View File

@@ -1,88 +0,0 @@
package org.xyzh.system.mapper.dept;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.xyzh.api.system.vo.PermissionVO;
import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.dto.sys.TbSysDeptRoleDTO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @description 系统部门角色关系Mapper接口
* @filename TbSysDeptRoleMapper.java
* @author yslg
* @copyright yslg
* @since 2025-11-07
*/
@Mapper
public interface TbSysDeptRoleMapper extends BaseMapper<TbSysDeptRoleDTO> {
/**
* @description 插入系统部门角色关系
* @param deptRoleDTO 系统部门角色关系DTO
* @return int 插入结果
* @author yslg
* @since 2025-11-07
*/
int insertDeptRole(TbSysDeptRoleDTO deptRoleDTO);
/**
* @description 更新系统部门角色关系
* @param deptRoleDTO 系统部门角色关系DTO
* @return int 更新结果
* @author yslg
* @since 2025-11-07
*/
int updateDeptRole(TbSysDeptRoleDTO deptRoleDTO);
/**
* @description 删除系统部门角色关系
* @param deptRoleDTO 系统部门角色关系DTO
* @return int 删除结果
* @author yslg
* @since 2025-11-07
*/
int deleteDeptRole(TbSysDeptRoleDTO deptRoleDTO);
/**
* @description 根据部门ID查询系统部门角色关系
* @param deptId 部门ID
* @param roleId 角色ID
* @return UserDeptRoleVO 用户部门角色VO
* @author yslg
* @since 2025-11-07
*/
PermissionVO getDeptRoleByDeptId(String deptId, String roleId);
/**
* @description 根据条件查询系统部门角色关系列表
* @param filter 系统部门角色关系DTO
* @return List<UserDeptRoleVO> 用户部门角色VO列表
* @author yslg
* @since 2025-11-07
*/
List<PermissionVO> getDeptRoleByFilter(@Param("filter") TbSysDeptRoleDTO filter);
/**
* @description 根据条件查询系统部门角色关系分页列表
* @param filter 系统部门角色关系DTO
* @param pageParam 分页参数
* @return List<UserDeptRoleVO> 用户部门角色VO列表
* @author yslg
* @since 2025-11-07
*/
List<PermissionVO> getDeptRolePageByFilter(@Param("filter") TbSysDeptRoleDTO filter, @Param("pageParam") PageParam pageParam);
/**
* @description 根据条件查询系统部门角色关系数量
* @param filter 系统部门角色关系DTO
* @return int 系统部门角色关系数量
* @author yslg
* @since 2025-11-07
*/
int getDeptRoleCount(TbSysDeptRoleDTO filter);
}

Some files were not shown because too many files have changed in this diff Show More