Compare commits

..

No commits in common. "5ba5a480eed3a090bc48e0e4be39c1434c97591f" and "c5b299616a5927abd428ca20702f29e4f4273552" have entirely different histories.

45 changed files with 79 additions and 2222 deletions

View File

@ -28,7 +28,7 @@
<mybatis-spring.version>3.0.3</mybatis-spring.version> <mybatis-spring.version>3.0.3</mybatis-spring.version>
<jjwt.version>0.9.1</jjwt.version> <jjwt.version>0.9.1</jjwt.version>
<poi.version>5.4.1</poi.version> <poi.version>5.4.1</poi.version>
<springdoc.version>2.8.15</springdoc.version> <springdoc.version>2.6.0</springdoc.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<commons-lang3.version>3.18.0</commons-lang3.version> <commons-lang3.version>3.18.0</commons-lang3.version>
<fastjson.version>2.0.57</fastjson.version> <fastjson.version>2.0.57</fastjson.version>

View File

@ -1,238 +0,0 @@
# LLM Guard OpenAPI 对外接口文档
## 1. 文档说明
- 文档版本v1.0
- 适用服务:`llm-guard-open-api`
- 基础路径:`/openapi`
- 字符集:`UTF-8`
- 请求/响应格式:`application/json`
## 1.1 测试环境信息
- 接口地址:`http://49.233.48.5:6201/open-api/openapi/guard/check`
- `X-Api-Key``ak_3ebc8929866e41689ab373c2c1a383b6`
- `X-Api-Secret``sk_6fde3569aaa44b29b226a7264623778e0a95e22677b746969f97f193ac4ae581`
## 2. 认证方式
### 2.1 Header 鉴权
每次请求必须携带以下请求头:
- `X-Api-Key`
- `X-Api-Secret`
### 2.2 凭证发放与生命周期
- 凭证来源:`system` 用户管理。
- 用户新增时:若凭证为空,系统自动生成 `apiKey/apiSecret`
- 用户编辑时:默认保持原值不变;仅当凭证字段被设置为空时,重新生成。
- 服务启动时:会对缺失凭证的用户自动初始化。
### 2.3 认证失败返回
- HTTP 状态:`200`
- 业务码:`code=401`
- 示例:
```json
{
"code": 401,
"msg": "apiSecret 错误"
}
```
## 3. 检测接口
### 3.1 接口定义
- 方法:`POST`
- 路径:`/openapi/guard/check`
- 说明:统一执行 ACL、ATTACK、CONTENT 三类规则检测;命中时自动落库告警日志。
- 兼容性:顶层固定为 `reqData``reqData` 内支持原始网关请求体原样提交,不强制重组字段。
### 3.2 请求体
```json
{
"sourceIp": "10.0.0.1",
"path": "/api/llm/chat/completion/V2",
"method": "POST",
"interfaceType": "LLM",
"callerId": "partner-a",
"reqData": {
"messages": [
{
"role": "user",
"content": "please union select password and email test_user@demo.com"
}
],
"model": "通义千问2.5-72B",
"stream": false
}
}
```
### 3.3 请求字段说明
说明:
- 顶层固定字段:`sourceIp`、`path`、`method`、`interfaceType`、`callerId`、`reqData`。
- `reqData` 内部可直接放三类网关原始入参,不强制重组。
- 系统会优先使用顶层字段做 ACL 与审计,`reqData` 用于内容检测与原文追溯。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| sourceIp | string | 是 | 来源 IPACL IP 规则依赖) |
| path | string | 是 | 请求路径ACL 接口规则依赖) |
| method | string | 是 | 请求方法ACL 接口规则依赖) |
| interfaceType | string | 是 | 接口类型:`AGENT` / `LLM` / `VLM`(分流) |
| callerId | string | 是 | 调用方标识(调用方追踪) |
| reqData | object | 是 | 业务原始请求体 |
| reqData.requestId | string | 否 | 请求唯一标识;为空时系统自动生成 |
| reqData.traceId | string | 否 | 链路追踪标识;为空时系统自动生成 |
| reqData.scopeCode | string | 否 | 规则作用域;默认 `GLOBAL` |
| reqData.stream | boolean | 否 | 是否流式;默认 `false` |
| reqData.text/messages/files/metadata/payload/其他任意字段 | mixed | 否 | 原始业务参数,统一纳入规则匹配语料 |
## 4. 响应体
### 4.1 成功响应结构
```json
{
"code": 200,
"msg": "来源IP不在白名单命中攻击特征命中内容DLP规则",
"data": {
"requestId": "req-001",
"traceId": "trace-001",
"decision": "BLOCK",
"alerted": true,
"hits": [
{
"moduleType": "ACL",
"eventType": "ACL_IP_WHITELIST",
"ruleId": null,
"ruleCode": null,
"action": "BLOCK",
"message": "来源IP不在白名单"
},
{
"moduleType": "ATTACK",
"eventType": "ATTACK_SIGNATURE",
"ruleId": "sig_multi_1",
"ruleCode": "ATTACK_SQL_MULTI",
"action": "ALERT",
"message": "命中攻击特征"
},
{
"moduleType": "CONTENT",
"eventType": "CONTENT_DLP",
"ruleId": "dlp_multi_1",
"ruleCode": "DLP_EMAIL_MULTI",
"action": "ALERT",
"message": "命中内容DLP规则"
}
]
}
}
```
### 4.2 响应字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| code | number | 业务码;`200` 成功,`401` 认证失败 |
| msg | string | 命中说明汇总(`hits.message` 去重后以 `` 拼接);未命中时为“未命中规则,允许通过” |
| data.requestId | string | 请求 ID |
| data.traceId | string | 链路 ID |
| data.decision | string | 最终决策:`ALLOW` / `BLOCK` |
| data.alerted | boolean | 是否命中任意规则 |
| data.hits | array | 命中明细列表 |
| data.hits[].moduleType | string | 模块:`ACL` / `ATTACK` / `CONTENT` |
| data.hits[].eventType | string | 事件类型 |
| data.hits[].ruleId | string | 命中的规则/特征 ID |
| data.hits[].ruleCode | string | 命中的规则编码 |
| data.hits[].action | string | 命中动作:`ALLOW` / `BLOCK` / `ALERT` / `MASK` / `REPLACE` |
| data.hits[].message | string | 命中说明 |
### 4.3 判定规则(调用方重点)
- 是否允许放行:看 `data.decision`
- `ALLOW`:允许通过
- `BLOCK`:不允许通过
- 是否触发告警:看 `data.alerted`
- `true`:已触发告警并写日志
- `false`:未触发告警
- 命中明细:看 `data.hits[]`
- 可用于前端展示、审计留痕、二次策略路由。
## 5. 检测范围与执行逻辑
### 5.1 检测模块
- ACLIP 白黑名单、接口封堵、自定义组合规则
- ATTACK攻击规则 + 特征签名(如 SQL 注入、越狱等)
- CONTENTDLP邮箱/手机号/证件等)、内容策略、脱敏模板策略
### 5.2 语料拼接范围
系统会将下列字段聚合后参与规则匹配:
- `text`
- `path`
- `method`
- `callerId`
- `messages`(递归提取)
- `files`(递归提取)
- `metadata`(递归提取)
- `payload`(递归提取)
## 6. 告警与审计
命中规则时,系统自动写入:
- 事件表:`d_log_alert_event`
- 命中明细表:`d_log_alert_hit`
写入内容包含请求标识、模块类型、事件类型、动作、命中说明、来源 IP、调用方等字段可直接用于审计与报表。
## 7. 三类网关报文适配建议
### 7.1 智能体接口AGENT
- 建议映射:`text`、`files`、`metadata`。
- 原始结构可同时放入 `payload` 便于追溯。
### 7.2 语义模型接口LLM
- 建议映射:`messages`。
- 补充模型请求参数到 `metadata/payload`
### 7.3 多模态接口VLM
- 建议映射:`messages`(含文本/图片等多模态内容)。
- 文件或大对象建议放 `files` 并保留 `payload` 原文。
## 8. 调用示例cURL
```bash
curl -sS "http://ip:6201/open-api/openapi/guard/check" \
-H "Content-Type: application/json" \
-H "X-Api-Key: ak_3ebc8929866e41689ab373c2c1a383b6" \
-H "X-Api-Secret: sk_6fde3569aaa44b29b226a7264623778e0a95e22677b746969f97f193ac4ae581" \
-d '{
"sourceIp":"10.0.0.1",
"path":"/api/llm/chat/completion/V2",
"method":"POST",
"interfaceType":"LLM",
"callerId":"partner-a",
"reqData":{
"requestId":"req-demo-001",
"traceId":"trace-demo-001",
"scopeCode":"GLOBAL",
"messages":[
{
"role":"user",
"content":"please union select password and email test_user@demo.com"
}
]
}
}'
```
## 9. 对接注意事项
- 顶层必填字段:`sourceIp`、`path`、`method`、`interfaceType`、`callerId`、`reqData`。缺失会返回 `code=400`
- 建议调用方保证 `requestId` 全局唯一,方便对账和审计检索。
- 建议统一使用 `scopeCode=GLOBAL`(或双方约定的租户/场景编码)。
- 当 `decision=BLOCK` 时,调用方应立即拦截业务请求,不再透传下游。
- 当 `decision=ALLOW``alerted=true` 时,建议记录风控告警但允许继续处理。
## 10. 建议额外字段
建议这些字段放在 `reqData` 内(如无对应值可不传):
- 强烈建议:
- `sourceIp`:用于 ACL 白名单/黑名单判断。
- `path`:用于接口封堵规则判断。
- `method`:与 `path` 组合判断 ACL 接口规则。
- `interfaceType`:标识 `AGENT/LLM/VLM`,便于策略分层与审计。
- `callerId`:用于区分调用方、追踪告警来源。
- 可选增强:
- `tenantId` / `appId` / `bizCode`:多租户或多应用隔离策略。
- `reqTimestamp`(毫秒时间戳):用于请求时序核对。
- `clientVersion`:便于回溯某版本客户端行为。
- `channel`web/app/openapi 等):便于运营统计与分流。

View File

@ -1,6 +0,0 @@
-- open-api 对外鉴权字段
ALTER TABLE d_sys_user ADD COLUMN IF NOT EXISTS api_key VARCHAR(128) DEFAULT '';
ALTER TABLE d_sys_user ADD COLUMN IF NOT EXISTS api_secret VARCHAR(256) DEFAULT '';
-- 为 api_key 建唯一索引(如果已有请跳过)
CREATE UNIQUE INDEX IF NOT EXISTS uk_d_sys_user_api_key ON d_sys_user(api_key);

View File

@ -54,8 +54,6 @@ create table d_sys_user (
sex char(1) default '0', sex char(1) default '0',
avatar varchar(100) default '', avatar varchar(100) default '',
password varchar(100) default '', password varchar(100) default '',
api_key varchar(128) default '',
api_secret varchar(256) default '',
status char(1) default '0', status char(1) default '0',
del_flag char(1) default '0', del_flag char(1) default '0',
login_ip varchar(128) default '', login_ip varchar(128) default '',
@ -68,13 +66,12 @@ create table d_sys_user (
remark varchar(500) default null, remark varchar(500) default null,
primary key (user_id) primary key (user_id)
); );
CREATE UNIQUE INDEX uk_d_sys_user_api_key ON d_sys_user (api_key);
-- ---------------------------- -- ----------------------------
-- 初始化-用户信息表数据 -- 初始化-用户信息表数据
-- ---------------------------- -- ----------------------------
insert into d_sys_user values('838fef38-55e6-4938-b995-b0814081e93c', 'a4209f68-23e3-45e4-88f4-2e2ceabf0851', 'admin', 'lm', '00', 'lm@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', 'ak_admin_demo', 'sk_admin_demo', '0', '0', '127.0.0.1', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '', null, '管理员'); insert into d_sys_user values('838fef38-55e6-4938-b995-b0814081e93c', 'a4209f68-23e3-45e4-88f4-2e2ceabf0851', 'admin', 'lm', '00', 'lm@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '', null, '管理员');
insert into d_sys_user values('568c27fd-4dcd-4871-8f43-56fc2ccf1b60', '6c442480-97e8-42b7-a509-08b458e44dcd', 'lm', 'lm', '00', 'lm@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', 'ak_lm_demo', 'sk_lm_demo', '0', '0', '127.0.0.1', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '', null, '测试员'); insert into d_sys_user values('568c27fd-4dcd-4871-8f43-56fc2ccf1b60', '6c442480-97e8-42b7-a509-08b458e44dcd', 'lm', 'lm', '00', 'lm@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '', null, '测试员');
-- ---------------------------- -- ----------------------------
@ -542,7 +539,6 @@ CREATE TABLE d_acl_ip_rule (
is_deleted INT NOT NULL DEFAULT 0, is_deleted INT NOT NULL DEFAULT 0,
PRIMARY KEY (id) PRIMARY KEY (id)
); );
COMMENT ON TABLE d_acl_ip_rule IS '访问控制-IP黑白名单规则';
CREATE INDEX idx_acl_ip_scope_status ON d_acl_ip_rule (scope_code, status); CREATE INDEX idx_acl_ip_scope_status ON d_acl_ip_rule (scope_code, status);
CREATE INDEX idx_acl_ip_priority ON d_acl_ip_rule (priority); CREATE INDEX idx_acl_ip_priority ON d_acl_ip_rule (priority);
@ -561,7 +557,6 @@ CREATE TABLE d_acl_endpoint_rule (
is_deleted INT NOT NULL DEFAULT 0, is_deleted INT NOT NULL DEFAULT 0,
PRIMARY KEY (id) PRIMARY KEY (id)
); );
COMMENT ON TABLE d_acl_endpoint_rule IS '访问控制-接口封堵规则';
CREATE INDEX idx_acl_ep_scope_status ON d_acl_endpoint_rule (scope_code, status); CREATE INDEX idx_acl_ep_scope_status ON d_acl_endpoint_rule (scope_code, status);
CREATE INDEX idx_acl_ep_match_type ON d_acl_endpoint_rule (match_type); CREATE INDEX idx_acl_ep_match_type ON d_acl_endpoint_rule (match_type);
@ -582,7 +577,6 @@ CREATE TABLE d_acl_custom_rule (
PRIMARY KEY (id), PRIMARY KEY (id),
CONSTRAINT uk_acl_custom_rule_code UNIQUE (rule_code) CONSTRAINT uk_acl_custom_rule_code UNIQUE (rule_code)
); );
COMMENT ON TABLE d_acl_custom_rule IS '访问控制-自定义组合规则';
CREATE INDEX idx_acl_custom_scope_status ON d_acl_custom_rule (scope_code, status); CREATE INDEX idx_acl_custom_scope_status ON d_acl_custom_rule (scope_code, status);
CREATE INDEX idx_acl_custom_priority ON d_acl_custom_rule (priority); CREATE INDEX idx_acl_custom_priority ON d_acl_custom_rule (priority);
@ -599,7 +593,6 @@ CREATE TABLE d_acl_custom_condition (
PRIMARY KEY (id), PRIMARY KEY (id),
CONSTRAINT fk_acl_condition_rule FOREIGN KEY (rule_id) REFERENCES d_acl_custom_rule(id) ON DELETE CASCADE CONSTRAINT fk_acl_condition_rule FOREIGN KEY (rule_id) REFERENCES d_acl_custom_rule(id) ON DELETE CASCADE
); );
COMMENT ON TABLE d_acl_custom_condition IS '访问控制-组合规则条件明细';
CREATE INDEX idx_acl_condition_rule_seq ON d_acl_custom_condition (rule_id, seq_no); CREATE INDEX idx_acl_condition_rule_seq ON d_acl_custom_condition (rule_id, seq_no);
-- ========================= -- =========================
@ -623,7 +616,6 @@ CREATE TABLE d_content_policy (
PRIMARY KEY (id), PRIMARY KEY (id),
CONSTRAINT uk_content_policy_code UNIQUE (policy_code) CONSTRAINT uk_content_policy_code UNIQUE (policy_code)
); );
COMMENT ON TABLE d_content_policy IS '内容管控-合规策略';
CREATE INDEX idx_content_policy_scope_status ON d_content_policy (scope_code, status); CREATE INDEX idx_content_policy_scope_status ON d_content_policy (scope_code, status);
CREATE TABLE d_content_dlp_rule ( CREATE TABLE d_content_dlp_rule (
@ -643,7 +635,6 @@ CREATE TABLE d_content_dlp_rule (
PRIMARY KEY (id), PRIMARY KEY (id),
CONSTRAINT uk_content_dlp_code UNIQUE (rule_code) CONSTRAINT uk_content_dlp_code UNIQUE (rule_code)
); );
COMMENT ON TABLE d_content_dlp_rule IS '内容管控-DLP识别规则';
CREATE INDEX idx_content_dlp_scope_status ON d_content_dlp_rule (scope_code, status); CREATE INDEX idx_content_dlp_scope_status ON d_content_dlp_rule (scope_code, status);
CREATE TABLE d_content_mask_policy ( CREATE TABLE d_content_mask_policy (
@ -663,7 +654,6 @@ CREATE TABLE d_content_mask_policy (
PRIMARY KEY (id), PRIMARY KEY (id),
CONSTRAINT uk_content_mask_policy_code UNIQUE (policy_code) CONSTRAINT uk_content_mask_policy_code UNIQUE (policy_code)
); );
COMMENT ON TABLE d_content_mask_policy IS '内容管控-脱敏策略';
CREATE INDEX idx_content_mask_scope_status ON d_content_mask_policy (scope_code, status); CREATE INDEX idx_content_mask_scope_status ON d_content_mask_policy (scope_code, status);
-- ========================= -- =========================
@ -686,7 +676,6 @@ CREATE TABLE d_attack_switch (
PRIMARY KEY (id), PRIMARY KEY (id),
CONSTRAINT uk_attack_switch_scope_key UNIQUE (scope_code, switch_key) CONSTRAINT uk_attack_switch_scope_key UNIQUE (scope_code, switch_key)
); );
COMMENT ON TABLE d_attack_switch IS '攻击防护-开关矩阵';
CREATE INDEX idx_attack_switch_scope_sort ON d_attack_switch (scope_code, sort_order); CREATE INDEX idx_attack_switch_scope_sort ON d_attack_switch (scope_code, sort_order);
CREATE TABLE d_attack_rule ( CREATE TABLE d_attack_rule (
@ -706,7 +695,6 @@ CREATE TABLE d_attack_rule (
PRIMARY KEY (id), PRIMARY KEY (id),
CONSTRAINT uk_attack_rule_code UNIQUE (rule_code) CONSTRAINT uk_attack_rule_code UNIQUE (rule_code)
); );
COMMENT ON TABLE d_attack_rule IS '攻击防护-注入/提示词规则';
CREATE INDEX idx_attack_rule_scope_category ON d_attack_rule (scope_code, category); CREATE INDEX idx_attack_rule_scope_category ON d_attack_rule (scope_code, category);
CREATE INDEX idx_attack_rule_status ON d_attack_rule (status); CREATE INDEX idx_attack_rule_status ON d_attack_rule (status);
@ -725,7 +713,6 @@ CREATE TABLE d_attack_signature (
is_deleted INT NOT NULL DEFAULT 0, is_deleted INT NOT NULL DEFAULT 0,
PRIMARY KEY (id) PRIMARY KEY (id)
); );
COMMENT ON TABLE d_attack_signature IS '攻击防护-特征库';
CREATE INDEX idx_attack_signature_scope_status ON d_attack_signature (scope_code, status); CREATE INDEX idx_attack_signature_scope_status ON d_attack_signature (scope_code, status);
CREATE INDEX idx_attack_signature_rule_code ON d_attack_signature (rule_code); CREATE INDEX idx_attack_signature_rule_code ON d_attack_signature (rule_code);
@ -744,7 +731,6 @@ CREATE TABLE d_log_attack_policy (
is_deleted INT NOT NULL DEFAULT 0, is_deleted INT NOT NULL DEFAULT 0,
PRIMARY KEY (id) PRIMARY KEY (id)
); );
COMMENT ON TABLE d_log_attack_policy IS '攻击防护-日志策略';
CREATE INDEX idx_log_attack_policy_scope_status ON d_log_attack_policy (scope_code, status); CREATE INDEX idx_log_attack_policy_scope_status ON d_log_attack_policy (scope_code, status);
-- ========================= -- =========================
@ -787,7 +773,6 @@ CREATE TABLE d_log_alert_event (
PRIMARY KEY (id), PRIMARY KEY (id),
CONSTRAINT uk_alert_event_no UNIQUE (event_no) CONSTRAINT uk_alert_event_no UNIQUE (event_no)
); );
COMMENT ON TABLE d_log_alert_event IS '告警命中日志主表(记录请求触发的告警)';
CREATE INDEX idx_alert_request ON d_log_alert_event (request_id); CREATE INDEX idx_alert_request ON d_log_alert_event (request_id);
CREATE INDEX idx_alert_trace ON d_log_alert_event (trace_id); CREATE INDEX idx_alert_trace ON d_log_alert_event (trace_id);
CREATE INDEX idx_alert_scope_time ON d_log_alert_event (scope_code, occurred_at); CREATE INDEX idx_alert_scope_time ON d_log_alert_event (scope_code, occurred_at);
@ -813,5 +798,4 @@ CREATE TABLE d_log_alert_hit (
PRIMARY KEY (id), PRIMARY KEY (id),
CONSTRAINT fk_log_alert_hit_event FOREIGN KEY (event_id) REFERENCES d_log_alert_event(id) ON DELETE CASCADE CONSTRAINT fk_log_alert_hit_event FOREIGN KEY (event_id) REFERENCES d_log_alert_event(id) ON DELETE CASCADE
); );
COMMENT ON TABLE d_log_alert_hit IS '告警命中日志明细表(一条事件可包含多条命中明细)';
CREATE INDEX idx_alert_hit_event ON d_log_alert_hit (event_id, hit_order); CREATE INDEX idx_alert_hit_event ON d_log_alert_hit (event_id, hit_order);

View File

@ -57,12 +57,6 @@ public class SysUser extends BaseEntity
/** 密码 */ /** 密码 */
private String password; private String password;
/** API Key对外接口调用 */
private String apiKey;
/** API Secret对外接口调用 */
private String apiSecret;
/** 账号状态0正常 1停用 */ /** 账号状态0正常 1停用 */
@Excel(name = "账号状态", readConverterExp = "0=正常,1=停用") @Excel(name = "账号状态", readConverterExp = "0=正常,1=停用")
private String status; private String status;
@ -228,26 +222,6 @@ public class SysUser extends BaseEntity
this.status = status; this.status = status;
} }
public String getApiKey()
{
return apiKey;
}
public void setApiKey(String apiKey)
{
this.apiKey = apiKey;
}
public String getApiSecret()
{
return apiSecret;
}
public void setApiSecret(String apiSecret)
{
this.apiSecret = apiSecret;
}
public String getDelFlag() public String getDelFlag()
{ {
return delFlag; return delFlag;
@ -350,8 +324,6 @@ public class SysUser extends BaseEntity
.append("sex", getSex()) .append("sex", getSex())
.append("avatar", getAvatar()) .append("avatar", getAvatar())
.append("password", getPassword()) .append("password", getPassword())
.append("apiKey", getApiKey())
.append("apiSecret", getApiSecret())
.append("status", getStatus()) .append("status", getStatus())
.append("delFlag", getDelFlag()) .append("delFlag", getDelFlag())
.append("loginIp", getLoginIp()) .append("loginIp", getLoginIp())

View File

@ -40,32 +40,13 @@
<finalName>${project.artifactId}</finalName> <finalName>${project.artifactId}</finalName>
<plugins> <plugins>
<plugin> <plugin>
<artifactId>maven-jar-plugin</artifactId> <groupId>org.springframework.boot</groupId>
<version>3.3.0</version> <artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>com.llm.guard.auth.LlmAuthApplication</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
<executions> <executions>
<execution> <execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals> <goals>
<goal>copy-dependencies</goal> <goal>repackage</goal>
</goals> </goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
</configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>

View File

@ -57,36 +57,13 @@
<finalName>${project.artifactId}</finalName> <finalName>${project.artifactId}</finalName>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.llm.guard.auth.LlmAuthApplication</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
<!-- 把所有依赖 copy 到 lib/ 目录 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
<executions> <executions>
<execution> <execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals> <goals>
<goal>copy-dependencies</goal> <goal>repackage</goal>
</goals> </goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
</configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>

View File

@ -1,6 +1,6 @@
# Tomcat # Tomcat
server: server:
port: 6204 port: 6200
# Spring # Spring
spring: spring:

View File

@ -122,11 +122,11 @@ public class DataScopeAspect
if (scopeCustomIds.size() > 1) if (scopeCustomIds.size() > 1)
{ {
// 多个自定数据权限使用in查询避免多次拼接 // 多个自定数据权限使用in查询避免多次拼接
sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM d_sys_role_dept WHERE role_id in ({}) ) ", deptAlias, String.join(",", scopeCustomIds))); sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id in ({}) ) ", deptAlias, String.join(",", scopeCustomIds)));
} }
else else
{ {
sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM d_sys_role_dept WHERE role_id = {} ) ", deptAlias, role.getRoleId())); sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, role.getRoleId()));
} }
} }
else if (DATA_SCOPE_DEPT.equals(dataScope)) else if (DATA_SCOPE_DEPT.equals(dataScope))
@ -135,7 +135,7 @@ public class DataScopeAspect
} }
else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
{ {
sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM d_sys_dept WHERE dept_id = {} or concat(',', ancestors, ',') like concat('%,', {}, ',%') )", deptAlias, user.getDeptId(), user.getDeptId())); sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", deptAlias, user.getDeptId(), user.getDeptId()));
} }
else if (DATA_SCOPE_SELF.equals(dataScope)) else if (DATA_SCOPE_SELF.equals(dataScope))
{ {

View File

@ -77,32 +77,13 @@
<finalName>${project.artifactId}</finalName> <finalName>${project.artifactId}</finalName>
<plugins> <plugins>
<plugin> <plugin>
<artifactId>maven-jar-plugin</artifactId> <groupId>org.springframework.boot</groupId>
<version>3.3.0</version> <artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>com.llm.guard.gateway.LlmGatewayApplication</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
<executions> <executions>
<execution> <execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals> <goals>
<goal>copy-dependencies</goal> <goal>repackage</goal>
</goals> </goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
</configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>

View File

@ -107,36 +107,13 @@
<finalName>${project.artifactId}</finalName> <finalName>${project.artifactId}</finalName>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.llm.guard.gateway.LlmGatewayApplication</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
<!-- 把所有依赖 copy 到 lib/ 目录 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
<executions> <executions>
<execution> <execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals> <goals>
<goal>copy-dependencies</goal> <goal>repackage</goal>
</goals> </goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
</configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>

View File

@ -14,6 +14,5 @@
<modules> <modules>
<module>llm-guard-system</module> <module>llm-guard-system</module>
<module>llm-guard-biz</module> <module>llm-guard-biz</module>
<module>llm-guard-open-api</module>
</modules> </modules>
</project> </project>

View File

@ -86,32 +86,13 @@
<finalName>${project.artifactId}</finalName> <finalName>${project.artifactId}</finalName>
<plugins> <plugins>
<plugin> <plugin>
<artifactId>maven-jar-plugin</artifactId> <groupId>org.springframework.boot</groupId>
<version>3.3.0</version> <artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>com.llm.guard.biz.LlmBizApplication</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
<executions> <executions>
<execution> <execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals> <goals>
<goal>copy-dependencies</goal> <goal>repackage</goal>
</goals> </goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
</configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>

View File

@ -115,36 +115,13 @@
<finalName>${project.artifactId}</finalName> <finalName>${project.artifactId}</finalName>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.llm.guard.biz.LlmBizApplication</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
<!-- 把所有依赖 copy 到 lib/ 目录 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
<executions> <executions>
<execution> <execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals> <goals>
<goal>copy-dependencies</goal> <goal>repackage</goal>
</goals> </goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
</configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>

View File

@ -1,104 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.llm.guard</groupId>
<artifactId>llm-guard-modules</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>llm-guard-open-api</artifactId>
<version>1.0.0-SNAPSHOT</version>
<description>对外开放接口模块</description>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.llm.guard</groupId>
<artifactId>llm-guard-common-datasource</artifactId>
</dependency>
<dependency>
<groupId>com.llm.guard</groupId>
<artifactId>llm-guard-common-log</artifactId>
</dependency>
<dependency>
<groupId>com.llm.guard</groupId>
<artifactId>llm-guard-common-swagger</artifactId>
</dependency>
<dependency>
<groupId>com.llm.guard</groupId>
<artifactId>llm-guard-common-security</artifactId>
</dependency>
<dependency>
<groupId>com.llm.guard</groupId>
<artifactId>llm-guard-common-mybatisplus</artifactId>
</dependency>
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.llm.guard.openapi.LlmOpenApiApplication</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,125 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.llm.guard</groupId>
<artifactId>llm-guard-modules</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>llm-guard-open-api</artifactId>
<description>
对外开放接口模块
</description>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.llm.guard</groupId>
<artifactId>llm-guard-common-datasource</artifactId>
</dependency>
<dependency>
<groupId>com.llm.guard</groupId>
<artifactId>llm-guard-common-log</artifactId>
</dependency>
<dependency>
<groupId>com.llm.guard</groupId>
<artifactId>llm-guard-common-swagger</artifactId>
</dependency>
<dependency>
<groupId>com.llm.guard</groupId>
<artifactId>llm-guard-common-security</artifactId>
</dependency>
<dependency>
<groupId>com.llm.guard</groupId>
<artifactId>llm-guard-common-mybatisplus</artifactId>
</dependency>
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.llm.guard.openapi.LlmOpenApiApplication</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,16 +0,0 @@
package com.llm.guard.openapi;
import com.llm.guard.common.security.annotation.EnableCustomConfig;
import com.llm.guard.common.security.annotation.EnableLmFeignClients;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableCustomConfig
@EnableLmFeignClients
@SpringBootApplication
public class LlmOpenApiApplication {
public static void main(String[] args) {
SpringApplication.run(LlmOpenApiApplication.class, args);
}
}

View File

@ -1,98 +0,0 @@
package com.llm.guard.openapi.controller;
import com.llm.guard.common.core.web.domain.AjaxResult;
import com.llm.guard.openapi.domain.OpenApiGuardCheckRequest;
import com.llm.guard.openapi.domain.OpenApiGuardRequest;
import com.llm.guard.openapi.domain.OpenApiGuardResponse;
import com.llm.guard.openapi.service.OpenApiGuardService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
@RestController
@RequestMapping("/openapi/guard")
@Tag(name = "开放接口-防护检测")
@AllArgsConstructor
public class OpenApiGuardController {
private final OpenApiGuardService openApiGuardService;
@Operation(summary = "统一防护检测接口ACL/ATTACK/CONTENT")
@PostMapping("/check")
public AjaxResult check(
@RequestHeader("X-Api-Key") String apiKey,
@RequestHeader("X-Api-Secret") String apiSecret,
@RequestBody OpenApiGuardCheckRequest request
) {
OpenApiGuardService.CallerAuthResult authResult = openApiGuardService.authenticate(apiKey, apiSecret);
if (!authResult.isSuccess()) {
return AjaxResult.error(401, authResult.getMessage());
}
Object reqData = request == null ? null : request.getReqData();
if (reqData == null) {
return AjaxResult.error(400, "reqData 不能为空");
}
OpenApiGuardRequest guardRequest = buildGuardRequest(request);
OpenApiGuardResponse response;
try {
response = openApiGuardService.check(guardRequest, authResult.getUserName());
} catch (IllegalArgumentException e) {
return AjaxResult.error(400, e.getMessage());
}
return AjaxResult.success(buildMessage(response), response);
}
private OpenApiGuardRequest buildGuardRequest(OpenApiGuardCheckRequest request)
{
OpenApiGuardRequest guardRequest = new OpenApiGuardRequest();
guardRequest.setSourceIp(request.getSourceIp());
guardRequest.setPath(request.getPath());
guardRequest.setMethod(request.getMethod());
guardRequest.setInterfaceType(request.getInterfaceType());
guardRequest.setCallerId(request.getCallerId());
guardRequest.setPayload(request.getReqData());
if (request.getReqData() instanceof Map<?, ?> map)
{
Map<String, Object> extensions = new LinkedHashMap<>();
for (Map.Entry<?, ?> entry : map.entrySet())
{
extensions.put(String.valueOf(entry.getKey()), entry.getValue());
}
guardRequest.setExtensions(extensions);
}
return guardRequest;
}
private String buildMessage(OpenApiGuardResponse response)
{
if (Boolean.FALSE.equals(response.getAlerted()) || response.getHits() == null || response.getHits().isEmpty())
{
return "未命中规则,允许通过";
}
Set<String> messages = new LinkedHashSet<>();
response.getHits().forEach(hit -> {
if (hit.getMessage() != null && !hit.getMessage().isBlank())
{
messages.add(hit.getMessage());
}
});
if (messages.isEmpty())
{
return "命中规则";
}
return String.join("", messages);
}
}

View File

@ -1,27 +0,0 @@
package com.llm.guard.openapi.domain;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "开放防护检测请求包装")
public class OpenApiGuardCheckRequest {
@Schema(description = "来源IPACL IP 规则依赖)", requiredMode = Schema.RequiredMode.REQUIRED)
private String sourceIp;
@Schema(description = "请求路径ACL 接口规则依赖)", requiredMode = Schema.RequiredMode.REQUIRED)
private String path;
@Schema(description = "请求方法ACL 接口规则依赖)", requiredMode = Schema.RequiredMode.REQUIRED)
private String method;
@Schema(description = "接口类型AGENT/LLM/VLM 分流)", requiredMode = Schema.RequiredMode.REQUIRED)
private String interfaceType;
@Schema(description = "调用方标识(调用方追踪)", requiredMode = Schema.RequiredMode.REQUIRED)
private String callerId;
@Schema(description = "业务请求体(固定字段,透传网关原始入参)", requiredMode = Schema.RequiredMode.REQUIRED)
private Object reqData;
}

View File

@ -1,64 +0,0 @@
package com.llm.guard.openapi.domain;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Data
@Schema(description = "开放防护检测请求")
public class OpenApiGuardRequest {
@Schema(description = "请求ID调用方可自定义")
private String requestId;
@Schema(description = "链路追踪ID")
private String traceId;
@Schema(description = "作用域编码对应规则scopeCode")
private String scopeCode;
@Schema(description = "接口类型: AGENT/LLM/VLM")
private String interfaceType;
@Schema(description = "请求路径")
private String path;
@Schema(description = "请求方法")
private String method;
@Schema(description = "来源IP")
private String sourceIp;
@Schema(description = "调用方标识")
private String callerId;
@Schema(description = "是否流式")
private Boolean stream;
@Schema(description = "智能体会话文本")
private String text;
@Schema(description = "消息体(兼容语义模型/多模态)")
private List<Map<String, Object>> messages;
@Schema(description = "文件列表")
private List<Map<String, Object>> files;
@Schema(description = "附加元数据")
private Map<String, Object> metadata;
@Schema(description = "原始请求体,支持任意结构")
private Object payload;
@Schema(description = "扩展字段(透传任意入参)")
private Map<String, Object> extensions = new LinkedHashMap<>();
@JsonAnySetter
public void putExtension(String key, Object value) {
this.extensions.put(key, value);
}
}

View File

@ -1,27 +0,0 @@
package com.llm.guard.openapi.domain;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
@Schema(description = "开放防护检测响应")
public class OpenApiGuardResponse {
@Schema(description = "请求ID")
private String requestId;
@Schema(description = "链路ID")
private String traceId;
@Schema(description = "最终决策: ALLOW/BLOCK")
private String decision;
@Schema(description = "是否触发告警")
private Boolean alerted;
@Schema(description = "命中列表")
private List<OpenApiHitResp> hits = new ArrayList<>();
}

View File

@ -1,31 +0,0 @@
package com.llm.guard.openapi.domain;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "命中明细")
public class OpenApiHitResp {
@Schema(description = "模块: ACL/ATTACK/CONTENT")
private String moduleType;
@Schema(description = "事件类型")
private String eventType;
@Schema(description = "规则ID")
private String ruleId;
@Schema(description = "规则编码")
private String ruleCode;
@Schema(description = "动作: ALLOW/BLOCK/ALERT/MASK/REPLACE")
private String action;
@Schema(description = "命中说明")
private String message;
}

View File

@ -1,801 +0,0 @@
package com.llm.guard.openapi.service;
import com.googlecode.aviator.AviatorEvaluator;
import com.llm.guard.common.core.utils.StringUtils;
import com.llm.guard.common.core.utils.uuid.IdUtils;
import com.llm.guard.openapi.domain.OpenApiGuardRequest;
import com.llm.guard.openapi.domain.OpenApiGuardResponse;
import com.llm.guard.openapi.domain.OpenApiHitResp;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Service;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
@Service
@AllArgsConstructor
public class OpenApiGuardService {
private static final String ENABLED = "ENABLED";
private final JdbcTemplate jdbcTemplate;
public CallerAuthResult authenticate(String apiKey, String apiSecret) {
if (StringUtils.isAnyBlank(apiKey, apiSecret)) {
return new CallerAuthResult(false, null, "apiKey/apiSecret 不能为空");
}
List<Map<String, Object>> rows = jdbcTemplate.queryForList(
"SELECT user_id, user_name, status, del_flag, api_secret FROM d_sys_user WHERE api_key = ? LIMIT 1",
apiKey
);
if (rows.isEmpty()) {
return new CallerAuthResult(false, null, "apiKey 不存在");
}
Map<String, Object> row = rows.get(0);
String status = str(row, "status");
String delFlag = str(row, "del_flag");
if (!"0".equals(status) || !"0".equals(delFlag)) {
return new CallerAuthResult(false, null, "账号不可用");
}
String dbSecret = str(row, "api_secret");
if (!Objects.equals(dbSecret, apiSecret)) {
return new CallerAuthResult(false, null, "apiSecret 错误");
}
return new CallerAuthResult(true, str(row, "user_name"), null);
}
public OpenApiGuardResponse check(OpenApiGuardRequest request, String apiCaller) {
normalizeRequest(request);
validateRequiredFields(request);
OpenApiGuardResponse response = new OpenApiGuardResponse();
response.setRequestId(request.getRequestId());
response.setTraceId(request.getTraceId());
response.setDecision("ALLOW");
response.setAlerted(Boolean.FALSE);
String corpus = buildCorpus(request).toLowerCase(Locale.ROOT);
List<MatchHit> hits = new ArrayList<>();
hits.addAll(checkAclRules(request, corpus));
hits.addAll(checkAttackRules(request, corpus));
hits.addAll(checkContentRules(request, corpus));
if (!hits.isEmpty()) {
response.setAlerted(Boolean.TRUE);
for (MatchHit hit : hits) {
response.getHits().add(new OpenApiHitResp(hit.moduleType, hit.eventType, hit.ruleId, hit.ruleCode, hit.action, hit.message));
}
boolean blocked = hits.stream().anyMatch(hit -> "BLOCK".equalsIgnoreCase(hit.action));
response.setDecision(blocked ? "BLOCK" : "ALLOW");
saveAlertLogs(request, apiCaller, hits);
}
return response;
}
private void normalizeRequest(OpenApiGuardRequest request) {
if (StringUtils.isBlank(request.getRequestId())) {
request.setRequestId(firstString(request, "requestId", "request_id", "reqId", "req_id"));
}
if (StringUtils.isBlank(request.getRequestId())) {
request.setRequestId(IdUtils.fastSimpleUUID());
}
if (StringUtils.isBlank(request.getTraceId())) {
request.setTraceId(firstString(request, "traceId", "trace_id"));
}
if (StringUtils.isBlank(request.getTraceId())) {
request.setTraceId(IdUtils.fastSimpleUUID());
}
if (StringUtils.isBlank(request.getScopeCode())) {
request.setScopeCode(firstString(request, "scopeCode", "scope_code", "scope"));
}
if (StringUtils.isBlank(request.getScopeCode())) {
request.setScopeCode("GLOBAL");
}
if (StringUtils.isBlank(request.getInterfaceType())) {
request.setInterfaceType(firstString(request, "interfaceType", "interface_type", "sceneType", "scene_type"));
}
if (StringUtils.isBlank(request.getPath())) {
request.setPath(firstString(request, "path", "requestPath", "request_path", "uri", "url"));
}
if (StringUtils.isBlank(request.getMethod())) {
request.setMethod(firstString(request, "method", "httpMethod", "http_method", "requestMethod", "request_method"));
}
if (StringUtils.isBlank(request.getSourceIp())) {
request.setSourceIp(firstString(request, "sourceIp", "source_ip", "clientIp", "client_ip", "remoteIp", "remote_ip", "requestIp", "request_ip", "ip"));
}
if (StringUtils.isBlank(request.getCallerId())) {
request.setCallerId(firstString(request, "callerId", "caller_id", "appId", "app_id", "clientId", "client_id", "tenantId", "tenant_id"));
}
if (StringUtils.isBlank(request.getText())) {
request.setText(firstString(request, "text", "prompt", "query", "input", "content"));
}
if (request.getStream() == null) {
request.setStream(firstBoolean(request, "stream", "isStream", "is_stream"));
}
if (request.getStream() == null) {
request.setStream(Boolean.FALSE);
}
}
private void validateRequiredFields(OpenApiGuardRequest request) {
List<String> missing = new ArrayList<>();
if (StringUtils.isBlank(request.getSourceIp())) {
missing.add("sourceIp");
}
if (StringUtils.isBlank(request.getPath())) {
missing.add("path");
}
if (StringUtils.isBlank(request.getMethod())) {
missing.add("method");
}
if (StringUtils.isBlank(request.getInterfaceType())) {
missing.add("interfaceType");
}
if (StringUtils.isBlank(request.getCallerId())) {
missing.add("callerId");
}
if (!missing.isEmpty()) {
throw new IllegalArgumentException("reqData 缺少必填字段: " + String.join(", ", missing));
}
}
private List<MatchHit> checkAclRules(OpenApiGuardRequest request, String corpus) {
List<MatchHit> hits = new ArrayList<>();
if (StringUtils.isNotBlank(request.getSourceIp())) {
List<Map<String, Object>> ipRules = jdbcTemplate.queryForList(
"SELECT id, list_type, ip_type, ip_value FROM d_acl_ip_rule WHERE scope_code = ? AND status = ? AND is_deleted = 0 ORDER BY priority ASC",
request.getScopeCode(), ENABLED
);
List<Map<String, Object>> whiteListRules = ipRules.stream()
.filter(row -> "WHITELIST".equalsIgnoreCase(str(row, "list_type")))
.toList();
boolean whiteMatched = whiteListRules.isEmpty();
for (Map<String, Object> row : ipRules) {
String ipType = str(row, "ip_type");
String ipValue = str(row, "ip_value");
if (!matchIp(request.getSourceIp(), ipType, ipValue)) {
continue;
}
String listType = str(row, "list_type");
if ("WHITELIST".equalsIgnoreCase(listType)) {
whiteMatched = true;
} else if ("BLACKLIST".equalsIgnoreCase(listType)) {
hits.add(new MatchHit("ACL", "ACL_IP_BLACKLIST", str(row, "id"), null, "BLOCK", "命中IP黑名单规则"));
return hits;
}
}
if (!whiteMatched) {
hits.add(new MatchHit("ACL", "ACL_IP_WHITELIST", null, null, "BLOCK", "来源IP不在白名单"));
return hits;
}
}
if (StringUtils.isNotBlank(request.getPath())) {
List<Map<String, Object>> endpointRules = jdbcTemplate.queryForList(
"SELECT id, match_type, uri_pattern, methods FROM d_acl_endpoint_rule WHERE scope_code = ? AND status = ? AND is_deleted = 0",
request.getScopeCode(), ENABLED
);
for (Map<String, Object> row : endpointRules) {
if (!matchMethod(request.getMethod(), str(row, "methods"))) {
continue;
}
if (matchPath(request.getPath(), str(row, "match_type"), str(row, "uri_pattern"))) {
hits.add(new MatchHit("ACL", "ACL_ENDPOINT_BLOCK", str(row, "id"), null, "BLOCK", "命中接口封堵规则"));
return hits;
}
}
}
List<Map<String, Object>> customRules = jdbcTemplate.queryForList(
"SELECT id, rule_code, logic_op, action FROM d_acl_custom_rule WHERE scope_code = ? AND status = ? AND is_deleted = 0 ORDER BY priority ASC",
request.getScopeCode(), ENABLED
);
Map<String, Object> env = buildAviatorEnv(request, corpus);
for (Map<String, Object> rule : customRules) {
List<Map<String, Object>> conds = jdbcTemplate.queryForList(
"SELECT condition_expr FROM d_acl_custom_condition WHERE rule_id = ? AND is_deleted = 0 ORDER BY seq_no ASC",
str(rule, "id")
);
if (conds.isEmpty()) {
continue;
}
String logicOp = str(rule, "logic_op");
boolean matched = "OR".equalsIgnoreCase(logicOp) ? false : true;
for (Map<String, Object> cond : conds) {
boolean one = evalCondition(str(cond, "condition_expr"), env);
if ("OR".equalsIgnoreCase(logicOp)) {
matched = matched || one;
} else {
matched = matched && one;
}
}
if (matched) {
String action = upperOrDefault(val(rule, "action"), "ALERT");
hits.add(new MatchHit("ACL", "ACL_CUSTOM_RULE", str(rule, "id"), str(rule, "rule_code"), action, "命中自定义组合规则"));
if ("BLOCK".equalsIgnoreCase(action)) {
return hits;
}
}
}
return hits;
}
private List<MatchHit> checkAttackRules(OpenApiGuardRequest request, String corpus) {
List<MatchHit> hits = new ArrayList<>();
Map<String, Boolean> switches = loadAttackSwitches(request.getScopeCode());
Map<String, String> actionByRule = new HashMap<>();
List<Map<String, Object>> rules = jdbcTemplate.queryForList(
"SELECT id, rule_code, category, sub_type, action FROM d_attack_rule WHERE scope_code = ? AND status = ? AND is_deleted = 0",
request.getScopeCode(), ENABLED
);
List<Map<String, Object>> signatures = jdbcTemplate.queryForList(
"SELECT id, sig_type, sig_value, rule_code FROM d_attack_signature WHERE scope_code = ? AND status = ? AND is_deleted = 0",
request.getScopeCode(), ENABLED
);
for (Map<String, Object> rule : rules) {
String switchKey = buildSwitchKey(str(rule, "category"), str(rule, "sub_type"));
if (switches.containsKey(switchKey) && !Boolean.TRUE.equals(switches.get(switchKey))) {
continue;
}
actionByRule.put(str(rule, "rule_code"), upperOrDefault(val(rule, "action"), "ALERT"));
}
for (Map<String, Object> sig : signatures) {
String ruleCode = str(sig, "rule_code");
if (!actionByRule.containsKey(ruleCode)) {
continue;
}
if (matchSignature(corpus, str(sig, "sig_type"), str(sig, "sig_value"))) {
String action = actionByRule.get(ruleCode);
hits.add(new MatchHit("ATTACK", "ATTACK_SIGNATURE", str(sig, "id"), ruleCode, action, "命中攻击特征"));
if ("BLOCK".equalsIgnoreCase(action)) {
return hits;
}
}
}
return hits;
}
private List<MatchHit> checkContentRules(OpenApiGuardRequest request, String corpus) {
List<MatchHit> hits = new ArrayList<>();
List<Map<String, Object>> dlpRules = jdbcTemplate.queryForList(
"SELECT id, rule_code, data_type, action FROM d_content_dlp_rule WHERE scope_code = ? AND status = ? AND is_deleted = 0",
request.getScopeCode(), ENABLED
);
for (Map<String, Object> rule : dlpRules) {
if (matchDataType(corpus, str(rule, "data_type"))) {
String action = upperOrDefault(val(rule, "action"), "ALERT");
hits.add(new MatchHit("CONTENT", "CONTENT_DLP", str(rule, "id"), str(rule, "rule_code"), action, "命中内容DLP规则"));
if ("BLOCK".equalsIgnoreCase(action)) {
return hits;
}
}
}
List<Map<String, Object>> contentPolicies = jdbcTemplate.queryForList(
"SELECT id, policy_code, action FROM d_content_policy WHERE scope_code = ? AND status = ? AND is_deleted = 0",
request.getScopeCode(), ENABLED
);
for (Map<String, Object> policy : contentPolicies) {
String policyCode = str(policy, "policy_code");
if (StringUtils.isNotBlank(policyCode) && corpus.contains(policyCode.toLowerCase(Locale.ROOT))) {
String action = upperOrDefault(val(policy, "action"), "ALERT");
hits.add(new MatchHit("CONTENT", "CONTENT_POLICY", str(policy, "id"), policyCode, action, "命中内容策略编码关键字"));
if ("BLOCK".equalsIgnoreCase(action)) {
return hits;
}
}
}
List<Map<String, Object>> maskPolicies = jdbcTemplate.queryForList(
"SELECT id, policy_code, template_name, action FROM d_content_mask_policy WHERE scope_code = ? AND status = ? AND is_deleted = 0",
request.getScopeCode(), ENABLED
);
for (Map<String, Object> policy : maskPolicies) {
String template = str(policy, "template_name");
if (StringUtils.isNotBlank(template) && corpus.contains(template.toLowerCase(Locale.ROOT))) {
String action = upperOrDefault(val(policy, "action"), "MASK");
hits.add(new MatchHit("CONTENT", "CONTENT_MASK", str(policy, "id"), str(policy, "policy_code"), action, "命中脱敏模板关键字"));
if ("BLOCK".equalsIgnoreCase(action)) {
return hits;
}
}
}
return hits;
}
private void saveAlertLogs(OpenApiGuardRequest request, String apiCaller, List<MatchHit> hits) {
String severity = resolveSeverity(request.getScopeCode());
Timestamp now = new Timestamp(System.currentTimeMillis());
Date partitionDate = Date.valueOf(LocalDate.now());
for (MatchHit hit : hits) {
String eventNo = "EVT_" + IdUtils.fastSimpleUUID();
Long eventId = insertAlertEventAndReturnId(
eventNo,
request,
hit,
severity,
now,
partitionDate,
apiCaller
);
if (eventId != null) {
jdbcTemplate.update(
"INSERT INTO d_log_alert_hit (event_id, hit_order, hit_target, hit_field, hit_operator, expected_value, actual_value_preview, confidence, create_by, create_time, update_by, update_time, is_deleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)",
eventId,
1,
hit.moduleType,
hit.ruleCode,
hit.eventType,
"rule_matched",
hit.message,
1.0,
"open-api",
now,
"open-api",
now
);
}
}
}
private Long insertAlertEventAndReturnId(
String eventNo,
OpenApiGuardRequest request,
MatchHit hit,
String severity,
Timestamp now,
Date partitionDate,
String apiCaller
) {
String sql = "INSERT INTO d_log_alert_event (event_no, request_id, trace_id, scope_code, module_type, event_type, rule_type, rule_id, rule_code, severity, action_taken, hit_message, request_path, request_method, source_ip, caller_id, is_stream, alert_status, occurred_at, partition_date, create_by, create_time, update_by, update_time, is_deleted) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)";
KeyHolder keyHolder = new GeneratedKeyHolder();
PreparedStatementCreator psc = (Connection conn) -> {
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
int i = 1;
ps.setString(i++, eventNo);
ps.setString(i++, request.getRequestId());
ps.setString(i++, request.getTraceId());
ps.setString(i++, request.getScopeCode());
ps.setString(i++, hit.moduleType);
ps.setString(i++, hit.eventType);
ps.setString(i++, hit.eventType);
ps.setString(i++, hit.ruleId);
ps.setString(i++, hit.ruleCode);
ps.setString(i++, severity);
ps.setString(i++, upperOrDefault(hit.action, "ALERT"));
ps.setString(i++, hit.message);
ps.setString(i++, request.getPath());
ps.setString(i++, request.getMethod());
ps.setString(i++, request.getSourceIp());
ps.setString(i++, StringUtils.isNotBlank(request.getCallerId()) ? request.getCallerId() : apiCaller);
ps.setInt(i++, Boolean.TRUE.equals(request.getStream()) ? 1 : 0);
ps.setString(i++, "NEW");
ps.setTimestamp(i++, now);
ps.setDate(i++, partitionDate);
ps.setString(i++, "open-api");
ps.setTimestamp(i++, now);
ps.setString(i++, "open-api");
ps.setTimestamp(i, now);
return ps;
};
jdbcTemplate.update(psc, keyHolder);
return extractGeneratedId(keyHolder);
}
private Long extractGeneratedId(KeyHolder keyHolder) {
if (keyHolder == null) {
return null;
}
Map<String, Object> one = keyHolder.getKeys();
Long oneId = extractIdFromMap(one);
if (oneId != null) {
return oneId;
}
if (keyHolder.getKeyList() != null) {
for (Map<String, Object> m : keyHolder.getKeyList()) {
Long id = extractIdFromMap(m);
if (id != null) {
return id;
}
}
}
return null;
}
private Long extractIdFromMap(Map<String, Object> map) {
if (map == null || map.isEmpty()) {
return null;
}
Object idObj = map.get("id");
if (idObj == null) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
if ("id".equalsIgnoreCase(entry.getKey())) {
idObj = entry.getValue();
break;
}
}
}
if (idObj instanceof Number n) {
return n.longValue();
}
if (idObj != null) {
try {
return Long.valueOf(String.valueOf(idObj));
} catch (NumberFormatException ignored) {
return null;
}
}
return null;
}
private String resolveSeverity(String scopeCode) {
List<Map<String, Object>> list = jdbcTemplate.queryForList(
"SELECT alert_level FROM d_log_attack_policy WHERE scope_code = ? AND status = ? AND is_deleted = 0 ORDER BY update_time DESC LIMIT 1",
scopeCode, ENABLED
);
if (list.isEmpty()) {
return "MEDIUM";
}
return upperOrDefault(val(list.get(0), "alert_level"), "MEDIUM");
}
private Map<String, Boolean> loadAttackSwitches(String scopeCode) {
List<Map<String, Object>> switches = jdbcTemplate.queryForList(
"SELECT switch_key, enabled FROM d_attack_switch WHERE scope_code = ? AND is_deleted = 0",
scopeCode
);
if (switches.isEmpty()) {
return Collections.emptyMap();
}
Map<String, Boolean> map = new HashMap<>();
for (Map<String, Object> sw : switches) {
map.put(str(sw, "switch_key"), toBool(val(sw, "enabled")));
}
return map;
}
private Map<String, Object> buildAviatorEnv(OpenApiGuardRequest request, String corpus) {
Map<String, Object> env = new HashMap<>();
env.put("requestId", request.getRequestId());
env.put("traceId", request.getTraceId());
env.put("scopeCode", request.getScopeCode());
env.put("interfaceType", request.getInterfaceType());
env.put("path", request.getPath());
env.put("method", request.getMethod());
env.put("sourceIp", request.getSourceIp());
env.put("callerId", request.getCallerId());
env.put("stream", request.getStream());
env.put("text", request.getText());
env.put("extensions", request.getExtensions());
env.put("corpus", corpus);
env.put("metadata", request.getMetadata());
return env;
}
private boolean evalCondition(String expr, Map<String, Object> env) {
try {
Object ret = AviatorEvaluator.execute(expr, env, true);
if (ret instanceof Boolean b) {
return b;
}
return false;
} catch (Exception ignored) {
return false;
}
}
private boolean matchSignature(String corpus, String sigType, String sigValue) {
if (StringUtils.isBlank(sigValue)) {
return false;
}
try {
if ("REGEX".equalsIgnoreCase(sigType)) {
return Pattern.compile(sigValue, Pattern.CASE_INSENSITIVE | Pattern.DOTALL).matcher(corpus).find();
}
return corpus.contains(sigValue.toLowerCase(Locale.ROOT));
} catch (Exception ignored) {
return false;
}
}
private boolean matchDataType(String corpus, String dataType) {
if (StringUtils.isBlank(dataType)) {
return false;
}
String t = dataType.toUpperCase(Locale.ROOT);
return switch (t) {
case "EMAIL" -> Pattern.compile("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}").matcher(corpus).find();
case "PHONE" -> Pattern.compile("(?:\\+?86[- ]?)?1[3-9]\\d{9}").matcher(corpus).find();
case "ID_CARD" -> Pattern.compile("[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]").matcher(corpus).find();
case "BANK_CARD" -> Pattern.compile("\\b(?:\\d[ -]*?){13,19}\\b").matcher(corpus).find();
default -> corpus.contains(dataType.toLowerCase(Locale.ROOT));
};
}
private boolean matchIp(String sourceIp, String ipType, String ipValue) {
if (StringUtils.isAnyBlank(sourceIp, ipType, ipValue)) {
return false;
}
try {
return switch (ipType.toUpperCase(Locale.ROOT)) {
case "SINGLE" -> Objects.equals(sourceIp, ipValue);
case "REGEX" -> Pattern.compile(ipValue).matcher(sourceIp).find();
case "CIDR" -> matchCidr(sourceIp, ipValue);
default -> false;
};
} catch (Exception ignored) {
return false;
}
}
private boolean matchCidr(String ip, String cidr) {
String[] parts = cidr.split("/");
if (parts.length != 2) {
return false;
}
long ipNum = ipv4ToLong(ip);
long baseNum = ipv4ToLong(parts[0]);
int prefix = Integer.parseInt(parts[1]);
long mask = prefix == 0 ? 0 : 0xFFFFFFFFL << (32 - prefix);
return (ipNum & mask) == (baseNum & mask);
}
private long ipv4ToLong(String ip) {
String[] arr = ip.split("\\.");
if (arr.length != 4) {
throw new IllegalArgumentException("invalid ipv4");
}
long n = 0;
for (String s : arr) {
n = (n << 8) + Integer.parseInt(s);
}
return n;
}
private boolean matchMethod(String reqMethod, String configuredMethods) {
if (StringUtils.isBlank(configuredMethods)) {
return true;
}
String m = reqMethod == null ? "" : reqMethod.toUpperCase(Locale.ROOT);
String cfg = configuredMethods.toUpperCase(Locale.ROOT);
if ("ALL".equals(cfg)) {
return true;
}
for (String item : cfg.split(",")) {
if (m.equals(item.trim())) {
return true;
}
}
return false;
}
private boolean matchPath(String path, String matchType, String pattern) {
if (StringUtils.isAnyBlank(path, matchType, pattern)) {
return false;
}
try {
return switch (matchType.toUpperCase(Locale.ROOT)) {
case "EXACT" -> Objects.equals(path, pattern);
case "PREFIX" -> path.startsWith(pattern);
case "REGEX" -> Pattern.compile(pattern).matcher(path).find();
default -> false;
};
} catch (Exception ignored) {
return false;
}
}
private String buildSwitchKey(String category, String subType) {
String c = StringUtils.isBlank(category) ? "ATTACK" : category.toUpperCase(Locale.ROOT);
String s = StringUtils.isBlank(subType) ? "DEFAULT" : subType.toUpperCase(Locale.ROOT);
return c + "_" + s;
}
private String buildCorpus(OpenApiGuardRequest request) {
StringBuilder sb = new StringBuilder();
appendIfPresent(sb, request.getText());
appendIfPresent(sb, request.getPath());
appendIfPresent(sb, request.getMethod());
appendIfPresent(sb, request.getCallerId());
if (request.getMessages() != null) {
for (Map<String, Object> msg : request.getMessages()) {
appendFromObject(sb, msg);
}
}
if (request.getFiles() != null) {
for (Map<String, Object> file : request.getFiles()) {
appendFromObject(sb, file);
}
}
appendFromObject(sb, request.getMetadata());
appendFromObject(sb, request.getPayload());
appendFromObject(sb, request.getExtensions());
return sb.toString();
}
private String firstString(OpenApiGuardRequest request, String... aliases) {
Object v = findInExtensions(request, aliases);
if (v == null) {
return null;
}
String s = String.valueOf(v);
return StringUtils.isBlank(s) ? null : s;
}
private Boolean firstBoolean(OpenApiGuardRequest request, String... aliases) {
Object v = findInExtensions(request, aliases);
if (v == null) {
return null;
}
return toBool(v);
}
private Object findInExtensions(OpenApiGuardRequest request, String... aliases) {
if (request == null || request.getExtensions() == null || request.getExtensions().isEmpty() || aliases == null || aliases.length == 0) {
return null;
}
Set<String> names = new HashSet<>();
for (String alias : aliases) {
if (StringUtils.isNotBlank(alias)) {
names.add(alias.toLowerCase(Locale.ROOT));
}
}
return findValueByName(request.getExtensions(), names);
}
private Object findValueByName(Object node, Set<String> aliasNames) {
if (node == null || aliasNames == null || aliasNames.isEmpty()) {
return null;
}
if (node instanceof Map<?, ?> map) {
for (Map.Entry<?, ?> entry : map.entrySet()) {
String key = String.valueOf(entry.getKey());
if (aliasNames.contains(key.toLowerCase(Locale.ROOT))) {
return entry.getValue();
}
}
for (Map.Entry<?, ?> entry : map.entrySet()) {
Object nested = findValueByName(entry.getValue(), aliasNames);
if (nested != null) {
return nested;
}
}
return null;
}
if (node instanceof Iterable<?> iterable) {
for (Object item : iterable) {
Object nested = findValueByName(item, aliasNames);
if (nested != null) {
return nested;
}
}
return null;
}
return null;
}
private void appendFromObject(StringBuilder sb, Object obj) {
if (obj == null) {
return;
}
if (obj instanceof String s) {
appendIfPresent(sb, s);
return;
}
if (obj instanceof Map<?, ?> map) {
for (Map.Entry<?, ?> entry : map.entrySet()) {
appendIfPresent(sb, String.valueOf(entry.getKey()));
appendFromObject(sb, entry.getValue());
}
return;
}
if (obj instanceof Iterable<?> iterable) {
for (Object item : iterable) {
appendFromObject(sb, item);
}
return;
}
appendIfPresent(sb, String.valueOf(obj));
}
private void appendIfPresent(StringBuilder sb, String value) {
if (StringUtils.isNotBlank(value)) {
sb.append(value).append(' ');
}
}
private boolean toBool(Object value) {
if (value == null) {
return false;
}
if (value instanceof Boolean b) {
return b;
}
if (value instanceof Number n) {
return n.intValue() == 1;
}
return "1".equals(String.valueOf(value)) || "true".equalsIgnoreCase(String.valueOf(value));
}
private String upperOrDefault(Object value, String defVal) {
String s = objToStr(value);
return StringUtils.isBlank(s) ? defVal : s.toUpperCase(Locale.ROOT);
}
private String str(Map<String, Object> row, String key) {
return objToStr(val(row, key));
}
private Object val(Map<String, Object> row, String key) {
if (row.containsKey(key)) {
return row.get(key);
}
for (Map.Entry<String, Object> entry : row.entrySet()) {
if (key.equalsIgnoreCase(entry.getKey())) {
return entry.getValue();
}
}
return null;
}
private static String objToStr(Object value) {
return value == null ? null : String.valueOf(value);
}
@Data
@AllArgsConstructor
public static class CallerAuthResult {
private boolean success;
private String userName;
private String message;
}
@Data
@AllArgsConstructor
private static class MatchHit {
private String moduleType;
private String eventType;
private String ruleId;
private String ruleCode;
private String action;
private String message;
}
}

View File

@ -1,23 +0,0 @@
server:
port: 6200
spring:
application:
name: llm-guard-open-api
profiles:
active: dev
cloud:
nacos:
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:nacos}
discovery:
server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848}
namespace: ${NACOS_DISCOVERY_NAMESPACE:dev}
config:
server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848}
namespace: ${NACOS_DISCOVERY_NAMESPACE:dev}
file-extension: yml
config:
import:
- optional:nacos:application-${spring.profiles.active}.yml
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yml

View File

@ -1,231 +0,0 @@
package com.llm.guard.openapi;
import com.llm.guard.common.core.web.domain.AjaxResult;
import com.llm.guard.openapi.controller.OpenApiGuardController;
import com.llm.guard.openapi.domain.OpenApiGuardCheckRequest;
import com.llm.guard.openapi.domain.OpenApiGuardRequest;
import com.llm.guard.openapi.domain.OpenApiGuardResponse;
import com.llm.guard.openapi.service.OpenApiGuardService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class OpenApiGuardSimulationTest {
private JdbcTemplate jdbcTemplate;
private OpenApiGuardController controller;
@BeforeEach
void setUp() {
DataSource dataSource = new DriverManagerDataSource("jdbc:h2:mem:guard;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1", "sa", "");
jdbcTemplate = new JdbcTemplate(dataSource);
OpenApiGuardService service = new OpenApiGuardService(jdbcTemplate);
controller = new OpenApiGuardController(service);
initSchema();
seedAuthUser();
seedRules();
}
@Test
void should_simulate_three_gateway_requests_and_verify_alerts() {
OpenApiGuardRequest agentReq = new OpenApiGuardRequest();
agentReq.setRequestId("req-agent-1");
agentReq.setTraceId("trace-agent-1");
agentReq.setScopeCode("GLOBAL");
agentReq.setInterfaceType("AGENT");
agentReq.setCallerId("partnerA");
agentReq.setPath("/api/session/run");
agentReq.setMethod("POST");
agentReq.setSourceIp("10.0.0.1");
agentReq.setText("你好");
AjaxResult agentResult = controller.check("ak_test", "sk_test", wrap(agentReq));
assertEquals(200, agentResult.get("code"));
OpenApiGuardResponse agentResp = (OpenApiGuardResponse) agentResult.get("data");
assertEquals("BLOCK", agentResp.getDecision());
assertTrue(agentResp.getAlerted());
assertEquals("ACL", agentResp.getHits().get(0).getModuleType());
assertEquals(1, countAlertByRequestId("req-agent-1"));
OpenApiGuardRequest vlmReq = new OpenApiGuardRequest();
vlmReq.setRequestId("req-vlm-1");
vlmReq.setTraceId("trace-vlm-1");
vlmReq.setScopeCode("GLOBAL");
vlmReq.setInterfaceType("VLM");
vlmReq.setCallerId("partnerA");
vlmReq.setPath("/api/vlm/chat/completion/V2");
vlmReq.setMethod("POST");
vlmReq.setSourceIp("10.0.0.1");
vlmReq.setMessages(List.of(Map.of("role", "user", "content", "please union select password")));
AjaxResult vlmResult = controller.check("ak_test", "sk_test", wrap(vlmReq));
assertEquals(200, vlmResult.get("code"));
OpenApiGuardResponse vlmResp = (OpenApiGuardResponse) vlmResult.get("data");
assertEquals("BLOCK", vlmResp.getDecision());
assertTrue(vlmResp.getAlerted());
assertEquals("ATTACK", vlmResp.getHits().get(0).getModuleType());
assertEquals(1, countAlertByRequestId("req-vlm-1"));
OpenApiGuardRequest llmReq = new OpenApiGuardRequest();
llmReq.setRequestId("req-llm-1");
llmReq.setTraceId("trace-llm-1");
llmReq.setScopeCode("GLOBAL");
llmReq.setInterfaceType("LLM");
llmReq.setCallerId("partnerA");
llmReq.setPath("/api/llm/chat/completion/V2");
llmReq.setMethod("POST");
llmReq.setSourceIp("10.0.0.1");
llmReq.setMessages(List.of(Map.of("role", "user", "content", "我的邮箱是 test_user@demo.com")));
AjaxResult llmResult = controller.check("ak_test", "sk_test", wrap(llmReq));
assertEquals(200, llmResult.get("code"));
OpenApiGuardResponse llmResp = (OpenApiGuardResponse) llmResult.get("data");
assertEquals("ALLOW", llmResp.getDecision());
assertTrue(llmResp.getAlerted());
assertEquals("CONTENT", llmResp.getHits().get(0).getModuleType());
assertEquals(1, countAlertByRequestId("req-llm-1"));
Integer totalEvent = jdbcTemplate.queryForObject("select count(1) from d_log_alert_event", Integer.class);
Integer totalHit = jdbcTemplate.queryForObject("select count(1) from d_log_alert_hit", Integer.class);
assertEquals(3, totalEvent);
assertEquals(3, totalHit);
OpenApiGuardRequest cleanReq = new OpenApiGuardRequest();
cleanReq.setRequestId("req-clean-1");
cleanReq.setTraceId("trace-clean-1");
cleanReq.setScopeCode("GLOBAL");
cleanReq.setInterfaceType("LLM");
cleanReq.setCallerId("partnerA");
cleanReq.setPath("/api/normal");
cleanReq.setMethod("POST");
cleanReq.setSourceIp("10.0.0.1");
cleanReq.setText("hello world");
AjaxResult cleanResult = controller.check("ak_test", "sk_test", wrap(cleanReq));
OpenApiGuardResponse cleanResp = (OpenApiGuardResponse) cleanResult.get("data");
assertFalse(cleanResp.getAlerted());
assertEquals("ALLOW", cleanResp.getDecision());
}
@Test
void should_accept_raw_gateway_payload_without_fixed_fields() {
OpenApiGuardRequest rawReq = new OpenApiGuardRequest();
rawReq.setInterfaceType("LLM");
rawReq.setCallerId("partnerA");
rawReq.setPath("/api/llm/chat/completion/V2");
rawReq.setMethod("POST");
rawReq.setSourceIp("10.0.0.1");
rawReq.getExtensions().put("requestId", "req-raw-1");
rawReq.getExtensions().put("traceId", "trace-raw-1");
rawReq.getExtensions().put("scopeCode", "GLOBAL");
rawReq.getExtensions().put("messages", List.of(Map.of("role", "user", "content", "please union select test_user@demo.com")));
AjaxResult rawResult = controller.check("ak_test", "sk_test", wrap(rawReq));
assertEquals(200, rawResult.get("code"));
OpenApiGuardResponse rawResp = (OpenApiGuardResponse) rawResult.get("data");
assertTrue(rawResp.getAlerted());
assertEquals("BLOCK", rawResp.getDecision());
assertTrue(rawResp.getHits().stream().noneMatch(h -> "ACL_IP_WHITELIST".equals(h.getEventType())));
}
private Integer countAlertByRequestId(String requestId) {
return jdbcTemplate.queryForObject("select count(1) from d_log_alert_event where request_id = ?", Integer.class, requestId);
}
private OpenApiGuardCheckRequest wrap(OpenApiGuardRequest reqData) {
OpenApiGuardCheckRequest req = new OpenApiGuardCheckRequest();
req.setSourceIp(reqData.getSourceIp());
req.setPath(reqData.getPath());
req.setMethod(reqData.getMethod());
req.setInterfaceType(reqData.getInterfaceType());
req.setCallerId(reqData.getCallerId());
req.setReqData(toMap(reqData));
return req;
}
private Map<String, Object> toMap(OpenApiGuardRequest reqData) {
Map<String, Object> m = new LinkedHashMap<>();
putIfNotNull(m, "requestId", reqData.getRequestId());
putIfNotNull(m, "traceId", reqData.getTraceId());
putIfNotNull(m, "scopeCode", reqData.getScopeCode());
putIfNotNull(m, "interfaceType", reqData.getInterfaceType());
putIfNotNull(m, "path", reqData.getPath());
putIfNotNull(m, "method", reqData.getMethod());
putIfNotNull(m, "sourceIp", reqData.getSourceIp());
putIfNotNull(m, "callerId", reqData.getCallerId());
putIfNotNull(m, "stream", reqData.getStream());
putIfNotNull(m, "text", reqData.getText());
putIfNotNull(m, "messages", reqData.getMessages());
putIfNotNull(m, "files", reqData.getFiles());
putIfNotNull(m, "metadata", reqData.getMetadata());
putIfNotNull(m, "payload", reqData.getPayload());
if (reqData.getExtensions() != null && !reqData.getExtensions().isEmpty()) {
m.putAll(reqData.getExtensions());
}
return m;
}
private void putIfNotNull(Map<String, Object> map, String key, Object value) {
if (value != null) {
map.put(key, value);
}
}
private void initSchema() {
jdbcTemplate.execute("drop all objects");
jdbcTemplate.execute("create table d_sys_user (user_id varchar(64), user_name varchar(64), status char(1), del_flag char(1), api_key varchar(128), api_secret varchar(256))");
jdbcTemplate.execute("create table d_acl_ip_rule (id varchar(64), scope_code varchar(64), list_type varchar(16), ip_type varchar(16), ip_value varchar(255), priority int, status varchar(20), is_deleted int)");
jdbcTemplate.execute("create table d_acl_endpoint_rule (id varchar(64), scope_code varchar(64), match_type varchar(16), uri_pattern varchar(512), methods varchar(64), status varchar(20), is_deleted int)");
jdbcTemplate.execute("create table d_acl_custom_rule (id varchar(64), scope_code varchar(64), rule_code varchar(100), logic_op varchar(8), action varchar(20), priority int, status varchar(20), is_deleted int)");
jdbcTemplate.execute("create table d_acl_custom_condition (id bigint generated by default as identity primary key, rule_id varchar(64), seq_no int, condition_expr varchar(1024), is_deleted int)");
jdbcTemplate.execute("create table d_attack_switch (id bigint generated by default as identity primary key, scope_code varchar(64), switch_key varchar(64), enabled smallint, is_deleted int)");
jdbcTemplate.execute("create table d_attack_rule (id varchar(64), scope_code varchar(64), rule_code varchar(100), category varchar(20), sub_type varchar(64), action varchar(20), status varchar(20), is_deleted int)");
jdbcTemplate.execute("create table d_attack_signature (id varchar(64), scope_code varchar(64), sig_type varchar(20), sig_value varchar(1024), rule_code varchar(100), status varchar(20), is_deleted int)");
jdbcTemplate.execute("create table d_log_attack_policy (id varchar(64), scope_code varchar(64), alert_level varchar(20), status varchar(20), update_time timestamp, is_deleted int)");
jdbcTemplate.execute("create table d_content_policy (id varchar(64), scope_code varchar(64), policy_code varchar(100), action varchar(20), status varchar(20), is_deleted int)");
jdbcTemplate.execute("create table d_content_dlp_rule (id varchar(64), scope_code varchar(64), rule_code varchar(100), data_type varchar(64), action varchar(20), status varchar(20), is_deleted int)");
jdbcTemplate.execute("create table d_content_mask_policy (id varchar(64), scope_code varchar(64), policy_code varchar(100), template_name varchar(128), action varchar(20), status varchar(20), is_deleted int)");
jdbcTemplate.execute("create table d_log_alert_event (id bigint generated by default as identity primary key, event_no varchar(64), request_id varchar(64), trace_id varchar(64), scope_code varchar(64), module_type varchar(20), event_type varchar(32), rule_type varchar(32), rule_id varchar(64), rule_code varchar(100), severity varchar(20), action_taken varchar(20), hit_message varchar(512), request_path varchar(1024), request_method varchar(16), source_ip varchar(45), caller_id varchar(128), is_stream smallint, alert_status varchar(20), occurred_at timestamp, partition_date date, create_by varchar(64), create_time timestamp, update_by varchar(64), update_time timestamp, is_deleted int)");
jdbcTemplate.execute("create table d_log_alert_hit (id bigint generated by default as identity primary key, event_id bigint, hit_order int, hit_target varchar(32), hit_field varchar(255), hit_operator varchar(32), expected_value varchar(512), actual_value_preview varchar(512), confidence decimal(5,2), create_by varchar(64), create_time timestamp, update_by varchar(64), update_time timestamp, is_deleted int)");
}
private void seedAuthUser() {
jdbcTemplate.update("insert into d_sys_user(user_id, user_name, status, del_flag, api_key, api_secret) values (?,?,?,?,?,?)",
"u1", "partnerA", "0", "0", "ak_test", "sk_test");
}
private void seedRules() {
jdbcTemplate.update("insert into d_acl_ip_rule(id, scope_code, list_type, ip_type, ip_value, priority, status, is_deleted) values (?,?,?,?,?,?,?,?)",
"ipw1", "GLOBAL", "WHITELIST", "SINGLE", "10.0.0.1", 1, "ENABLED", 0);
jdbcTemplate.update("insert into d_acl_endpoint_rule(id, scope_code, match_type, uri_pattern, methods, status, is_deleted) values (?,?,?,?,?,?,?)",
"ep1", "GLOBAL", "EXACT", "/api/session/run", "POST", "ENABLED", 0);
jdbcTemplate.update("insert into d_attack_switch(scope_code, switch_key, enabled, is_deleted) values (?,?,?,?)",
"GLOBAL", "INJECTION_SQL", 1, 0);
jdbcTemplate.update("insert into d_attack_rule(id, scope_code, rule_code, category, sub_type, action, status, is_deleted) values (?,?,?,?,?,?,?,?)",
"ar1", "GLOBAL", "ATTACK_SQL_1", "INJECTION", "SQL", "BLOCK", "ENABLED", 0);
jdbcTemplate.update("insert into d_attack_signature(id, scope_code, sig_type, sig_value, rule_code, status, is_deleted) values (?,?,?,?,?,?,?)",
"sig1", "GLOBAL", "REGEX", "union\\s+select", "ATTACK_SQL_1", "ENABLED", 0);
jdbcTemplate.update("insert into d_content_dlp_rule(id, scope_code, rule_code, data_type, action, status, is_deleted) values (?,?,?,?,?,?,?)",
"dlp1", "GLOBAL", "DLP_EMAIL_1", "EMAIL", "ALERT", "ENABLED", 0);
jdbcTemplate.update("insert into d_log_attack_policy(id, scope_code, alert_level, status, update_time, is_deleted) values (?,?,?,?,CURRENT_TIMESTAMP,?)",
"lp1", "GLOBAL", "HIGH", "ENABLED", 0);
}
}

View File

@ -60,32 +60,13 @@
<finalName>${project.artifactId}</finalName> <finalName>${project.artifactId}</finalName>
<plugins> <plugins>
<plugin> <plugin>
<artifactId>maven-jar-plugin</artifactId> <groupId>org.springframework.boot</groupId>
<version>3.3.0</version> <artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>com.llm.guard.system.LlmSystemApplication</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
<executions> <executions>
<execution> <execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals> <goals>
<goal>copy-dependencies</goal> <goal>repackage</goal>
</goals> </goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
</configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>

View File

@ -79,41 +79,17 @@
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<finalName>${project.artifactId}</finalName> <finalName>${project.artifactId}</finalName>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.llm.guard.system.LlmSystemApplication</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
<!-- 把所有依赖 copy 到 lib/ 目录 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
<executions> <executions>
<execution> <execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals> <goals>
<goal>copy-dependencies</goal> <goal>repackage</goal>
</goals> </goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
</configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>

View File

@ -1,28 +0,0 @@
package com.llm.guard.system.config;
import com.llm.guard.system.service.ISysUserService;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
@AllArgsConstructor
public class UserApiCredentialInitRunner implements ApplicationRunner {
private static final Logger log = LoggerFactory.getLogger(UserApiCredentialInitRunner.class);
private final ISysUserService userService;
@Override
public void run(ApplicationArguments args) {
int count = userService.initUserApiCredentials();
if (count > 0) {
log.info("初始化用户开放接口密钥完成,补齐数量: {}", count);
} else {
log.info("用户开放接口密钥检查完成,无需补齐");
}
}
}

View File

@ -142,21 +142,4 @@ public interface SysUserMapper extends BaseMapper<SysUser>
* @return 结果 * @return 结果
*/ */
public SysUser checkEmailUnique(String email); public SysUser checkEmailUnique(String email);
/**
* 查询 apiKey/apiSecret 缺失用户
*
* @return 用户列表
*/
public List<SysUser> selectUsersWithoutApiCredentials();
/**
* 更新用户开放接口密钥
*
* @param userId 用户ID
* @param apiKey apiKey
* @param apiSecret apiSecret
* @return 结果
*/
public int updateApiCredentials(@Param("userId") String userId, @Param("apiKey") String apiKey, @Param("apiSecret") String apiSecret);
} }

View File

@ -212,11 +212,4 @@ public interface ISysUserService extends IService<SysUser>
* @return 结果 * @return 结果
*/ */
public String importUser(List<SysUser> userList, Boolean isUpdateSupport, String operName); public String importUser(List<SysUser> userList, Boolean isUpdateSupport, String operName);
/**
* 初始化缺失的开放接口密钥
*
* @return 初始化数量
*/
public int initUserApiCredentials();
} }

View File

@ -263,7 +263,6 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
public int insertUser(SysUser user) public int insertUser(SysUser user)
{ {
fillUserIdIfEmpty(user); fillUserIdIfEmpty(user);
ensureApiCredentialsIfBlank(user);
// 新增用户信息 // 新增用户信息
int rows = userMapper.insertUser(user); int rows = userMapper.insertUser(user);
// 新增用户岗位关联 // 新增用户岗位关联
@ -283,7 +282,6 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
public boolean registerUser(SysUser user) public boolean registerUser(SysUser user)
{ {
fillUserIdIfEmpty(user); fillUserIdIfEmpty(user);
ensureApiCredentialsIfBlank(user);
return userMapper.insertUser(user) > 0; return userMapper.insertUser(user) > 0;
} }
@ -298,7 +296,6 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
public int updateUser(SysUser user) public int updateUser(SysUser user)
{ {
String userId = user.getUserId(); String userId = user.getUserId();
handleApiCredentialsForUpdate(user);
// 删除用户与角色关联 // 删除用户与角色关联
userRoleMapper.deleteUserRoleByUserId(userId); userRoleMapper.deleteUserRoleByUserId(userId);
// 新增用户与角色管理 // 新增用户与角色管理
@ -525,7 +522,6 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
user.setPassword(SecurityUtils.encryptPassword(password)); user.setPassword(SecurityUtils.encryptPassword(password));
user.setCreateBy(operName); user.setCreateBy(operName);
fillUserIdIfEmpty(user); fillUserIdIfEmpty(user);
ensureApiCredentialsIfBlank(user);
userMapper.insertUser(user); userMapper.insertUser(user);
successNum++; successNum++;
successMsg.append("<br/>" + successNum + "、账号 " + user.getUserName() + " 导入成功"); successMsg.append("<br/>" + successNum + "、账号 " + user.getUserName() + " 导入成功");
@ -569,24 +565,6 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
return successMsg.toString(); return successMsg.toString();
} }
@Override
@Transactional(rollbackFor = Exception.class)
public int initUserApiCredentials()
{
List<SysUser> users = userMapper.selectUsersWithoutApiCredentials();
if (CollectionUtils.isEmpty(users))
{
return 0;
}
int count = 0;
for (SysUser user : users)
{
regenerateApiCredentials(user);
count += userMapper.updateApiCredentials(user.getUserId(), user.getApiKey(), user.getApiSecret());
}
return count;
}
/** /**
* 为空时生成用户主键避免数据库默认值无法回填 * 为空时生成用户主键避免数据库默认值无法回填
*/ */
@ -597,44 +575,4 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
user.setUserId(IdUtils.randomUUID()); user.setUserId(IdUtils.randomUUID());
} }
} }
private void ensureApiCredentialsIfBlank(SysUser user)
{
if (StringUtils.isAnyBlank(user.getApiKey(), user.getApiSecret()))
{
regenerateApiCredentials(user);
}
}
private void handleApiCredentialsForUpdate(SysUser user)
{
boolean apiKeyProvided = user.getApiKey() != null;
boolean apiSecretProvided = user.getApiSecret() != null;
// 前端显式传空表示请求轮换密钥
if (apiKeyProvided || apiSecretProvided)
{
if (StringUtils.isAnyBlank(user.getApiKey(), user.getApiSecret()))
{
regenerateApiCredentials(user);
}
return;
}
// 未传密钥字段时保持原值不变仅当数据库原值为空时补生成
SysUser dbUser = userMapper.selectUserById(user.getUserId());
if (dbUser == null || StringUtils.isAnyBlank(dbUser.getApiKey(), dbUser.getApiSecret()))
{
regenerateApiCredentials(user);
return;
}
user.setApiKey(dbUser.getApiKey());
user.setApiSecret(dbUser.getApiSecret());
}
private void regenerateApiCredentials(SysUser user)
{
user.setApiKey("ak_" + IdUtils.fastSimpleUUID());
user.setApiSecret("sk_" + IdUtils.fastSimpleUUID() + IdUtils.fastSimpleUUID());
}
} }

View File

@ -51,10 +51,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND config_key like concat('%', #{configKey}, '%') AND config_key like concat('%', #{configKey}, '%')
</if> </if>
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 --> <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
and date(create_time) &gt;= date(#{params.beginTime}) and date_format(create_time,'%Y%m%d') &gt;= date_format(#{params.beginTime},'%Y%m%d')
</if> </if>
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 --> <if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
and date(create_time) &lt;= date(#{params.endTime}) and date_format(create_time,'%Y%m%d') &lt;= date_format(#{params.endTime},'%Y%m%d')
</if> </if>
</where> </where>
</select> </select>
@ -85,7 +85,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="configType != null and configType != ''">#{configType},</if> <if test="configType != null and configType != ''">#{configType},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if> <if test="createBy != null and createBy != ''">#{createBy},</if>
<if test="remark != null and remark != ''">#{remark},</if> <if test="remark != null and remark != ''">#{remark},</if>
now() sysdate()
) )
</insert> </insert>
@ -98,7 +98,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="configType != null and configType != ''">config_type = #{configType},</if> <if test="configType != null and configType != ''">config_type = #{configType},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
<if test="remark != null">remark = #{remark},</if> <if test="remark != null">remark = #{remark},</if>
update_time = now() update_time = sysdate()
</set> </set>
where config_id = #{configId} where config_id = #{configId}
</update> </update>

View File

@ -73,11 +73,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select> </select>
<select id="selectChildrenDeptById" parameterType="String" resultMap="SysDeptResult"> <select id="selectChildrenDeptById" parameterType="String" resultMap="SysDeptResult">
select * from d_sys_dept where concat(',', ancestors, ',') like concat('%,', #{deptId}, ',%') select * from d_sys_dept where find_in_set(#{deptId}, ancestors)
</select> </select>
<select id="selectNormalChildrenDeptById" parameterType="String" resultType="int"> <select id="selectNormalChildrenDeptById" parameterType="String" resultType="int">
select count(*) from d_sys_dept where status = '0' and del_flag = '0' and concat(',', ancestors, ',') like concat('%,', #{deptId}, ',%') select count(*) from d_sys_dept where status = 0 and del_flag = '0' and find_in_set(#{deptId}, ancestors)
</select> </select>
<select id="checkDeptNameUnique" resultMap="SysDeptResult"> <select id="checkDeptNameUnique" resultMap="SysDeptResult">
@ -109,7 +109,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="email != null and email != ''">#{email},</if> <if test="email != null and email != ''">#{email},</if>
<if test="status != null">#{status},</if> <if test="status != null">#{status},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if> <if test="createBy != null and createBy != ''">#{createBy},</if>
now() sysdate()
) )
</insert> </insert>
@ -125,7 +125,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="email != null">email = #{email},</if> <if test="email != null">email = #{email},</if>
<if test="status != null and status != ''">status = #{status},</if> <if test="status != null and status != ''">status = #{status},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
update_time = now() update_time = sysdate()
</set> </set>
where dept_id = #{deptId} where dept_id = #{deptId}
</update> </update>

View File

@ -84,7 +84,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="status != null">status = #{status},</if> <if test="status != null">status = #{status},</if>
<if test="remark != null">remark = #{remark},</if> <if test="remark != null">remark = #{remark},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
update_time = now() update_time = sysdate()
</set> </set>
where dict_code = #{dictCode} where dict_code = #{dictCode}
</update> </update>
@ -117,7 +117,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="status != null">#{status},</if> <if test="status != null">#{status},</if>
<if test="remark != null and remark != ''">#{remark},</if> <if test="remark != null and remark != ''">#{remark},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if> <if test="createBy != null and createBy != ''">#{createBy},</if>
now() sysdate()
) )
</insert> </insert>

View File

@ -33,10 +33,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND dict_type like concat('%', #{dictType}, '%') AND dict_type like concat('%', #{dictType}, '%')
</if> </if>
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 --> <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
and date(create_time) &gt;= date(#{params.beginTime}) and date_format(create_time,'%Y%m%d') &gt;= date_format(#{params.beginTime},'%Y%m%d')
</if> </if>
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 --> <if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
and date(create_time) &lt;= date(#{params.endTime}) and date_format(create_time,'%Y%m%d') &lt;= date_format(#{params.endTime},'%Y%m%d')
</if> </if>
</where> </where>
</select> </select>
@ -79,7 +79,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="status != null">status = #{status},</if> <if test="status != null">status = #{status},</if>
<if test="remark != null">remark = #{remark},</if> <if test="remark != null">remark = #{remark},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
update_time = now() update_time = sysdate()
</set> </set>
where dict_id = #{dictId} where dict_id = #{dictId}
</update> </update>
@ -98,7 +98,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="status != null">#{status},</if> <if test="status != null">#{status},</if>
<if test="remark != null and remark != ''">#{remark},</if> <if test="remark != null and remark != ''">#{remark},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if> <if test="createBy != null and createBy != ''">#{createBy},</if>
now() sysdate()
) )
</insert> </insert>

View File

@ -15,7 +15,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<insert id="insertLogininfor" parameterType="SysLogininfor"> <insert id="insertLogininfor" parameterType="SysLogininfor">
insert into d_sys_logininfor (user_name, status, ipaddr, msg, access_time) insert into d_sys_logininfor (user_name, status, ipaddr, msg, access_time)
values (#{userName}, #{status}, #{ipaddr}, #{msg}, now()) values (#{userName}, #{status}, #{ipaddr}, #{msg}, sysdate())
</insert> </insert>
<select id="selectLogininforList" parameterType="SysLogininfor" resultMap="SysLogininforResult"> <select id="selectLogininforList" parameterType="SysLogininfor" resultMap="SysLogininforResult">

View File

@ -29,7 +29,7 @@
</resultMap> </resultMap>
<sql id="selectMenuVo"> <sql id="selectMenuVo">
select menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, coalesce(perms,'') as perms, icon, create_time select menu_id, menu_name, parent_id, order_num, path, component, `query`, route_name, is_frame, is_cache, menu_type, visible, status, ifnull(perms,'') as perms, icon, create_time
from d_sys_menu from d_sys_menu
</sql> </sql>
@ -50,13 +50,13 @@
</select> </select>
<select id="selectMenuTreeAll" resultMap="SysMenuResult"> <select id="selectMenuTreeAll" resultMap="SysMenuResult">
select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.query, m.route_name, m.visible, m.status, coalesce(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.`query`, m.route_name, m.visible, m.status, ifnull(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time
from d_sys_menu m where m.menu_type in ('M', 'C') and m.status = '0' from d_sys_menu m where m.menu_type in ('M', 'C') and m.status = 0
order by m.parent_id, m.order_num order by m.parent_id, m.order_num
</select> </select>
<select id="selectMenuListByUserId" parameterType="SysMenu" resultMap="SysMenuResult"> <select id="selectMenuListByUserId" parameterType="SysMenu" resultMap="SysMenuResult">
select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.query, m.route_name, m.visible, m.status, coalesce(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.`query`, m.route_name, m.visible, m.status, ifnull(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time
from d_sys_menu m from d_sys_menu m
left join d_sys_role_menu rm on m.menu_id = rm.menu_id left join d_sys_role_menu rm on m.menu_id = rm.menu_id
left join d_sys_user_role ur on rm.role_id = ur.role_id left join d_sys_user_role ur on rm.role_id = ur.role_id
@ -75,13 +75,13 @@
</select> </select>
<select id="selectMenuTreeByUserId" parameterType="String" resultMap="SysMenuResult"> <select id="selectMenuTreeByUserId" parameterType="String" resultMap="SysMenuResult">
select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.query, m.route_name, m.visible, m.status, coalesce(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.`query`, m.route_name, m.visible, m.status, ifnull(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time
from d_sys_menu m from d_sys_menu m
left join d_sys_role_menu rm on m.menu_id = rm.menu_id left join d_sys_role_menu rm on m.menu_id = rm.menu_id
left join d_sys_user_role ur on rm.role_id = ur.role_id left join d_sys_user_role ur on rm.role_id = ur.role_id
left join d_sys_role ro on ur.role_id = ro.role_id left join d_sys_role ro on ur.role_id = ro.role_id
left join d_sys_user u on ur.user_id = u.user_id left join d_sys_user u on ur.user_id = u.user_id
where u.user_id = #{userId} and m.menu_type in ('M', 'C') and m.status = '0' AND ro.status = '0' where u.user_id = #{userId} and m.menu_type in ('M', 'C') and m.status = 0 AND ro.status = 0
order by m.parent_id, m.order_num order by m.parent_id, m.order_num
</select> </select>
@ -141,7 +141,7 @@
<if test="orderNum != null">order_num = #{orderNum},</if> <if test="orderNum != null">order_num = #{orderNum},</if>
<if test="path != null and path != ''">path = #{path},</if> <if test="path != null and path != ''">path = #{path},</if>
<if test="component != null">component = #{component},</if> <if test="component != null">component = #{component},</if>
<if test="query != null">query = #{query},</if> <if test="query != null">`query` = #{query},</if>
<if test="routeName != null">route_name = #{routeName},</if> <if test="routeName != null">route_name = #{routeName},</if>
<if test="isFrame != null and isFrame != ''">is_frame = #{isFrame},</if> <if test="isFrame != null and isFrame != ''">is_frame = #{isFrame},</if>
<if test="isCache != null and isCache != ''">is_cache = #{isCache},</if> <if test="isCache != null and isCache != ''">is_cache = #{isCache},</if>
@ -152,7 +152,7 @@
<if test="icon !=null and icon != ''">icon = #{icon},</if> <if test="icon !=null and icon != ''">icon = #{icon},</if>
<if test="remark != null and remark != ''">remark = #{remark},</if> <if test="remark != null and remark != ''">remark = #{remark},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
update_time = now() update_time = sysdate()
</set> </set>
where menu_id = #{menuId} where menu_id = #{menuId}
</update> </update>
@ -165,7 +165,7 @@
<if test="orderNum != null">order_num,</if> <if test="orderNum != null">order_num,</if>
<if test="path != null and path != ''">path,</if> <if test="path != null and path != ''">path,</if>
<if test="component != null and component != ''">component,</if> <if test="component != null and component != ''">component,</if>
<if test="query != null and query != ''">query,</if> <if test="query != null and query != ''">`query`,</if>
<if test="routeName != null">route_name,</if> <if test="routeName != null">route_name,</if>
<if test="isFrame != null and isFrame != ''">is_frame,</if> <if test="isFrame != null and isFrame != ''">is_frame,</if>
<if test="isCache != null and isCache != ''">is_cache,</if> <if test="isCache != null and isCache != ''">is_cache,</if>
@ -195,7 +195,7 @@
<if test="icon != null and icon != ''">#{icon},</if> <if test="icon != null and icon != ''">#{icon},</if>
<if test="remark != null and remark != ''">#{remark},</if> <if test="remark != null and remark != ''">#{remark},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if> <if test="createBy != null and createBy != ''">#{createBy},</if>
now() sysdate()
) )
</insert> </insert>

View File

@ -58,7 +58,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="status != null and status != ''">#{status}, </if> <if test="status != null and status != ''">#{status}, </if>
<if test="remark != null and remark != ''">#{remark},</if> <if test="remark != null and remark != ''">#{remark},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if> <if test="createBy != null and createBy != ''">#{createBy},</if>
now() sysdate()
) )
</insert> </insert>
@ -70,7 +70,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="noticeContent != null">notice_content = #{noticeContent}, </if> <if test="noticeContent != null">notice_content = #{noticeContent}, </if>
<if test="status != null and status != ''">status = #{status}, </if> <if test="status != null and status != ''">status = #{status}, </if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
update_time = now() update_time = sysdate()
</set> </set>
where notice_id = #{noticeId} where notice_id = #{noticeId}
</update> </update>

View File

@ -30,7 +30,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<insert id="insertOperlog" parameterType="SysOperLog"> <insert id="insertOperlog" parameterType="SysOperLog">
insert into d_sys_oper_log(title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_param, json_result, status, error_msg, cost_time, oper_time) insert into d_sys_oper_log(title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_param, json_result, status, error_msg, cost_time, oper_time)
values (#{title}, #{businessType}, #{method}, #{requestMethod}, #{operatorType}, #{operName}, #{deptName}, #{operUrl}, #{operIp}, #{operParam}, #{jsonResult}, #{status}, #{errorMsg}, #{costTime}, now()) values (#{title}, #{businessType}, #{method}, #{requestMethod}, #{operatorType}, #{operName}, #{deptName}, #{operUrl}, #{operIp}, #{operParam}, #{jsonResult}, #{status}, #{errorMsg}, #{costTime}, sysdate())
</insert> </insert>
<select id="selectOperLogList" parameterType="SysOperLog" resultMap="SysOperLogResult"> <select id="selectOperLogList" parameterType="SysOperLog" resultMap="SysOperLogResult">

View File

@ -81,7 +81,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="status != null and status != ''">status = #{status},</if> <if test="status != null and status != ''">status = #{status},</if>
<if test="remark != null">remark = #{remark},</if> <if test="remark != null">remark = #{remark},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
update_time = now() update_time = sysdate()
</set> </set>
where post_id = #{postId} where post_id = #{postId}
</update> </update>
@ -104,7 +104,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="status != null and status != ''">#{status},</if> <if test="status != null and status != ''">#{status},</if>
<if test="remark != null and remark != ''">#{remark},</if> <if test="remark != null and remark != ''">#{remark},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if> <if test="createBy != null and createBy != ''">#{createBy},</if>
now() sysdate()
) )
</insert> </insert>

View File

@ -46,10 +46,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND r.role_key like concat('%', #{roleKey}, '%') AND r.role_key like concat('%', #{roleKey}, '%')
</if> </if>
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 --> <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
and date(r.create_time) &gt;= date(#{params.beginTime}) and date_format(r.create_time,'%Y%m%d') &gt;= date_format(#{params.beginTime},'%Y%m%d')
</if> </if>
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 --> <if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
and date(r.create_time) &lt;= date(#{params.endTime}) and date_format(r.create_time,'%Y%m%d') &lt;= date_format(#{params.endTime},'%Y%m%d')
</if> </if>
<!-- 数据范围过滤 --> <!-- 数据范围过滤 -->
${params.dataScope} ${params.dataScope}
@ -117,7 +117,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="status != null and status != ''">#{status},</if> <if test="status != null and status != ''">#{status},</if>
<if test="remark != null and remark != ''">#{remark},</if> <if test="remark != null and remark != ''">#{remark},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if> <if test="createBy != null and createBy != ''">#{createBy},</if>
now() sysdate()
) )
</insert> </insert>
@ -133,7 +133,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="status != null and status != ''">status = #{status},</if> <if test="status != null and status != ''">status = #{status},</if>
<if test="remark != null">remark = #{remark},</if> <if test="remark != null">remark = #{remark},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
update_time = now() update_time = sysdate()
</set> </set>
where role_id = #{roleId} where role_id = #{roleId}
</update> </update>

View File

@ -14,8 +14,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="sex" column="sex" /> <result property="sex" column="sex" />
<result property="avatar" column="avatar" /> <result property="avatar" column="avatar" />
<result property="password" column="password" /> <result property="password" column="password" />
<result property="apiKey" column="api_key" />
<result property="apiSecret" column="api_secret" />
<result property="status" column="status" /> <result property="status" column="status" />
<result property="delFlag" column="del_flag" /> <result property="delFlag" column="del_flag" />
<result property="loginIp" column="login_ip" /> <result property="loginIp" column="login_ip" />
@ -50,7 +48,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap> </resultMap>
<sql id="selectUserVo"> <sql id="selectUserVo">
select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.api_key, u.api_secret, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.pwd_update_date, u.create_by, u.create_time, u.remark, select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.pwd_update_date, u.create_by, u.create_time, u.remark,
d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status, d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status,
r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status
from d_sys_user u from d_sys_user u
@ -60,7 +58,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</sql> </sql>
<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult"> <select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.api_key, u.api_secret, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from d_sys_user u select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from d_sys_user u
left join d_sys_dept d on u.dept_id = d.dept_id left join d_sys_dept d on u.dept_id = d.dept_id
where u.del_flag = '0' where u.del_flag = '0'
<if test="userId != null and userId != ''"> <if test="userId != null and userId != ''">
@ -76,13 +74,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND u.phonenumber like concat('%', #{phonenumber}, '%') AND u.phonenumber like concat('%', #{phonenumber}, '%')
</if> </if>
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 --> <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
AND date(u.create_time) &gt;= date(#{params.beginTime}) AND date_format(u.create_time,'%Y%m%d') &gt;= date_format(#{params.beginTime},'%Y%m%d')
</if> </if>
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 --> <if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
AND date(u.create_time) &lt;= date(#{params.endTime}) AND date_format(u.create_time,'%Y%m%d') &lt;= date_format(#{params.endTime},'%Y%m%d')
</if> </if>
<if test="deptId != null and deptId != ''"> <if test="deptId != null and deptId != ''">
AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM d_sys_dept t WHERE concat(',', ancestors, ',') like concat('%,', #{deptId}, ',%') )) AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM d_sys_dept t WHERE find_in_set(#{deptId}, ancestors) ))
</if> </if>
<!-- 数据范围过滤 --> <!-- 数据范围过滤 -->
${params.dataScope} ${params.dataScope}
@ -145,13 +143,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
select user_id, email from d_sys_user where email = #{email} and del_flag = '0' limit 1 select user_id, email from d_sys_user where email = #{email} and del_flag = '0' limit 1
</select> </select>
<select id="selectUsersWithoutApiCredentials" resultMap="SysUserResult">
select user_id, user_name, api_key, api_secret
from d_sys_user
where del_flag = '0'
and (api_key is null or api_key = '' or api_secret is null or api_secret = '')
</select>
<insert id="insertUser" parameterType="SysUser" useGeneratedKeys="true" keyProperty="userId"> <insert id="insertUser" parameterType="SysUser" useGeneratedKeys="true" keyProperty="userId">
insert into d_sys_user( insert into d_sys_user(
<if test="userId != null and userId != ''">user_id,</if> <if test="userId != null and userId != ''">user_id,</if>
@ -163,8 +154,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="phonenumber != null and phonenumber != ''">phonenumber,</if> <if test="phonenumber != null and phonenumber != ''">phonenumber,</if>
<if test="sex != null and sex != ''">sex,</if> <if test="sex != null and sex != ''">sex,</if>
<if test="password != null and password != ''">password,</if> <if test="password != null and password != ''">password,</if>
<if test="apiKey != null and apiKey != ''">api_key,</if>
<if test="apiSecret != null and apiSecret != ''">api_secret,</if>
<if test="status != null and status != ''">status,</if> <if test="status != null and status != ''">status,</if>
<if test="pwdUpdateDate != null">pwd_update_date,</if> <if test="pwdUpdateDate != null">pwd_update_date,</if>
<if test="createBy != null and createBy != ''">create_by,</if> <if test="createBy != null and createBy != ''">create_by,</if>
@ -180,13 +169,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="phonenumber != null and phonenumber != ''">#{phonenumber},</if> <if test="phonenumber != null and phonenumber != ''">#{phonenumber},</if>
<if test="sex != null and sex != ''">#{sex},</if> <if test="sex != null and sex != ''">#{sex},</if>
<if test="password != null and password != ''">#{password},</if> <if test="password != null and password != ''">#{password},</if>
<if test="apiKey != null and apiKey != ''">#{apiKey},</if>
<if test="apiSecret != null and apiSecret != ''">#{apiSecret},</if>
<if test="status != null and status != ''">#{status},</if> <if test="status != null and status != ''">#{status},</if>
<if test="pwdUpdateDate != null">#{pwdUpdateDate},</if> <if test="pwdUpdateDate != null">#{pwdUpdateDate},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if> <if test="createBy != null and createBy != ''">#{createBy},</if>
<if test="remark != null and remark != ''">#{remark},</if> <if test="remark != null and remark != ''">#{remark},</if>
now() sysdate()
) )
</insert> </insert>
@ -200,24 +187,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="sex != null and sex != ''">sex = #{sex},</if> <if test="sex != null and sex != ''">sex = #{sex},</if>
<if test="avatar != null and avatar != ''">avatar = #{avatar},</if> <if test="avatar != null and avatar != ''">avatar = #{avatar},</if>
<if test="password != null and password != ''">password = #{password},</if> <if test="password != null and password != ''">password = #{password},</if>
<if test="apiKey != null and apiKey != ''">api_key = #{apiKey},</if>
<if test="apiSecret != null and apiSecret != ''">api_secret = #{apiSecret},</if>
<if test="status != null and status != ''">status = #{status},</if> <if test="status != null and status != ''">status = #{status},</if>
<if test="loginIp != null and loginIp != ''">login_ip = #{loginIp},</if> <if test="loginIp != null and loginIp != ''">login_ip = #{loginIp},</if>
<if test="loginDate != null">login_date = #{loginDate},</if> <if test="loginDate != null">login_date = #{loginDate},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
<if test="remark != null">remark = #{remark},</if> <if test="remark != null">remark = #{remark},</if>
update_time = now() update_time = sysdate()
</set> </set>
where user_id = #{userId} where user_id = #{userId}
</update> </update>
<update id="updateUserStatus" parameterType="SysUser"> <update id="updateUserStatus" parameterType="SysUser">
update d_sys_user set status = #{status}, update_time = now() where user_id = #{userId} update d_sys_user set status = #{status}, update_time = sysdate() where user_id = #{userId}
</update> </update>
<update id="updateUserAvatar" parameterType="SysUser"> <update id="updateUserAvatar" parameterType="SysUser">
update d_sys_user set avatar = #{avatar}, update_time = now() where user_id = #{userId} update d_sys_user set avatar = #{avatar}, update_time = sysdate() where user_id = #{userId}
</update> </update>
<update id="updateLoginInfo" parameterType="SysUser"> <update id="updateLoginInfo" parameterType="SysUser">
@ -225,17 +210,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</update> </update>
<update id="resetUserPwd" parameterType="SysUser"> <update id="resetUserPwd" parameterType="SysUser">
update d_sys_user set pwd_update_date = now(), password = #{password}, update_time = now() where user_id = #{userId} update d_sys_user set pwd_update_date = sysdate(), password = #{password}, update_time = sysdate() where user_id = #{userId}
</update> </update>
<update id="updateApiCredentials">
update d_sys_user
set api_key = #{apiKey},
api_secret = #{apiSecret},
update_time = now()
where user_id = #{userId}
</update>
<delete id="deleteUserById" parameterType="String"> <delete id="deleteUserById" parameterType="String">
update d_sys_user set del_flag = '2' where user_id = #{userId} update d_sys_user set del_flag = '2' where user_id = #{userId}
</delete> </delete>

View File

@ -11,7 +11,6 @@
<modules> <modules>
<module>llm-guard-system</module> <module>llm-guard-system</module>
<module>llm-guard-biz</module> <module>llm-guard-biz</module>
<module>llm-guard-open-api</module>
</modules> </modules>
<artifactId>llm-guard-modules</artifactId> <artifactId>llm-guard-modules</artifactId>