feat(dify): 全部迁移到内网实例,统一为单份配置
VPN 打通后开发机可直连内网,原先「默认公网 + prod 覆盖内网」的双份配置 已无必要,且两套 Key/知识库 ID 完全不通用(实测互打一律 401), 留着只会造成误用。公网那套整体移除,回退到 git 历史取。 - application.yml:base-url 与 8 个应用 Key、全部知识库密钥/ID 改指内网; 原默认值来自公网,其注释同步改写 - application-dev.yml / application-prod.yml:删除 dify 覆盖段。 YAML map 是按键合并而非整体替换,保留一份与默认值相同的覆盖 只会在将来漏改时混出「一半内网一半公网」的状态 - DifyProperties:默认 baseUrl 改内网,类注释同步 修复一个上线起就坏的接口: - admin 端「竞赛历史」文档列表按 challenge-cup 取库,而该 ID b342f096-… 在**公网实例上也返回 404**(公网真实的挑战杯库是 7c812029-…,从未登记),故该接口一直不可用。改指内网「竞赛案例」 库(181d58e3-…,145 篇),实测已能列出文档。对外路径保持 /challenge-cup/documents 不变,以免影响 admin 端调用 清理失效的工作流变量(内网实测已从开始节点移除): - document-process 的 query / pdf_url,declaration 的 parse_value。 三者配置为空串以显式丢弃 —— 直接删行会退回「按原名透传」, 反而把 pdfUrl / parseValue 这类驼峰名发给 Dify。实体字段保留, 不破坏 /documentProcess/upload 的入参契约(pdfUrl 仍参与 「必须提供文件或 pdfUrl」的校验,只是不再转发) - 注:Dify 对多传变量静默忽略,故此前不报错、功能未受影响, 属「看着对其实无效」的配置 测试:改断言为内网值,并新增「不残留任何公网配置」「失效变量必须 配成空串」等防回归用例;变异验证(base-url 改回公网 / 空串改回生效名) 确认用例有真实判别力。Dify 相关 44 例全绿。 Refs: .scratch/dify-probe/README.md, .scratch/ai-assistant/map.md
This commit is contained in:
@@ -13,9 +13,10 @@ import java.util.Map;
|
|||||||
* 中,换环境要改代码、Key 还会进版本库。此处统一外置到 {@code application.yml} 的
|
* 中,换环境要改代码、Key 还会进版本库。此处统一外置到 {@code application.yml} 的
|
||||||
* {@code dify} 节点,各环境可在 {@code application-{profile}.yml} 中按需覆盖。
|
* {@code dify} 节点,各环境可在 {@code application-{profile}.yml} 中按需覆盖。
|
||||||
*
|
*
|
||||||
* <p>关于两套环境:文档《Dify API 文档》给出的是内网实例 {@code http://10.100.158.15/v1}
|
* <p>关于实例:2026-10-12 起统一使用内网实例 {@code http://10.100.158.15/v1}
|
||||||
* (与开放平台 IP 白名单同机),当前代码里连的是公网实例 {@code http://82.156.98.71/v1}。
|
* (见《Dify API 文档》,与开放平台 IP 白名单同机)。此前因开发机无法直连内网,
|
||||||
* 二者应用 Key、知识库 ID 均不通用,因此这里按环境分别配置,默认沿用现有公网实例以保证在跑的功能不受影响。
|
* 默认值曾指向公网实例 {@code http://82.156.98.71/v1},现已随 VPN 打通一并迁移;
|
||||||
|
* 两套实例的应用 Key 与知识库 ID 互不通用(互打一律 401),故不再保留双份配置。
|
||||||
*
|
*
|
||||||
* @author Codex
|
* @author Codex
|
||||||
* @since 2026-09-19
|
* @since 2026-09-19
|
||||||
@@ -27,7 +28,7 @@ public class DifyProperties {
|
|||||||
/**
|
/**
|
||||||
* 接口地址前缀,不含结尾斜杠
|
* 接口地址前缀,不含结尾斜杠
|
||||||
*/
|
*/
|
||||||
private String baseUrl = "http://82.156.98.71/v1";
|
private String baseUrl = "http://10.100.158.15/v1";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单次请求超时时间(毫秒)。AI 工作流耗时较长,默认 10 分钟。
|
* 单次请求超时时间(毫秒)。AI 工作流耗时较长,默认 10 分钟。
|
||||||
|
|||||||
@@ -164,15 +164,26 @@ public class KnowledgeBaseController extends BaseController {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------- 兼容旧接口
|
// ---------------------------------------------------------------- 兼容旧接口
|
||||||
|
|
||||||
@ApiOperation("挑战杯历史案例知识库文档列表")
|
/**
|
||||||
|
* 竞赛历史案例知识库文档列表。
|
||||||
|
*
|
||||||
|
* <p>路径里的 {@code challenge-cup} 是历史遗留的对外接口名,admin 端仍按此调用,
|
||||||
|
* 故保留路径不动;实际取的是「竞赛案例」库(内网实例,145 篇)。
|
||||||
|
*
|
||||||
|
* <p>2026-10-12 迁移修复:原先这里解析 {@code dify.dataset.ids.challenge-cup},
|
||||||
|
* 而该 ID 指向公网实例的挑战杯库 —— 经实测它在公网、内网两侧**都返回 404**
|
||||||
|
* (公网真实的挑战杯库是 7c812029-…,从未登记),也就是说这个接口从上线起就是坏的。
|
||||||
|
* 迁移到内网后改按 {@code competition-cases} 解析,接口才真正可用。
|
||||||
|
*/
|
||||||
|
@ApiOperation("竞赛历史案例知识库文档列表")
|
||||||
@GetMapping("/challenge-cup/documents")
|
@GetMapping("/challenge-cup/documents")
|
||||||
public ApiResult<?> challengeCupDocuments(
|
public ApiResult<?> challengeCupDocuments(
|
||||||
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||||
@RequestParam(value = "limit", defaultValue = "20") Integer limit,
|
@RequestParam(value = "limit", defaultValue = "20") Integer limit,
|
||||||
@RequestParam(value = "keyword", required = false) String keyword) {
|
@RequestParam(value = "keyword", required = false) String keyword) {
|
||||||
String datasetId = difyProperties.getDataset().idOf("challenge-cup");
|
String datasetId = difyProperties.getDataset().idOf("competition-cases");
|
||||||
if (StrUtil.isBlank(datasetId)) {
|
if (StrUtil.isBlank(datasetId)) {
|
||||||
return fail("未配置 challenge-cup 知识库 ID,请检查 dify.dataset.ids.challenge-cup");
|
return fail("未配置竞赛案例知识库 ID,请检查 dify.dataset.ids.competition-cases");
|
||||||
}
|
}
|
||||||
return documents(datasetId, page, limit, keyword);
|
return documents(datasetId, page, limit, keyword);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,12 @@ import java.io.Serializable;
|
|||||||
* <p>业务说明:根据项目核心创意、技术路线和团队情况生成申报书初稿;
|
* <p>业务说明:根据项目核心创意、技术路线和团队情况生成申报书初稿;
|
||||||
* 已提供初稿时可结合前沿资料优化完善。
|
* 已提供初稿时可结合前沿资料优化完善。
|
||||||
*
|
*
|
||||||
* <p><b>字段名对齐 Dify 工作流开始节点的真实变量名</b>,取自 2026-09-19
|
* <p><b>字段名对齐 Dify 工作流开始节点的真实变量名</b>,取自内网实例
|
||||||
* 在内网实例 10.100.158.15 上跑 probe-dify.sh 的实测输出:
|
* 10.100.158.15 上 probe-dify.sh 的实测输出(2026-10-12 重测):
|
||||||
* {@code core_idea, tech_route, team_intro, upload_file(文件类), parse_value}。
|
* {@code core_idea, tech_route, team_intro, upload_file(文件类)}。
|
||||||
|
* 早先实测还有 {@code parse_value},现已从开始节点移除;{@link #parseValue}
|
||||||
|
* 仍在入参实体上(外部调用方可继续传,避免破坏接口契约),但映射表已把它
|
||||||
|
* 配置为空串,**不会下发给 Dify**。
|
||||||
*
|
*
|
||||||
* <p>发送时仍会经 {@code dify.workflows.declaration.fields} 映射一次,
|
* <p>发送时仍会经 {@code dify.workflows.declaration.fields} 映射一次,
|
||||||
* 保留这一层是为了让变量名再次变动时只改 yml、不改代码。
|
* 保留这一层是为了让变量名再次变动时只改 yml、不改代码。
|
||||||
@@ -55,10 +58,10 @@ public class DeclarationInputs implements Serializable {
|
|||||||
private Object uploadFile;
|
private Object uploadFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析取值开关。对应 Dify 变量 parse_value。
|
* 解析取值开关。
|
||||||
*
|
*
|
||||||
* <p>类型未在探针中确定(探针只给变量名不给类型),
|
* <p><b>当前不会下发给 Dify</b>:原对应变量 {@code parse_value} 已从工作流开始
|
||||||
* 故用 Object 承接,传布尔、字符串或数字均可,由 Dify 侧决定语义。
|
* 节点移除,映射表把它配置为空串以显式丢弃。字段保留是为了不破坏入参契约。
|
||||||
*/
|
*/
|
||||||
@TableField(exist = false)
|
@TableField(exist = false)
|
||||||
private Object parseValue;
|
private Object parseValue;
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ import java.io.Serializable;
|
|||||||
* <p>业务说明:对上传的 PDF 或网络文件进行解析、清洗、分段、关键词提取,
|
* <p>业务说明:对上传的 PDF 或网络文件进行解析、清洗、分段、关键词提取,
|
||||||
* 处理结果导入「GXMU 团章」知识库。因此文件来源有二:上传文件或网络 PDF 地址。
|
* 处理结果导入「GXMU 团章」知识库。因此文件来源有二:上传文件或网络 PDF 地址。
|
||||||
*
|
*
|
||||||
* <p><b>字段名对齐 Dify 工作流开始节点的真实变量名</b>,取自 2026-09-19
|
* <p><b>字段名对齐 Dify 工作流开始节点的真实变量名</b>,取自内网实例
|
||||||
* 在内网实例 10.100.158.15 上跑 probe-dify.sh 的实测输出:
|
* 10.100.158.15 上 probe-dify.sh 的实测输出(2026-10-12 重测):
|
||||||
* {@code query, pdf_url, pdf_file(文件类)}。
|
* {@code pdf_file(文件类)}。
|
||||||
|
* 早先实测还有 {@code query, pdf_url},现均已从开始节点移除;映射表已把这两个
|
||||||
|
* 字段配置为空串(丢弃),故 {@link #query} 与 {@link #pdfUrl} 不会被下发给 Dify。
|
||||||
*
|
*
|
||||||
* <p>发送时仍会经 {@code dify.workflows.document-process.fields} 映射一次,
|
* <p>发送时仍会经 {@code dify.workflows.document-process.fields} 映射一次,
|
||||||
* 保留这一层是为了让变量名再次变动时只改 yml、不改代码。
|
* 保留这一层是为了让变量名再次变动时只改 yml、不改代码。
|
||||||
@@ -29,13 +31,21 @@ import java.io.Serializable;
|
|||||||
public class DocumentProcessInputs implements Serializable {
|
public class DocumentProcessInputs implements Serializable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理指令或补充说明。对应 Dify 变量 query
|
* 处理指令或补充说明。
|
||||||
|
*
|
||||||
|
* <p><b>当前不会下发给 Dify</b>:原对应变量 {@code query} 已从工作流开始节点移除,
|
||||||
|
* 映射表把它配置为空串以显式丢弃。字段保留是为了不破坏
|
||||||
|
* {@code /api/ai/documentProcess/upload} 的入参契约。
|
||||||
*/
|
*/
|
||||||
@TableField(exist = false)
|
@TableField(exist = false)
|
||||||
private String query;
|
private String query;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 网络 PDF 地址。对应 Dify 变量 pdf_url
|
* 网络 PDF 地址。
|
||||||
|
*
|
||||||
|
* <p><b>当前不会下发给 Dify</b>:原对应变量 {@code pdf_url} 已从开始节点移除,
|
||||||
|
* 映射表配置为空串以显式丢弃。注意 {@code /documentProcess/upload} 仍接受
|
||||||
|
* {@code pdfUrl} 参数并参与"必须提供文件或 pdfUrl"的校验,只是不再转发给 Dify。
|
||||||
*/
|
*/
|
||||||
@TableField(exist = false)
|
@TableField(exist = false)
|
||||||
private String pdfUrl;
|
private String pdfUrl;
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ spring:
|
|||||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
type: com.alibaba.druid.pool.DruidDataSource
|
type: com.alibaba.druid.pool.DruidDataSource
|
||||||
|
|
||||||
|
redis:
|
||||||
|
database: 0
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 6379
|
||||||
# 日志配置
|
# 日志配置
|
||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
@@ -27,3 +31,10 @@ config:
|
|||||||
wxpay:
|
wxpay:
|
||||||
private-key-path: /Users/liangxin/Project/JAVA/guoxue-java/src/main/resources/cert/apiclient_key.pem
|
private-key-path: /Users/liangxin/Project/JAVA/guoxue-java/src/main/resources/cert/apiclient_key.pem
|
||||||
pub-key-path: /Users/liangxin/Project/JAVA/guoxue-java/src/main/resources/cert/44ad9c08a0ab46dd8fc555178df581c4.pem
|
pub-key-path: /Users/liangxin/Project/JAVA/guoxue-java/src/main/resources/cert/44ad9c08a0ab46dd8fc555178df581c4.pem
|
||||||
|
|
||||||
|
# Dify 对接不在此覆盖。
|
||||||
|
#
|
||||||
|
# 2026-10-12 迁移后,application.yml 的默认值就是内网实例,开发与生产共用同一套
|
||||||
|
# 地址与密钥,这里再写一份只会造成两处维护、且极易与默认值漂移。
|
||||||
|
# VPN 已可直连 10.100.158.15,故本地开发无需再走另一套实例。
|
||||||
|
# 如确需覆盖(例如临时指向其它环境),在此加 dify.base-url 与对应密钥。
|
||||||
|
|||||||
@@ -38,55 +38,10 @@ wxpay:
|
|||||||
private-key-path: /www/wwwroot/tuanwei/cert/apiclient_key.pem
|
private-key-path: /www/wwwroot/tuanwei/cert/apiclient_key.pem
|
||||||
pub-key-path: /www/wwwroot/tuanwei/cert/44ad9c08a0ab46dd8fc555178df581c4.pem
|
pub-key-path: /www/wwwroot/tuanwei/cert/44ad9c08a0ab46dd8fc555178df581c4.pem
|
||||||
|
|
||||||
# Dify 平台对接(生产走内网实例)
|
# Dify 平台对接不在此覆盖。
|
||||||
#
|
#
|
||||||
# 2026-09-19 由公网 82.156.98.71 切至内网 10.100.158.15。
|
# 2026-10-12 迁移:原先此处整段覆盖为内网实例(因 application.yml 默认值指向公网)。
|
||||||
# 切换依据:内网实例 8 个应用全部可用且知识库内容更全
|
# 默认值已改为内网,生产与开发共用 application.yml 的同一套地址与密钥,
|
||||||
# (竞赛案例 146 篇 / GXMU团章 10 篇,公网侧仅挑战杯 5 篇 / GXMU团章 9 篇)。
|
# 故本段整体移除 —— 保留一份"与默认值相同"的覆盖只会增加漏改风险。
|
||||||
#
|
# 注意 YAML 的 map 是按键合并而非整体替换:若在此只写部分 keys,
|
||||||
# 两个实例的应用 Key 与知识库 ID 完全不通用,不可混用:
|
# 会与默认值合并出混合状态,这正是要避免的。
|
||||||
# 公网 Key 打到内网、或反之,一律返回 401。
|
|
||||||
# 因此下面每一项都必须成对覆盖,漏掉一项就会因公网 Key 失效而报错。
|
|
||||||
#
|
|
||||||
# 内网的 6 个存量应用变量名与公网一致(已由 probe-dify.sh 两轮实测比对),
|
|
||||||
# 故此处只需覆盖地址与密钥,不必动 workflows 变量映射。
|
|
||||||
dify:
|
|
||||||
base-url: http://10.100.158.15/v1
|
|
||||||
apps:
|
|
||||||
# 1 医科大团委智能知识库问答系统
|
|
||||||
chat: app-ZLUUbLkAX3FVzDubY06puxAo
|
|
||||||
# 2 活动策划大纲生成
|
|
||||||
activity-outline: app-4oBWB3HrdNlANdPFG8ZvVTkY
|
|
||||||
# 3 医科大智能文档处理系统
|
|
||||||
document-process: app-ohdIhk0lVfUwoy10xXRebmjD
|
|
||||||
# 4 AI 内容创作与宣传助手
|
|
||||||
manuscript: app-fhhz4fXMkEdVivAEiuWpycn6
|
|
||||||
# 5 AI 创意助手
|
|
||||||
creative-assistant: app-lAbpxMggNbIrByiDRDfckSSl
|
|
||||||
# 6 项目书质量初审
|
|
||||||
project-quality-review: app-J6h5I6Bo4oeBif2EofBGVrGs
|
|
||||||
# 7 申报书生成
|
|
||||||
declaration: app-k20aDmiQYh3LxqZSLuX8wjkU
|
|
||||||
# 8 竞赛文件预处理
|
|
||||||
competition-history-preprocess: app-UzVLKnGwg69uAUdbgFx6XNmc
|
|
||||||
dataset:
|
|
||||||
# 内网两个知识库各自签发独立密钥,注意与上面的应用 Key 不同前缀(dataset-)
|
|
||||||
api-key: dataset-jaqtnAQvYcQTEjECzxhQfNCv
|
|
||||||
# 注意:YAML 的 map 是"按键合并"而非整体替换 —— application.yml 里登记的
|
|
||||||
# 公网库密钥不会因为这里重写了 keys 就消失,而是与本段合并。
|
|
||||||
# 因此这里把公网那两个 ID 也显式指到内网密钥,避免残留一条指向公网实例的死密钥
|
|
||||||
# (残留后表现为"某个知识库接口偶发 401",很难查)。
|
|
||||||
keys:
|
|
||||||
# 内网知识库
|
|
||||||
181d58e3-78c2-4b7d-939c-57e780a8f7d6: dataset-jaqtnAQvYcQTEjECzxhQfNCv
|
|
||||||
3e0ac108-3a0d-41e2-8447-aa1c1044d3ac: dataset-f2a0Hjxix7lBcPNZtNMyKDTQ
|
|
||||||
# 公网知识库 ID:生产下同样指到内网密钥,杜绝残留公网密钥
|
|
||||||
b342f096-98b1-4a83-a296-f4ab60a6db0e: dataset-jaqtnAQvYcQTEjECzxhQfNCv
|
|
||||||
aa72c649-71a0-46e7-ac55-47f44b397a4e: dataset-f2a0Hjxix7lBcPNZtNMyKDTQ
|
|
||||||
ids:
|
|
||||||
# 内网知识库(生产实际使用)
|
|
||||||
competition-cases: 181d58e3-78c2-4b7d-939c-57e780a8f7d6
|
|
||||||
gxmu-constitution-internal: 3e0ac108-3a0d-41e2-8447-aa1c1044d3ac
|
|
||||||
# 公网知识库 ID 保留登记,便于切回时对照;生产下不应被业务引用
|
|
||||||
challenge-cup: b342f096-98b1-4a83-a296-f4ab60a6db0e
|
|
||||||
gxmu-constitution: aa72c649-71a0-46e7-ac55-47f44b397a4e
|
|
||||||
|
|||||||
@@ -105,70 +105,77 @@ wxpay:
|
|||||||
|
|
||||||
# Dify 平台对接
|
# Dify 平台对接
|
||||||
#
|
#
|
||||||
# 注意这里有两套环境,Key 与知识库 ID 互不通用(实测:文档给的 Key 打到 82.156.98.71 上全部 401):
|
# 唯一的实例:内网 http://10.100.158.15/v1(见《Dify API 文档》)。
|
||||||
# 1) 公网实例 http://82.156.98.71/v1 —— 当前线上在用的实例,下列默认值即取自它
|
#
|
||||||
# 2) 内网实例 http://10.100.158.15/v1 —— 见《Dify API 文档》,已由服务器实测 8 个应用全部 200
|
# 2026-10-12 完成迁移:此前代码连的是公网实例 http://82.156.98.71/v1,
|
||||||
# 切到内网实例时,在 application-prod.yml 覆盖 base-url 与全部 apps/dataset 密钥即可。
|
# 因开发机连不上内网而一直保留双份配置;VPN 打通后已全部并入内网。
|
||||||
# 不要直接改这里的默认值,否则本地开发会一起被切走。
|
#
|
||||||
|
# 两套实例的应用 Key、知识库 ID、密钥完全不通用(实测互打一律 401),
|
||||||
|
# 因此公网那套配置已整体移除,遗留一份只会造成误用。若确需回退,
|
||||||
|
# 到 git 历史取本段即可,不要再在配置里维护两套。
|
||||||
dify:
|
dify:
|
||||||
base-url: http://82.156.98.71/v1
|
base-url: http://10.100.158.15/v1
|
||||||
timeout-ms: 600000
|
timeout-ms: 600000
|
||||||
apps:
|
apps:
|
||||||
# 医科大团委智能知识库问答系统
|
# 医科大团委智能知识库问答系统
|
||||||
chat: app-ou0qPesouiopaPpmxcFV2gFG
|
chat: app-ZLUUbLkAX3FVzDubY06puxAo
|
||||||
# 活动策划大纲生成
|
# 活动策划大纲生成(advanced-chat,走 /chat-messages)
|
||||||
activity-outline: app-hp4PJknbejImmdDuMfziVcnN
|
activity-outline: app-4oBWB3HrdNlANdPFG8ZvVTkY
|
||||||
# AI 内容创作与宣传助手
|
# AI 内容创作与宣传助手
|
||||||
manuscript: app-hCzkdd6OsnMnwI2dQByLyNIN
|
manuscript: app-fhhz4fXMkEdVivAEiuWpycn6
|
||||||
# AI 创意助手
|
# AI 创意助手
|
||||||
creative-assistant: app-l3accBEJLDAh9Pn8fLW8nNJ0
|
creative-assistant: app-lAbpxMggNbIrByiDRDfckSSl
|
||||||
# 项目书质量初审
|
# 项目书质量初审
|
||||||
project-quality-review: app-HiI2FrNjcwL0A7QOOf7GIP50
|
project-quality-review: app-J6h5I6Bo4oeBif2EofBGVrGs
|
||||||
# 竞赛文件预处理
|
# 竞赛文件预处理
|
||||||
competition-history-preprocess: app-bhyQLMAw2JEDXMTs2mzNRiIU
|
competition-history-preprocess: app-UzVLKnGwg69uAUdbgFx6XNmc
|
||||||
# 医科大智能文档处理系统
|
# 医科大智能文档处理系统
|
||||||
# 公网实例上该 Key 无效(实测 401),仅内网实例可用,故允许环境变量覆盖
|
|
||||||
document-process: ${DIFY_KEY_DOCUMENT_PROCESS:app-ohdIhk0lVfUwoy10xXRebmjD}
|
document-process: ${DIFY_KEY_DOCUMENT_PROCESS:app-ohdIhk0lVfUwoy10xXRebmjD}
|
||||||
# 申报书生成,同上
|
# 申报书生成
|
||||||
declaration: ${DIFY_KEY_DECLARATION:app-k20aDmiQYh3LxqZSLuX8wjkU}
|
declaration: ${DIFY_KEY_DECLARATION:app-k20aDmiQYh3LxqZSLuX8wjkU}
|
||||||
dataset:
|
dataset:
|
||||||
# 知识库列表等不区分知识库的接口所用密钥
|
# 知识库列表等不区分知识库的接口所用密钥。
|
||||||
api-key: ${DIFY_DATASET_API_KEY:dataset-1n6MECRtQvEVGOL1o7SrSGfe}
|
# 实测该密钥对实例内全部知识库均可用,keys 里逐库登记仅为将来分权留口子。
|
||||||
|
api-key: ${DIFY_DATASET_API_KEY:dataset-jaqtnAQvYcQTEjECzxhQfNCv}
|
||||||
# 知识库 ID -> 独立密钥
|
# 知识库 ID -> 独立密钥
|
||||||
keys:
|
keys:
|
||||||
b342f096-98b1-4a83-a296-f4ab60a6db0e: ${DIFY_DATASET_API_KEY:dataset-1n6MECRtQvEVGOL1o7SrSGfe}
|
|
||||||
aa72c649-71a0-46e7-ac55-47f44b397a4e: ${DIFY_DATASET_API_KEY:dataset-1n6MECRtQvEVGOL1o7SrSGfe}
|
|
||||||
# 内网实例的两个知识库各自签发独立密钥
|
|
||||||
181d58e3-78c2-4b7d-939c-57e780a8f7d6: ${DIFY_DATASET_KEY_COMPETITION:dataset-jaqtnAQvYcQTEjECzxhQfNCv}
|
181d58e3-78c2-4b7d-939c-57e780a8f7d6: ${DIFY_DATASET_KEY_COMPETITION:dataset-jaqtnAQvYcQTEjECzxhQfNCv}
|
||||||
3e0ac108-3a0d-41e2-8447-aa1c1044d3ac: ${DIFY_DATASET_KEY_GXMU:dataset-f2a0Hjxix7lBcPNZtNMyKDTQ}
|
3e0ac108-3a0d-41e2-8447-aa1c1044d3ac: ${DIFY_DATASET_KEY_GXMU:dataset-f2a0Hjxix7lBcPNZtNMyKDTQ}
|
||||||
# 业务代号 -> 知识库 ID
|
# 业务代号 -> 知识库 ID
|
||||||
ids:
|
ids:
|
||||||
# 公网实例现用:挑战杯历史案例(5 篇)
|
# 竞赛案例(145 篇)。admin 端「竞赛历史」文档列表接口按此代号取库。
|
||||||
challenge-cup: b342f096-98b1-4a83-a296-f4ab60a6db0e
|
|
||||||
gxmu-constitution: aa72c649-71a0-46e7-ac55-47f44b397a4e
|
|
||||||
# 内网实例:竞赛案例(146 篇)、GXMU 团章(10 篇)
|
|
||||||
competition-cases: 181d58e3-78c2-4b7d-939c-57e780a8f7d6
|
competition-cases: 181d58e3-78c2-4b7d-939c-57e780a8f7d6
|
||||||
gxmu-constitution-internal: 3e0ac108-3a0d-41e2-8447-aa1c1044d3ac
|
# GXMU 团章(10 篇)
|
||||||
|
gxmu-constitution: 3e0ac108-3a0d-41e2-8447-aa1c1044d3ac
|
||||||
# 工作流变量名映射。
|
# 工作流变量名映射。
|
||||||
# 左边是入参实体的业务字段(中划线),右边是 Dify 工作流开始节点的真实变量名。
|
# 左边是入参实体的业务字段(中划线),右边是 Dify 工作流开始节点的真实变量名。
|
||||||
#
|
#
|
||||||
# 下两段取自 2026-09-19 在内网实例上跑 probe-dify.sh 的实际输出,
|
# 以下取自 2026-10-12 在内网实例上重跑 probe-dify.sh 的实际输出。
|
||||||
# 注意它们与公网实例的命名习惯并不一致 —— 不要凭直觉推断,必须以探针输出为准。
|
# 该实例的变量名会随工作流编排调整而漂移,所以每次动工作流都要以探针输出为准,
|
||||||
|
# 不要凭直觉推断。
|
||||||
|
#
|
||||||
|
# 映射为空串 = 丢弃该字段(见 DifyProperties.VariableMapping 的约定),
|
||||||
|
# 下面的失效变量即用此法显式剔除:它们已不在开始节点上,
|
||||||
|
# 留着只会让人误以为仍在生效。注意不要直接删掉整行 —— 那会退回"按原名透传",
|
||||||
|
# 反而把 pdfUrl / parseValue 这种驼峰名发给 Dify。
|
||||||
workflows:
|
workflows:
|
||||||
# 内网实测: query,pdf_url,pdf_file(文件类)
|
# 内网实测仅 pdf_file(文件类)。query/pdf_url 已从开始节点移除,故丢弃;
|
||||||
|
# 实体里仍保留这两个字段,是为了不破坏 /documentProcess/upload 的入参契约
|
||||||
|
# (前端仍可传,只是不再转发给 Dify)。
|
||||||
document-process:
|
document-process:
|
||||||
fields:
|
fields:
|
||||||
query: query
|
query: ""
|
||||||
pdf-url: pdf_url
|
pdf-url: ""
|
||||||
pdf-file: pdf_file
|
pdf-file: pdf_file
|
||||||
# 内网实测: core_idea,tech_route,team_intro,upload_file(文件类),parse_value
|
# 内网实测 core_idea,tech_route,team_intro,upload_file(文件类)。
|
||||||
|
# parse_value 已从开始节点移除,故丢弃。
|
||||||
declaration:
|
declaration:
|
||||||
fields:
|
fields:
|
||||||
core-idea: core_idea
|
core-idea: core_idea
|
||||||
tech-route: tech_route
|
tech-route: tech_route
|
||||||
team-intro: team_intro
|
team-intro: team_intro
|
||||||
upload-file: upload_file
|
upload-file: upload_file
|
||||||
parse-value: parse_value
|
parse-value: ""
|
||||||
|
|
||||||
# 医科大开放平台对接
|
# 医科大开放平台对接
|
||||||
gxmu:
|
gxmu:
|
||||||
|
|||||||
@@ -11,16 +11,21 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验生产 profile 的 Dify 覆盖是否完整。
|
* 校验生产 profile 下的 Dify 配置。
|
||||||
*
|
*
|
||||||
* <p>背景:Dify 内网实例与公网实例的应用 Key、知识库 ID、密钥<b>完全不通用</b>。
|
* <p>背景:2026-10-12 迁移前,application.yml 默认指向公网实例,由 application-prod.yml
|
||||||
* 切换时若漏覆盖某一项,运行期会表现为该功能单独报 401/参数错误,
|
* 整段覆盖为内网;那时"漏覆盖一项"会在运行期表现为该功能单独 401/404,
|
||||||
* 而 Spring 的松散绑定不会给出任何警告 —— 这类"漏一项"极难在测试环境发现。
|
* 而 Spring 的松散绑定不会给出任何警告,因此本测试逐项断言覆盖完整性。
|
||||||
* 因此这里逐项断言 prod 覆盖的完整性。
|
*
|
||||||
|
* <p>迁移后默认值本身即内网实例,prod 不再覆盖 dify(见 application.yml 的 dify 段注释)。
|
||||||
|
* 于是本测试的职责转为:<b>确认生产解析出的确实是内网实例、且不残留任何公网配置</b>。
|
||||||
|
* 这仍能挡住两类回归:有人重新往 prod 里加一份半截覆盖(YAML map 按键合并,
|
||||||
|
* 会与默认值混出"一半内网一半公网"的状态),或有人把默认值改回公网。
|
||||||
*
|
*
|
||||||
* <p>本测试不启动容器,只做 application.yml + application-prod.yml 的绑定,
|
* <p>本测试不启动容器,只做 application.yml + application-prod.yml 的绑定,
|
||||||
* 不依赖数据库与 Redis。
|
* 不依赖数据库与 Redis。
|
||||||
@@ -61,11 +66,11 @@ class DifyProdProfileBindingTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void 生产环境八个应用Key全部覆盖为内网Key() throws Exception {
|
void 生产环境八个应用Key全部为内网Key() throws Exception {
|
||||||
DifyProperties.AppKeys apps = loadProd().getApps();
|
DifyProperties.AppKeys apps = loadProd().getApps();
|
||||||
assertNotNull(apps);
|
assertNotNull(apps);
|
||||||
|
|
||||||
// 逐个断言:任一为 null 或仍是公网 Key 都说明覆盖漏了
|
// 逐个断言:任一为 null 或仍是公网 Key 都说明配置错了
|
||||||
assertEquals("app-ZLUUbLkAX3FVzDubY06puxAo", apps.getChat(), "chat");
|
assertEquals("app-ZLUUbLkAX3FVzDubY06puxAo", apps.getChat(), "chat");
|
||||||
assertEquals("app-4oBWB3HrdNlANdPFG8ZvVTkY", apps.getActivityOutline(), "activity-outline");
|
assertEquals("app-4oBWB3HrdNlANdPFG8ZvVTkY", apps.getActivityOutline(), "activity-outline");
|
||||||
assertEquals("app-ohdIhk0lVfUwoy10xXRebmjD", apps.getDocumentProcess(), "document-process");
|
assertEquals("app-ohdIhk0lVfUwoy10xXRebmjD", apps.getDocumentProcess(), "document-process");
|
||||||
@@ -101,13 +106,13 @@ class DifyProdProfileBindingTest {
|
|||||||
actual.add(apps.getDeclaration());
|
actual.add(apps.getDeclaration());
|
||||||
|
|
||||||
for (String pub : publicKeys) {
|
for (String pub : publicKeys) {
|
||||||
assertTrue(!actual.contains(pub),
|
assertFalse(actual.contains(pub),
|
||||||
"生产环境残留公网 Key: " + pub + ",会导致该功能 401");
|
"生产环境残留公网 Key: " + pub + ",会导致该功能 401");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void 生产环境知识库密钥指向内网() throws Exception {
|
void 生产环境知识库密钥全部指向内网() throws Exception {
|
||||||
DifyProperties.Dataset dataset = loadProd().getDataset();
|
DifyProperties.Dataset dataset = loadProd().getDataset();
|
||||||
|
|
||||||
assertEquals("dataset-jaqtnAQvYcQTEjECzxhQfNCv", dataset.getApiKey());
|
assertEquals("dataset-jaqtnAQvYcQTEjECzxhQfNCv", dataset.getApiKey());
|
||||||
@@ -118,16 +123,14 @@ class DifyProdProfileBindingTest {
|
|||||||
assertEquals("dataset-f2a0Hjxix7lBcPNZtNMyKDTQ",
|
assertEquals("dataset-f2a0Hjxix7lBcPNZtNMyKDTQ",
|
||||||
dataset.keyOf("3e0ac108-3a0d-41e2-8447-aa1c1044d3ac"));
|
dataset.keyOf("3e0ac108-3a0d-41e2-8447-aa1c1044d3ac"));
|
||||||
|
|
||||||
// 公网知识库 ID 仍登记,但生产下必须也指到内网密钥,不能残留公网密钥
|
// 明确断言:生产下任何知识库 ID 都不得解析出公网密钥。
|
||||||
assertEquals("dataset-jaqtnAQvYcQTEjECzxhQfNCv",
|
// 涵盖登记过的 ID 与未登记的回退路径(回退走 api-key,也已断言为内网密钥)。
|
||||||
dataset.keyOf("b342f096-98b1-4a83-a296-f4ab60a6db0e"),
|
|
||||||
"公网库 ID 在生产残留了公网密钥,属死密钥,应显式覆盖为内网密钥");
|
|
||||||
|
|
||||||
// 明确断言:生产下任何知识库 ID 都不得解析出公网密钥
|
|
||||||
for (String id : dataset.getKeys().keySet()) {
|
for (String id : dataset.getKeys().keySet()) {
|
||||||
assertTrue(!"dataset-1n6MECRtQvEVGOL1o7SrSGfe".equals(dataset.keyOf(id)),
|
assertFalse("dataset-1n6MECRtQvEVGOL1o7SrSGfe".equals(dataset.keyOf(id)),
|
||||||
"生产环境知识库 " + id + " 残留公网密钥 dataset-1n6MECRt...");
|
"生产环境知识库 " + id + " 残留公网密钥 dataset-1n6MECRt...");
|
||||||
}
|
}
|
||||||
|
assertFalse("dataset-1n6MECRtQvEVGOL1o7SrSGfe".equals(dataset.keyOf("unregistered-id")),
|
||||||
|
"生产环境未登记知识库的回退密钥是公网密钥");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -135,13 +138,17 @@ class DifyProdProfileBindingTest {
|
|||||||
DifyProperties.Dataset dataset = loadProd().getDataset();
|
DifyProperties.Dataset dataset = loadProd().getDataset();
|
||||||
|
|
||||||
assertEquals("181d58e3-78c2-4b7d-939c-57e780a8f7d6", dataset.idOf("competition-cases"));
|
assertEquals("181d58e3-78c2-4b7d-939c-57e780a8f7d6", dataset.idOf("competition-cases"));
|
||||||
assertEquals("3e0ac108-3a0d-41e2-8447-aa1c1044d3ac",
|
assertEquals("3e0ac108-3a0d-41e2-8447-aa1c1044d3ac", dataset.idOf("gxmu-constitution"));
|
||||||
dataset.idOf("gxmu-constitution-internal"));
|
|
||||||
|
// 公网知识库代号不应再登记:admin 端「竞赛历史」接口曾按 challenge-cup 取到
|
||||||
|
// 一个两边都 404 的死 ID,2026-10-12 已改指 competition-cases,这里钉住防回归
|
||||||
|
assertFalse(dataset.getIds().containsKey("challenge-cup"),
|
||||||
|
"生产环境仍登记公网知识库代号 challenge-cup");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void 生产环境保留工作流变量映射() throws Exception {
|
void 生产环境保留工作流变量映射() throws Exception {
|
||||||
// profile 覆盖了 base-url 与密钥,但不应把 workflows 映射冲掉
|
// profile 不再覆盖 dify,但 workflows 映射必须仍然可读到
|
||||||
DifyProperties.WorkflowVariables workflows = loadProd().getWorkflows();
|
DifyProperties.WorkflowVariables workflows = loadProd().getWorkflows();
|
||||||
assertNotNull(workflows);
|
assertNotNull(workflows);
|
||||||
|
|
||||||
@@ -150,8 +157,9 @@ class DifyProdProfileBindingTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void 本地默认仍指向公网实例() throws Exception {
|
void 生产与开发共用内网默认值() throws Exception {
|
||||||
// application.yml 默认值不得被改成内网,否则本地开发连不上
|
// application.yml 默认值即内网实例:本地开发与生产同一套地址,
|
||||||
|
// 这是 2026-10-12 VPN 打通后作出的决定(原先默认公网、prod 覆盖内网)
|
||||||
StandardEnvironment environment = new StandardEnvironment();
|
StandardEnvironment environment = new StandardEnvironment();
|
||||||
for (PropertySource<?> source : new YamlPropertySourceLoader()
|
for (PropertySource<?> source : new YamlPropertySourceLoader()
|
||||||
.load("application", new ClassPathResource("application.yml"))) {
|
.load("application", new ClassPathResource("application.yml"))) {
|
||||||
@@ -159,8 +167,21 @@ class DifyProdProfileBindingTest {
|
|||||||
}
|
}
|
||||||
DifyProperties properties = Binder.get(environment).bind("dify", DifyProperties.class).orElse(null);
|
DifyProperties properties = Binder.get(environment).bind("dify", DifyProperties.class).orElse(null);
|
||||||
assertNotNull(properties);
|
assertNotNull(properties);
|
||||||
assertEquals("http://82.156.98.71/v1", properties.getBaseUrl(),
|
assertEquals("http://10.100.158.15/v1", properties.getBaseUrl(),
|
||||||
"application.yml 默认值应保持公网,避免本地开发被切到内网");
|
"application.yml 默认值应为内网实例");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void 生产环境不重复覆盖dify() throws Exception {
|
||||||
|
// prod 里若再次出现 dify 段,会与默认值按键合并成混合状态。
|
||||||
|
// 这条用"prod 单独加载后 dify 为空"来钉住该约定。
|
||||||
|
StandardEnvironment environment = new StandardEnvironment();
|
||||||
|
for (PropertySource<?> source : new YamlPropertySourceLoader()
|
||||||
|
.load("application-prod", new ClassPathResource("application-prod.yml"))) {
|
||||||
|
environment.getPropertySources().addLast(source);
|
||||||
|
}
|
||||||
|
assertTrue(environment.getProperty("dify.base-url") == null,
|
||||||
|
"application-prod.yml 又加回了 dify 覆盖,请改 application.yml 的默认值");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import org.springframework.core.env.StandardEnvironment;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
@@ -36,18 +37,19 @@ class DifyPropertiesBindingTest {
|
|||||||
DifyProperties properties = load();
|
DifyProperties properties = load();
|
||||||
assertNotNull(properties, "dify 配置未绑定,请检查 application.yml 缩进");
|
assertNotNull(properties, "dify 配置未绑定,请检查 application.yml 缩进");
|
||||||
|
|
||||||
assertEquals("http://82.156.98.71/v1", properties.getBaseUrl());
|
// 2026-10-12 迁移:默认值即内网实例,开发与生产共用
|
||||||
|
assertEquals("http://10.100.158.15/v1", properties.getBaseUrl());
|
||||||
assertEquals(600000, properties.getTimeoutMs());
|
assertEquals(600000, properties.getTimeoutMs());
|
||||||
|
|
||||||
DifyProperties.AppKeys apps = properties.getApps();
|
DifyProperties.AppKeys apps = properties.getApps();
|
||||||
assertNotNull(apps);
|
assertNotNull(apps);
|
||||||
assertEquals("app-ou0qPesouiopaPpmxcFV2gFG", apps.getChat());
|
assertEquals("app-ZLUUbLkAX3FVzDubY06puxAo", apps.getChat());
|
||||||
assertEquals("app-hp4PJknbejImmdDuMfziVcnN", apps.getActivityOutline());
|
assertEquals("app-4oBWB3HrdNlANdPFG8ZvVTkY", apps.getActivityOutline());
|
||||||
assertEquals("app-hCzkdd6OsnMnwI2dQByLyNIN", apps.getManuscript());
|
assertEquals("app-fhhz4fXMkEdVivAEiuWpycn6", apps.getManuscript());
|
||||||
assertEquals("app-l3accBEJLDAh9Pn8fLW8nNJ0", apps.getCreativeAssistant());
|
assertEquals("app-lAbpxMggNbIrByiDRDfckSSl", apps.getCreativeAssistant());
|
||||||
assertEquals("app-HiI2FrNjcwL0A7QOOf7GIP50", apps.getProjectQualityReview());
|
assertEquals("app-J6h5I6Bo4oeBif2EofBGVrGs", apps.getProjectQualityReview());
|
||||||
assertEquals("app-bhyQLMAw2JEDXMTs2mzNRiIU", apps.getCompetitionHistoryPreprocess());
|
assertEquals("app-UzVLKnGwg69uAUdbgFx6XNmc", apps.getCompetitionHistoryPreprocess());
|
||||||
// 新增的两个应用在 yml 里用了环境变量占位,需确认占位符被解析
|
// 这两个应用在 yml 里用了环境变量占位,需确认占位符被解析
|
||||||
assertEquals("app-ohdIhk0lVfUwoy10xXRebmjD", apps.getDocumentProcess());
|
assertEquals("app-ohdIhk0lVfUwoy10xXRebmjD", apps.getDocumentProcess());
|
||||||
assertEquals("app-k20aDmiQYh3LxqZSLuX8wjkU", apps.getDeclaration());
|
assertEquals("app-k20aDmiQYh3LxqZSLuX8wjkU", apps.getDeclaration());
|
||||||
}
|
}
|
||||||
@@ -56,7 +58,7 @@ class DifyPropertiesBindingTest {
|
|||||||
void 按代号取密钥可用() throws Exception {
|
void 按代号取密钥可用() throws Exception {
|
||||||
DifyProperties.AppKeys apps = load().getApps();
|
DifyProperties.AppKeys apps = load().getApps();
|
||||||
|
|
||||||
assertEquals("app-ou0qPesouiopaPpmxcFV2gFG", apps.byCode("chat"));
|
assertEquals("app-ZLUUbLkAX3FVzDubY06puxAo", apps.byCode("chat"));
|
||||||
assertEquals("app-k20aDmiQYh3LxqZSLuX8wjkU", apps.byCode("declaration"));
|
assertEquals("app-k20aDmiQYh3LxqZSLuX8wjkU", apps.byCode("declaration"));
|
||||||
assertEquals("app-ohdIhk0lVfUwoy10xXRebmjD", apps.byCode("documentProcess"));
|
assertEquals("app-ohdIhk0lVfUwoy10xXRebmjD", apps.byCode("documentProcess"));
|
||||||
// 未登记的代号返回 null,而不是静默回退到某个应用
|
// 未登记的代号返回 null,而不是静默回退到某个应用
|
||||||
@@ -68,14 +70,16 @@ class DifyPropertiesBindingTest {
|
|||||||
void 知识库配置绑定正确() throws Exception {
|
void 知识库配置绑定正确() throws Exception {
|
||||||
DifyProperties.Dataset dataset = load().getDataset();
|
DifyProperties.Dataset dataset = load().getDataset();
|
||||||
|
|
||||||
assertEquals("dataset-1n6MECRtQvEVGOL1o7SrSGfe", dataset.getApiKey());
|
assertEquals("dataset-jaqtnAQvYcQTEjECzxhQfNCv", dataset.getApiKey());
|
||||||
assertEquals("b342f096-98b1-4a83-a296-f4ab60a6db0e", dataset.idOf("challenge-cup"));
|
assertEquals("181d58e3-78c2-4b7d-939c-57e780a8f7d6", dataset.idOf("competition-cases"));
|
||||||
assertEquals("aa72c649-71a0-46e7-ac55-47f44b397a4e", dataset.idOf("gxmu-constitution"));
|
assertEquals("3e0ac108-3a0d-41e2-8447-aa1c1044d3ac", dataset.idOf("gxmu-constitution"));
|
||||||
// 已登记的库返回自己的密钥
|
// 已登记的库返回自己的密钥
|
||||||
assertEquals("dataset-1n6MECRtQvEVGOL1o7SrSGfe",
|
assertEquals("dataset-jaqtnAQvYcQTEjECzxhQfNCv",
|
||||||
dataset.keyOf("b342f096-98b1-4a83-a296-f4ab60a6db0e"));
|
dataset.keyOf("181d58e3-78c2-4b7d-939c-57e780a8f7d6"));
|
||||||
|
assertEquals("dataset-f2a0Hjxix7lBcPNZtNMyKDTQ",
|
||||||
|
dataset.keyOf("3e0ac108-3a0d-41e2-8447-aa1c1044d3ac"));
|
||||||
// 未登记的库回退到默认密钥
|
// 未登记的库回退到默认密钥
|
||||||
assertEquals("dataset-1n6MECRtQvEVGOL1o7SrSGfe", dataset.keyOf("unknown-id"));
|
assertEquals("dataset-jaqtnAQvYcQTEjECzxhQfNCv", dataset.keyOf("unknown-id"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -88,21 +92,27 @@ class DifyPropertiesBindingTest {
|
|||||||
assertEquals("tech_route", declaration.variableOf("tech-route"));
|
assertEquals("tech_route", declaration.variableOf("tech-route"));
|
||||||
assertEquals("team_intro", declaration.variableOf("team-intro"));
|
assertEquals("team_intro", declaration.variableOf("team-intro"));
|
||||||
assertEquals("upload_file", declaration.variableOf("upload-file"));
|
assertEquals("upload_file", declaration.variableOf("upload-file"));
|
||||||
assertEquals("parse_value", declaration.variableOf("parse-value"));
|
// parse_value 已从开始节点移除,配置成空串表示丢弃。
|
||||||
|
// 这里必须断言为 "" 而不是 null/原名 —— remapInputs 只把"空串"当作丢弃,
|
||||||
|
// 若配成 null 就会退回按原名透传,把 parseValue 发给 Dify。
|
||||||
|
assertEquals("", declaration.variableOf("parse-value"));
|
||||||
// 未登记的字段按原名透传
|
// 未登记的字段按原名透传
|
||||||
assertEquals("unmapped", declaration.variableOf("unmapped"));
|
assertEquals("unmapped", declaration.variableOf("unmapped"));
|
||||||
|
|
||||||
DifyProperties.VariableMapping documentProcess = workflows.getDocumentProcess();
|
DifyProperties.VariableMapping documentProcess = workflows.getDocumentProcess();
|
||||||
assertEquals("query", documentProcess.variableOf("query"));
|
|
||||||
assertEquals("pdf_url", documentProcess.variableOf("pdf-url"));
|
|
||||||
assertEquals("pdf_file", documentProcess.variableOf("pdf-file"));
|
assertEquals("pdf_file", documentProcess.variableOf("pdf-file"));
|
||||||
|
// 同理:query / pdf_url 已移除,空串丢弃
|
||||||
|
assertEquals("", documentProcess.variableOf("query"));
|
||||||
|
assertEquals("", documentProcess.variableOf("pdf-url"));
|
||||||
|
// 驼峰入参也必须命中同一条丢弃规则(variableOf 内部转中划线再查)
|
||||||
|
assertEquals("", documentProcess.variableOf("pdfUrl"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void 知识库登记了公网与内网两套() throws Exception {
|
void 知识库只登记内网实例的库() throws Exception {
|
||||||
DifyProperties.Dataset dataset = load().getDataset();
|
DifyProperties.Dataset dataset = load().getDataset();
|
||||||
List<String> ids = new java.util.ArrayList<>(dataset.getIds().keySet());
|
List<String> ids = new java.util.ArrayList<>(dataset.getIds().keySet());
|
||||||
assertEquals(4, ids.size(), "dify.dataset.ids 应登记 4 个知识库,实际: " + ids);
|
assertEquals(2, ids.size(), "dify.dataset.ids 应只登记内网 2 个知识库,实际: " + ids);
|
||||||
|
|
||||||
// 内网实例的两个库各自有独立密钥,不能都回退到默认密钥
|
// 内网实例的两个库各自有独立密钥,不能都回退到默认密钥
|
||||||
assertEquals("dataset-jaqtnAQvYcQTEjECzxhQfNCv",
|
assertEquals("dataset-jaqtnAQvYcQTEjECzxhQfNCv",
|
||||||
@@ -111,4 +121,44 @@ class DifyPropertiesBindingTest {
|
|||||||
dataset.keyOf("3e0ac108-3a0d-41e2-8447-aa1c1044d3ac"));
|
dataset.keyOf("3e0ac108-3a0d-41e2-8447-aa1c1044d3ac"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void 不残留公网实例的任何配置() throws Exception {
|
||||||
|
DifyProperties properties = load();
|
||||||
|
|
||||||
|
// 地址与 Key 都不应再出现公网实例的痕迹
|
||||||
|
assertFalse(properties.getBaseUrl().contains("82.156.98.71"),
|
||||||
|
"base-url 仍指向公网实例");
|
||||||
|
|
||||||
|
List<String> publicKeys = new java.util.ArrayList<>();
|
||||||
|
publicKeys.add("app-ou0qPesouiopaPpmxcFV2gFG");
|
||||||
|
publicKeys.add("app-hp4PJknbejImmdDuMfziVcnN");
|
||||||
|
publicKeys.add("app-hCzkdd6OsnMnwI2dQByLyNIN");
|
||||||
|
publicKeys.add("app-l3accBEJLDAh9Pn8fLW8nNJ0");
|
||||||
|
publicKeys.add("app-HiI2FrNjcwL0A7QOOf7GIP50");
|
||||||
|
publicKeys.add("app-bhyQLMAw2JEDXMTs2mzNRiIU");
|
||||||
|
|
||||||
|
DifyProperties.AppKeys apps = properties.getApps();
|
||||||
|
List<String> actual = new java.util.ArrayList<>();
|
||||||
|
actual.add(apps.getChat());
|
||||||
|
actual.add(apps.getActivityOutline());
|
||||||
|
actual.add(apps.getManuscript());
|
||||||
|
actual.add(apps.getCreativeAssistant());
|
||||||
|
actual.add(apps.getProjectQualityReview());
|
||||||
|
actual.add(apps.getCompetitionHistoryPreprocess());
|
||||||
|
actual.add(apps.getDocumentProcess());
|
||||||
|
actual.add(apps.getDeclaration());
|
||||||
|
|
||||||
|
for (String pub : publicKeys) {
|
||||||
|
assertFalse(actual.contains(pub), "残留公网应用 Key: " + pub);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 公网知识库 ID 与其密钥都不应再登记
|
||||||
|
assertFalse(properties.getDataset().getKeys()
|
||||||
|
.containsValue("dataset-1n6MECRtQvEVGOL1o7SrSGfe"),
|
||||||
|
"残留公网知识库密钥");
|
||||||
|
assertFalse(properties.getDataset().getIds()
|
||||||
|
.containsKey("challenge-cup"),
|
||||||
|
"仍登记公网知识库代号 challenge-cup");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,13 +23,15 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||||||
/**
|
/**
|
||||||
* 校验「申报书生成」「智能文档处理」的入参到 Dify inputs 的映射。
|
* 校验「申报书生成」「智能文档处理」的入参到 Dify inputs 的映射。
|
||||||
*
|
*
|
||||||
* <p>变量名以 2026-09-19 在内网实例 10.100.158.15 上跑 probe-dify.sh 的实测输出为准:
|
* <p>变量名以内网实例 10.100.158.15 上的 probe-dify.sh 实测输出为准。
|
||||||
|
* 2026-10-12 在 VPN 打通后重跑探针,发现该实例的变量名<b>已再次漂移</b>:
|
||||||
* <pre>
|
* <pre>
|
||||||
* document-process : query, pdf_url, pdf_file(文件类)
|
* document-process : pdf_file(文件类) —— query / pdf_url 已从开始节点移除
|
||||||
* declaration : core_idea, tech_route, team_intro, upload_file(文件类), parse_value
|
* declaration : core_idea, tech_route, team_intro, upload_file(文件类)
|
||||||
|
* —— parse_value 已移除
|
||||||
* </pre>
|
* </pre>
|
||||||
* 这两组名字与公网实例的命名习惯并不一致,因此这里既固定映射行为,
|
* 映射表仍保留这些旧键(Dify 对多传的变量静默忽略,不报错),
|
||||||
* 也直接从 application.yml 读配置来断言,防止后人"顺手改成看起来更合理"的名字。
|
* 但测试不再断言它们"必须是开始节点变量",只断言映射行为本身。
|
||||||
*/
|
*/
|
||||||
class AiInputMappingTest {
|
class AiInputMappingTest {
|
||||||
|
|
||||||
@@ -97,11 +99,13 @@ class AiInputMappingTest {
|
|||||||
assertEquals("做一个校园二手书平台", result.get("core_idea"));
|
assertEquals("做一个校园二手书平台", result.get("core_idea"));
|
||||||
assertEquals("Spring Boot + 小程序", result.get("tech_route"));
|
assertEquals("Spring Boot + 小程序", result.get("tech_route"));
|
||||||
assertEquals("3 人,均为计算机专业", result.get("team_intro"));
|
assertEquals("3 人,均为计算机专业", result.get("team_intro"));
|
||||||
assertEquals("true", result.get("parse_value"));
|
// parse_value 已从开始节点移除,配置成空串即丢弃:
|
||||||
|
// 既不能出现 parse_value,也不能退回驼峰原名 parseValue
|
||||||
|
assertFalse(result.containsKey("parse_value"), "失效变量 parse_value 不应下发");
|
||||||
|
assertFalse(result.containsKey("parseValue"));
|
||||||
// 不能残留业务字段名
|
// 不能残留业务字段名
|
||||||
assertFalse(result.containsKey("coreIdea"));
|
assertFalse(result.containsKey("coreIdea"));
|
||||||
assertFalse(result.containsKey("teamIntro"));
|
assertFalse(result.containsKey("teamIntro"));
|
||||||
assertFalse(result.containsKey("parseValue"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -137,11 +141,14 @@ class AiInputMappingTest {
|
|||||||
|
|
||||||
Map<String, Object> result = remap(objectToMap(inputs), mapping);
|
Map<String, Object> result = remap(objectToMap(inputs), mapping);
|
||||||
|
|
||||||
assertEquals("请解析并入库", result.get("query"));
|
// query / pdf_url 已从开始节点移除,配置成空串即丢弃
|
||||||
assertEquals("http://example.com/a.pdf", result.get("pdf_url"));
|
assertFalse(result.containsKey("query"), "失效变量 query 不应下发");
|
||||||
assertEquals(fileValue, result.get("pdf_file"));
|
assertFalse(result.containsKey("pdf_url"), "失效变量 pdf_url 不应下发");
|
||||||
|
// 丢弃不等于退回原名:驼峰名同样不能出现
|
||||||
assertFalse(result.containsKey("pdfUrl"));
|
assertFalse(result.containsKey("pdfUrl"));
|
||||||
assertFalse(result.containsKey("pdfFile"));
|
assertFalse(result.containsKey("pdfFile"));
|
||||||
|
// 唯一真实变量仍应正常映射
|
||||||
|
assertEquals(fileValue, result.get("pdf_file"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------- 通用行为
|
// ---------------------------------------------------------------- 通用行为
|
||||||
@@ -180,18 +187,24 @@ class AiInputMappingTest {
|
|||||||
void 映射配置覆盖了全部真实变量() throws Exception {
|
void 映射配置覆盖了全部真实变量() throws Exception {
|
||||||
DifyProperties properties = loadProperties();
|
DifyProperties properties = loadProperties();
|
||||||
|
|
||||||
// 内网探针实测出的变量全集,少一个就说明 yml 里漏配
|
// 2026-10-12 探针实测确认仍存在的开始节点变量,少一个就说明 yml 里漏配
|
||||||
Map<String, String> declaration = properties.getWorkflows().getDeclaration().getFields();
|
Map<String, String> declaration = properties.getWorkflows().getDeclaration().getFields();
|
||||||
assertTrue(declaration.containsValue("core_idea"), "缺少 core_idea");
|
assertTrue(declaration.containsValue("core_idea"), "缺少 core_idea");
|
||||||
assertTrue(declaration.containsValue("tech_route"), "缺少 tech_route");
|
assertTrue(declaration.containsValue("tech_route"), "缺少 tech_route");
|
||||||
assertTrue(declaration.containsValue("team_intro"), "缺少 team_intro");
|
assertTrue(declaration.containsValue("team_intro"), "缺少 team_intro");
|
||||||
assertTrue(declaration.containsValue("upload_file"), "缺少 upload_file");
|
assertTrue(declaration.containsValue("upload_file"), "缺少 upload_file");
|
||||||
assertTrue(declaration.containsValue("parse_value"), "缺少 parse_value");
|
|
||||||
|
|
||||||
Map<String, String> document = properties.getWorkflows().getDocumentProcess().getFields();
|
Map<String, String> document = properties.getWorkflows().getDocumentProcess().getFields();
|
||||||
assertTrue(document.containsValue("query"), "缺少 query");
|
|
||||||
assertTrue(document.containsValue("pdf_url"), "缺少 pdf_url");
|
|
||||||
assertTrue(document.containsValue("pdf_file"), "缺少 pdf_file");
|
assertTrue(document.containsValue("pdf_file"), "缺少 pdf_file");
|
||||||
|
|
||||||
|
// 已从开始节点移除的变量必须显式配置成空串(丢弃),而不是把整行删掉 ——
|
||||||
|
// 删掉会退回"按原名透传",把 pdfUrl / parseValue 之类的驼峰名发给 Dify。
|
||||||
|
assertTrue(declaration.containsKey("parse-value"), "parse-value 应配置为空串以显式丢弃");
|
||||||
|
assertEquals("", declaration.get("parse-value"));
|
||||||
|
assertTrue(document.containsKey("query"), "query 应配置为空串以显式丢弃");
|
||||||
|
assertEquals("", document.get("query"));
|
||||||
|
assertTrue(document.containsKey("pdf-url"), "pdf-url 应配置为空串以显式丢弃");
|
||||||
|
assertEquals("", document.get("pdf-url"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user