server: port: 8086 # 本地无 context-path(同 flow-center dev / Notice dev);生产经 CONTEXT_PATH=/form-center 注入, # Caddy 只反代 /form-center/api/* 与 /form-center/oapi/*,页面走前端静态。 # 端口/context-path 一律不写生产值:projman 用 SERVER_PORT、CONTEXT_PATH 环境变量注入(env 压过本文件)。 spring: application: name: form-center datasource: # 本地开发库;生产 DB 连接由 projman 平台供给(SPRING_DATASOURCE_* env 注入,本文件值被压过) url: jdbc:mysql://localhost:3306/bsch_formcenter?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver hikari: minimum-idle: 5 maximum-pool-size: 20 jpa: hibernate: # 开发期 update 自动建表;生产环境建议 none + 升级脚本(sql/ 目录,见 T2 起) ddl-auto: update show-sql: false open-in-view: false database-platform: org.hibernate.dialect.MySQL5InnoDBDialect properties: hibernate: jdbc: time_zone: Asia/Shanghai jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai # ===== 上传大小上限(T3 上传模块,问题单 P12 记档项)===== # 必须显式配:Spring Boot 默认 max-file-size=1MB,会把契约 §3 的 ≤50MB 附件在校验器之前就挡掉 # (报 MultipartException,用户只看到"上传失败")。两处上限与方案 §7.7 全局兜底同口径: # 单文件 ≤50MB;请求侧同样 50MB——前端逐文件上传(view-ui-plus 默认一次请求一个文件), # 不给"一个请求塞多个大文件"的放大面。生产若需调整由平台 env 覆盖,不写进 jar。 servlet: multipart: max-file-size: 50MB max-request-size: 50MB # ===== MQ 事件推送(契约 v2 §6.2,T17):全部键都从 RABBITMQ_* env 现读 ===== # 本地不启 broker:host 留空 → FcEventPublisher 判定"未配置"→ 发布整体 no-op(启动一条日志说明), # 服务照跑、提交链路照绿(V64)。**不在本文件写任何生产 host/账号**:projman 按环境注入 # RABBITMQ_HOST/PORT/USER/PASS/VHOST,哪天容器就绪哪天开,不阻塞任何批次(裁决 3「可选依赖」)。 rabbitmq: host: ${RABBITMQ_HOST:} port: ${RABBITMQ_PORT:5672} username: ${RABBITMQ_USER:} password: ${RABBITMQ_PASS:} virtual-host: ${RABBITMQ_VHOST:} # 关掉 Boot 的 RabbitAdmin(它在启动时就拨号声明拓扑:未配 broker 的环境会反复连接失败, # 实测把上下文启动拖到分钟级——"可选依赖"就名存实亡)。拓扑声明由 FcEventPublisher # 自己惰性做一次(exchangeDeclare durable topic,第一次真要发的时候),不依赖 admin。 dynamic: false # ===== v2 快照投递的两把可靠性闸(契约 answer-delivery-v2 §3.4,TASK_2 B.4)===== # correlated:逐条 confirm 回到 AnswerDeliveryService 的回调,只有它才写 broker_accepted_at; # 拿不到 confirm 就不算"总线收下",行留在 PENDING/FAILED 退避。 # 这两键对 v1 广播通道同样生效:v1 的消息没有 fc-delivery-id 头,回调按头缺席直接忽略。 publisher-confirm-type: ${RABBITMQ_PUBLISHER_CONFIRM_TYPE:correlated} publisher-returns: ${RABBITMQ_PUBLISHER_RETURNS:true} template: # mandatory=true + publisher-returns:路由不到目标私有队列(消费方队列没绑上=部署契约缺项) # 时必须回来,静默丢弃是最坏结局 mandatory: ${RABBITMQ_TEMPLATE_MANDATORY:true} # ===== 文件上传存储路径 ===== # 教训注释自 notice/service application.yml 原样抄入(2026-10 事故): # 仅供本地 Windows 开发兜底;生产读 FILE_PATH(平台统一,共享配置 application-platform.yml 一份 # /data/upload,子应用不再各自维护)。 # 切勿在生产只依赖本键:历史教训是 Linux 容器把 D:/data/upload # 锚定成 /app/D:/data/upload 写进容器可写层,每次更新部署附件全部丢失(2026-10)。 # 读取形态(T3 上传模块实现):${FILE_PATH:${FILE_UPLOAD_PATH:${file.upload-path:/data/upload}}} # —— FILE_PATH 由 projman 注入且持久卷固定挂 /data/upload,本键只在不注入 env 的本地生效,不可删。 file: upload-path: D:/data/upload # ===== 基础平台 yh 配置(flow-center/Notice/svc-hall 同款三件套)===== yh: auth: enabled: true sso: # 本地全链测试:指向 local-debug 的 CAS(8443 根路径,RSA+AJAX 登录页);生产由平台注入 # SSO_SERVER_URL_PREFIX / SSO_CLIENT_HOST_URL 覆盖(projman 按平台设置现算,本文件不要写生产值) server-url-prefix: http://localhost:8443 # 本地 dev 指向后端直连(脚本化登录 service=client-host-url+请求路径) client-host-url: http://localhost:8086 allow-headers: all allow-origins: all # 用户面与管理面全在 /api/* 下;本应用无匿名面。 # 业务方调用走 /oapi/*(appKey 签名,RequireAppSignFilter 强制 401) url-pattern: /api/* app-auth: enabled: true # 本应用自身身份(客户端签名用)——UserBase 注册后分配(进白名单),占位值上线前必须替换, # 生产由 projman 注入 YH_APP_AUTH_APP_KEY / APP_SECRET,禁止明文入库仓库。 # 本地占位 appKey=100021(svc-hall=100018 → flow-center=100019 → work-leave=100020 顺延); # 联调时需在 UserBase(及对端 trusted-apps)侧同步此占位对。 # 注意:占位 secret 必须带引号且含字母——裸 32 个 0 会被 YAML 按八进制整数解析成 "0",SignUtil 校验直接 500 app-key: 100021 app-secret: "form-center-dev-secret-100021" # 提供方强制验签(两级模型 §4.1):/oapi/** 无签名三件套即 401(starter 版 RequireAppSignFilter)。 # T1 暂无 /oapi 端点,声明保留(T4 对外读接口落地即用,不出现「先上线后端点无门」的空窗) server: enforce-patterns: [/oapi/*] # 中央密钥链(两级模型 §4.2):trusted-apps 先(本地确定性兜底),未命中走 SignKeyResolver # (Redis sign:app:* → Caffeine → UserBase 兜底端点)——任何在 UserBase 注册过的 appKey 自动可验 trust-registry: true # 入站验签白名单:只列本地需要直调本应用 /oapi 的对端,联调时按实际补充(号段同各应用本地占位)。 # 开 trust-registry 后此表仍保留为主兜底(Redis 停摆时白名单确定性工作) trusted-apps: "100018": "svc-hall-dev-secret-100018" "100019": "flow-center-dev-secret-100019" open: # 身份密钥唯一来源 = yh.app-auth(本文件 app-auth 段),open.* 副本不写; # base-host 是「服务目录三级兜底」的输入(userbase 目标 = base-host + 枚举 ctx /userbase): # 本地指向 local-debug 的 UserBase;生产由平台注入 YH_OPEN_BASE_HOST 覆盖(base.yunhaixingtong.com)。 # ⚠️ 绝不能把 services.userbase=http://localhost:8060 之类的本地覆盖写进本文件(同 flow-center 纪律): # yh.open.services.{key} 是目录第 2 级,平台注入的 base-host 只喂第 3 级兜底、压不过它 —— # 写进 jar 会让部署实例去连容器内的 localhost:8060(2026-09-18 事故:Connection refused → # 所有 /api/* 报 code=6 内部服务错误)。本地联调请运行时注入: # YH_OPEN_SERVICES_USERBASE=http://localhost:8060 mvn -o spring-boot:run base-host: http://localhost:8060 base-path: "" # ===== 表单中心专属配置 ===== form-center: # 本地开发跨端口 CORS:本应用 front dev(8089) 直调 /api/* 需带凭据的 CORS 头 # (CAS cookie 不隔离端口,免二次登录成立的前提);同主域生产置空即整体关闭 cors: allowed-origins: http://localhost:8089 # ===== hosted 填报(可信提交上下文,answer-delivery-v2 §6,TASK_2 A.2)===== # 键按**已验签的消费 appKey**取,浏览器传的任何 URL 都不采信:可嵌入 origin 决定被嵌页 # 只允许向哪个精确 origin 发 postMessage,回调基址是消费方确认入口(签发时锁定,作审计与 # 后续反向通知的唯一事实源)。缺键=签发直接失败(fail-closed,宁可不给入口也不给一个能被 # 任意父页面接走的入口)。生产由 env 注入,本文件的本地值仅用于本机联调。 # ⚠️ 本地 svc-hall 后端与 form-center 都写 8086(两应用不会同时本机起,联调时用 env 错开): # 跨应用联调请显式注入 FC_HOSTED_CALLBACK_URL_SVCHALL 指向 SH 后端实际端口。 hosted-fill: token-ttl-minutes: ${FC_HOSTED_TOKEN_TTL_MINUTES:30} embed-origin: "100018": ${FC_HOSTED_EMBED_ORIGIN_SVCHALL:http://localhost:8087} callback-url: "100018": ${FC_HOSTED_CALLBACK_URL_SVCHALL:http://localhost:8086/api/apply/intents} # ===== v2 快照投递(answer-delivery-v2 §3,TASK_2 B.1~B.6)===== # 持久性是 outbox 行本身(答卷事务内落库),这里只调"发送侧"的节奏与重试预算。 # source-app-key 默认取本应用签名身份(yh.app-auth.app-key),信封里的 sourceAppKey 由服务端给, # 不接受消费方申报。未配 broker 时行留在 PENDING、不丢事件(B.4)。 fc: answer: delivery: source-app-key: ${YH_APP_AUTH_APP_KEY:100021} scan-interval-ms: ${FC_DELIVERY_SCAN_INTERVAL_MS:10000} scan-initial-delay-ms: ${FC_DELIVERY_SCAN_INITIAL_DELAY_MS:30000} batch-size: ${FC_DELIVERY_BATCH_SIZE:50} # 有界重试:8 次×2 递增退避(30s→60s→…→封顶 1h),耗尽即 DEAD 留表待处置,不无限 requeue max-retries: ${FC_DELIVERY_MAX_RETRIES:8} backoff-base-seconds: ${FC_DELIVERY_BACKOFF_BASE_SECONDS:30} backoff-cap-seconds: ${FC_DELIVERY_BACKOFF_CAP_SECONDS:3600} logging: level: com.yunhai.formcenter: debug management: endpoints: web: exposure: include: health endpoint: health: show-details: never health: redis: # 引 data-redis 仅服务于 trust-registry 密钥链;关健康指标防 Redis 停摆拉 DOWN /actuator/health # (组合链 trusted-apps 先行兜底,Redis 不可用时验签仍确定性工作) enabled: false rabbit: # 同上一条先例(契约 v2 §6.2 裁决 3/V64):amqp 依赖是可选通道,RABBITMQ_HOST 未注入时 # 本地/未接 MQ 的环境不该被健康探针拖成 DOWN——发布器自身就是"未配置即 no-op"。 enabled: false