BiSheng Gateway License 到期降级与到期提醒:从硬停服到可控降级的设计与实现 📅 发布时间:2026/9/16 18:59:27 👁 浏览次数: BiSheng Gateway License 到期降级与到期提醒从硬停服到可控降级的设计与实现【免费下载链接】bishengBISHENG is an open LLM devops platform for next generation Enterprise AI applications. Powerful and comprehensive features include: GenAI workflow, RAG, Agent, Unified model management, Evaluation, SFT, Dataset Management, Enterprise-level System Management, Observability and more.项目地址: https://gitcode.com/GitHub_Trending/bi/bisheng本文基于features/v2.6.0/037-gateway-license-expiry-reminder/下的 spec.md、design.md、tasks.md 编写并结合仓库内 Gateway 架构文档 与 platform 前端实际代码src/frontend/platform/src/layout/LicenseBanner.tsx、src/frontend/platform/src/controllers/API/license.ts、src/frontend/platform/src/controllers/request.ts进行源码级印证。导读本篇围绕 BiSheng 商业版 bisheng-gateway 的 License 生命周期管理展开v2.6.0 将「License 过期即System.exit(0)硬停服」改造为「降级模式」——只拦截网关自处理的付费增值接口SSO/OAuth2、敏感词、用户组、getkey 等透传 bisheng 后端的/api/v1、/api/v2照常放行同时新增GET /api/license/status状态接口与 platform 管理台顶部常驻 Banner让超管在 license 临近到期或已过期时第一时间感知并续期。读完本文你将掌握该功能的验收标准、五项关键设计决策、状态接口契约与 severity 等级映射、降级拦截的判定规则、前端 Banner 的渲染逻辑以及实现过程中踩过的七个反直觉「坑」。1. 背景Gateway 是什么License 为什么重要bisheng-gateway 是 BiSheng 的商业拓展套件作为 Java 网关层部署在前端与后端之间提供 SSO/OAuth 统一认证、内容安全审查敏感词过滤、流量控制限流/在线计数和 API 反向代理能力。它是一个独立的 Java 项目Spring Boot Spring Cloud Gateway 3.2.6 / 2023.0.1JDK 17Maven 构建拥有独立数据库gt_*前缀 4 张表与独立配置通过 HTTP 与 bisheng 后端通信详见 docs/architecture/11-gateway.md。关键拓扑PRO/商业版部署浏览器 → Nginx → 前端 (platform) → Gateway :8180 → Backend :7860开启网关模式后前端 Vite 的VITE_PROXY_TARGET从http://localhost:7860切换为http://localhost:8180所有/api/**请求先经过 Gateway 再代理到 Backend。路由分工如下路径处理方说明/api/oauth2/*Gateway 自处理SSO/OAuth 登录入口与回调/api/sensitive/*Gateway 自处理敏感词管理 CRUD/api/group/*gateway 侧Gateway 自处理用户组/资源组管理/api/getkeyGateway 自处理RSA 公钥获取前端密码加密用/api/v1/**Gateway 代理 → Backendv1 API 全部代理/api/v2/**Gateway 代理 → Backendv2 RPC API 全部代理/api/v1/chat/**、/api/v2/chat/**Gateway WebSocket 代理WebSocket 连接代理License 校验由LicenseLoader作为ApplicationRunnerorder1在 Spring Boot 启动阶段执行读取bisheng.license配置 → RSA 私钥解密得到 JSON{name, version, expireDay, finger}→ 检查versiontrial比较expireDay与当前日期过期则System.exit(0)pro永久有效直接放行解密失败打印错误日志并System.exit(0)。finger机器指纹字段虽在 JSON 中但当前运行时未做指纹校验。核心约束License 私钥只在 GatewayRSA 私钥硬编码在LicenseLoader.java后端与前端对 license 内容一无所知。任何「让前端/后端知道到期状态」的方案都必须由 Gateway 提供数据——这是整个 feature 设计的前提。2. 目标与非目标把「硬停服」改成「可控降级」2.1 目标让 license 到期从「Gateway 进程System.exit(0)硬停服」改为「降级模式 管理后台常驻 Banner 提示」降级模式只拦网关自处理的付费接口SSO/oauth2、敏感词、用户组、getkey 等网关增值能力透传 bisheng 后端的接口/api/v1、/api/v2照常放行——核心产品免费、不拦而非整个进程消失 / 全线 502。到期提醒Gateway 暴露 license 状态查询接口GET /api/license/statusplatform 管理台超级管理员登录后顶部常驻 Banner 按到期等级warning/critical/expired提示剩余天数或已过期文案。2.2 明确排除非目标排除项说明后端 license 模块 / Redis 缓存 / Gateway→后端 HMAC 上报采用简化方案状态由前端经 Gateway 直接获取design §3 决策 1pro永久版参与提醒pro 永久有效不展示 Banner开源版无 Gateway的 Banner开源版无 license 概念状态接口不可达即静默不展示License 续期 / 重新激活流程本期只读展示不做续期入口后端 license 校验语义后端本就对 license 无感不改动2.3 用户故事作为平台超级管理员license 临近到期或已到期时在管理后台显著看到剩余天数与到期提醒以便及时续期避免业务毫无征兆地中断。作为使用商业版的终端用户 / 调用方license 过期后系统不是直接「进程消失 / 全线 502」而是仅停用网关的付费能力、核心产品透传 bisheng照常可用且访问付费接口时给出明确的「授权已过期」提示错误码 11001以便快速定位是授权问题而非系统故障。3. 验收标准11 条 AC 全览3.1 到期降级GatewayID角色操作预期结果AC-01系统trial license 到期后 Gateway 运行Gateway 进程不退出进入降级模式AC-02终端用户降级模式下访问网关自处理的付费接口/api/oauth2、/api/sensitive、/api/group、/api/getkey等返回统一的「license 已过期」错误体错误码 11001 文案非 502 / 连接拒绝AC-03终端用户 / 管理员降级模式下访问透传 bisheng 后端的接口/api/v1/**、/api/v2/**、/api/license/status、静态 / 健康检查正常放行核心产品照常可用、管理员可登录后台AC-04系统license 解密失败或未配置与到期同样进入降级模式不System.exit状态标记为过期/未知3.2 状态查询与等级映射AC-05— 系统在每次 license 检查启动一次 每小时整点后把{version, expireDay, daysRemaining, severity, checkedAt}写入 Gateway 内存状态。AC-06— 前端请求 license 状态接口时系统返回当前内存状态含 severity。AC-07— 系统按剩余天数映射 severity30 → normal、(7,30] → warning、(0,7] → critical、≤0 → expired、pro / 无 license / 状态缺失 → unknown。3.3 前端 BannerplatformID角色操作预期结果AC-08超级管理员登录 platform 后台severity 为 warning/critical/expired 时顶部常驻 Banner 按等级上色展示剩余天数 / 已过期文案AC-09超级管理员severity 为 normal / unknown或状态接口不可达开源版/404不展示 Banner不报错AC-10非超级管理员含组/部门管理员、普通用户登录 platform 后台不展示 BannerAC-11任意角色查看 Banner 文案中 / 英 / 日三语言齐全4. 五项关键设计决策design §34.1 决策 1License 状态如何送达前端 Banner —— 砍掉后端模块备选 APRD 原方案Gateway 每小时 HMAC 上报后端 → 后端新建bisheng/license/模块 Redis 缓存license:status→ 前端读后端。PRD 误以为后端已有/api/v1/internal/sso/login-sync可复用HMAC 校验实测后端无此端点、无任何 HMAC 校验、settings 无gateway_hmac_secret改动面最大。备选 B选定Gateway 自处理一个 license 状态接口前端登录后直接经 Gateway 拿状态。后端、Redis、HMAC 全部不碰。选定 B 的原因PRO 模式下 platform 流量本就穿过 Gateway决策前已核实拓扑 Gateway 已有/api/getkey、/api/group/*、/api/sensitive/*等「自处理不转发」的 Controller 先例「前端经 Gateway 取状态」天然可行。代价Banner 状态依赖请求那一刻 Gateway 可达若 Gateway 彻底不可达真挂而非降级前端取不到状态、Banner 不展示——但此时整个产品入口也不通属另一类故障。若未来出现「Gateway 挂了也要在独立后台解释过期」的硬需求回到方案 A。4.2 决策 2severity 阈值计算放在哪 —— Gateway 算阈值是业务规则集中在 Gateway 一处避免散落前端前端只渲染。边界归属warning含 30critical含 7expired含 0即daysRemaining 0。4.3 决策 3license 配置异常 / 解密失败 —— 同样降级硬退出同样导致「全线不可达、无从解释」与本 feature 目标相悖。配置缺失视作无有效授权 → 进入降级、severity 记为expired无有效授权应拦网关付费接口。4.4 决策 4降级拦谁、放谁 —— 只拦网关付费接口透传 bisheng 放行备选 A最初理解已废弃拦所有业务路由/api/v1,v2只放行登录/状态/静态——即「活着但拒绝业务、放行登录」。备选 B选定只拦网关自处理的付费接口/api/oauth2、/api/sensitive、/api/group、/api/getkey等网关增值能力透传给 bisheng 后端的/api/v1、/api/v2照常放行/api/license/status始终放行。判定规则/api/*路径中凡不属于/api/v1、/api/v2代理且不是/api/license状态的即视为网关付费接口 → 拦其余一律放行。何时需重新考虑若未来把某些付费能力放进/api/v1代理族下需改判定规则不能再简单按前缀。该判定逻辑落地在LicenseExpiredGlobalFilter.isGatewayPaidEndpoint()。4.5 决策 5降级触发时机与 severity 对齐到期日当天即降级severity 映射里daysRemaining 0即expired红条但降级的expired标志最初取current.isAfter(expireDay)严格次日才降级导致到期日当天Banner 已红显 expired 但付费接口还没拦——自相矛盾。选定把降级expired标志改为daysRemaining 0含到期日当天与 severity 完全对齐LicenseLoader.checkDate()中boolean expired daysRemaining 0;。代价是比原System.exit语义早一天到期日当天即停付费功能而非次日——对 trial 可接受。5. 状态接口契约与 severity 映射5.1 数据流LicenseLoader.checkDate()启动 Order(1) ReloadTask 每小时 → 解密 license → 写内存状态 LicenseStatusHolder{version, expireDay, daysRemaining, severity, expired} → (a) LicenseExpiredGlobalFilter 读 expired 决定拦/放 → (b) LicenseController GET /api/license/status 下发状态 → platform LicenseBanner 渲染检查入口LicenseLoader.checkDate()启动ApplicationRunner Order(1)ReloadTask.java的Scheduled(cron0 0 * * * ?)每小时整点。改造点checkDate()每次跑都把结果写入LicenseStatusHolder不止过期时两处System.exit(0)trial 过期、配置异常改为置expiredtrue 记日志。5.2 状态接口契约 ——GET /api/license/statusGateway 自处理、不转发后端。响应示例{ version: trial, expireDay: 2026-09-15, daysRemaining: 27, severity: warning, checkedAt: 2026-06-24T10:00:0008:00 }字段类型说明versionstringtrial/proexpireDaystringyyyy-MM-ddtrial 到期日pro 可为 nulldaysRemainingint / null剩余天数pro 为 nullseveritystringnormal/warning/critical/expired/unknowncheckedAtstring ISO8601最近一次检查时间前端src/frontend/platform/src/controllers/API/license.ts中LicenseStatus接口与之对应gateway 侧通过全局SnakeCaseStrategyObjectMapper 输出 snake_case前端字段为expire_day、days_remaining、checked_atexport type LicenseSeverity normal | warning | critical | expired | unknown export interface LicenseStatus { version: string | null expire_day: string | null days_remaining: number | null severity: LicenseSeverity expired: boolean checked_at: string | null }5.3 severity 映射daysRemainingseverity前端表现 30normal不展示 Banner(7, 30]含 30warning黄色 Banner显示剩余天数(0, 7]含 7critical红色 Banner≤ 0含 0expired红色置顶 Banner「已过期」文案pro / 无 license / 状态缺失unknown不展示 Banner5.4 降级错误体付费接口被拦沿用 Gateway 现有响应风格HTTP 200 业务错误码参照PathRateGlobalFilter限流返回{ data: , status_code: 11001, status_message: 软件授权已过期请联系管理员续期 }前端在 request.ts 中已内置对status_code 11001的全局兜底处理feature 037节流 toast 提示license.expired文案后Promise.reject(null)避免调用方再重复弹窗。此前置位于全局错误处理链中早于 403/404 跳转逻辑。6. 降级拦截的判定规则过期时只拦网关自处理的付费接口/api/*中不属于/api/v1、/api/v2代理透传且不是/api/license状态的路径——如/api/oauth2、/api/sensitive、/api/group、/api/getkey。放行清单/api/v1/**、/api/v2/**透传 bisheng 核心免费不拦/api/license/status降级模式下必须放行否则 Banner 取不到「为什么过期」——它虽是网关自处理路径但被显式排除在拦截之外非/api/路径静态资源 / actuator 健康检查重要实现细节Spring Cloud Gateway 的GlobalFilter只对代理路由生效看不到网关自处理的RestController端点getkey/sensitive/group/license 由 DispatcherHandler 直接分发不进 gateway 过滤链。若用 GlobalFilter 实现付费 controller 接口过期时根本拦不住返 200 而非 11001。因此LicenseExpiredGlobalFilter必须实现WebFilter包住整个 WebFlux 链代理路由 controller 都过order 取高优先级更早拦截。响应短路风格参照PathRateGlobalFilter。7. 关键模块职责划分模块 / 文件职责不做什么LicenseLoader.java改解密 license、算 daysRemaining/severity、写 Holder、置 expired不再System.exitLicenseStatusHolder.java新Component持有最近一次 license 状态线程安全volatile/AtomicReference不做解密LicenseController.java新RestControllerGET /api/license/status读 Holder 下发不暴露解密内容 / 私钥LicenseExpiredGlobalFilter.java新WebFilter Orderedexpired 时拦网关自处理付费接口、放行透传(/api/v1,v2)与/api/license不做限流 / 鉴权platformcontrollers/API/license.ts新GET/api/license/status404/错误吞掉返 null不直接 import axiosplatformLicenseBanner新超管可见按 severity 渲染不算阈值Gateway 现有可复用先例自处理 ControllerSensitiveWordsController/UserGroupControllerRestController路径/api/sensitive/*、/api/group/*不转发后端——LicenseController仿此。路由注意点/api/v1/**、/api/v2/**默认代理后端自处理路径需确保不落入代理/api/license/*不在/api/v1前缀下天然由 Controller 接管。8. 前端 Banner源码级实现前端实现已落地于仓库可在src/frontend/platform/src/layout/下直接查看8.1 状态拉取 —— license.tsexport async function getLicenseStatus(): PromiseLicenseStatus | null { try { return await axios.get(/api/license/status, { silent: true } as any) } catch { return null } }关键点silent: true抑制全局错误 toast / 重定向任何失败开源版无 Gateway 时 404、网络异常直接返回nullBanner 静默隐藏不报错。8.2 Banner 渲染 —— LicenseBanner.tsxconst SEVERITY_STYLE: Recordstring, string { warning: bg-amber-50 text-amber-800 border-amber-200 dark:bg-amber-950/40 dark:text-amber-300 dark:border-amber-900, critical: bg-red-50 text-red-700 border-red-200 dark:bg-red-950/40 dark:text-red-300 dark:border-red-900, expired: bg-red-50 text-red-700 border-red-200 dark:bg-red-950/40 dark:text-red-300 dark:border-red-900, }; const VISIBLE_SEVERITIES [warning, critical, expired];只有warning/critical/expired渲染 Bannernormal/unknown/null不可达渲染为空。critical与expired共享红色系warning为琥珀色。Banner 可见时将自身渲染高度发布为 CSS 自定义属性--license-banner-h挂在:root上供calc(100vh - Npx)类视口高度计算扣除隐藏时置0px使减法成为空操作。MainLayout.tsx的导航最大高度即使用var(--license-banner-h, 0px)参与计算。文案通过useTranslation走 i18nt(license.expired)已过期与t(license.warning/critical, { days })剩余天数满足 AC-11 三语言要求。8.3 超管可见性 —— MainLayout.tsxconst isSuperAdmin useMemo(() user.role admin, [user]) // ... {isSuperAdmin LicenseBanner /}Banner 仅在user.role admin超级管理员时挂载天然满足 AC-10非超管不展示组件自身再叠加 severity 过滤满足 AC-08 / AC-09。9. 已知坑与反直觉事实design §5#反直觉事实如果不知道会怎样在哪处理1PRD 说后端有/api/v1/internal/sso/login-sync的 HMAC 校验可复用——后端实际不存在该端点、无任何 HMAC 校验、settings 无gateway_hmac_secret照 PRD 走会发现「复用」无从复用被迫从零写后端 HMAC简化方案直接绕开后端不需要 HMAC2Gateway license 状态接口必须放在/api/v1/**之外如/api/license/*否则会被默认路由代理到后端而非 Gateway 自处理接口被转发到后端 → 后端无此路由 → 404LicenseController用/api/license/*路径3状态接口必须排除在降级拦截外过期后 Banner 取不到状态反而看不到过期提示LicenseExpiredGlobalFilter.isGatewayPaidEndpoint()显式放行/api/license4checkDate()当前仅在过期时动作平时不记状态warning/critical未过期阶段 Holder 为空 → Banner 拿不到剩余天数checkDate()每次跑都写 Holder5platform 前端在开源版直连后端、无 Gateway/api/license/status会 404若不吞错控制台报错 / 弹窗license.ts对非 2xx / 空响应返 nullBanner 静默6降级只拦网关付费接口不能拦透传 bisheng 的/api/v1,v2拦错了会把免费核心也停掉与「付费的才是网关」相悖普通用户聊天等会全断isGatewayPaidEndpoint()对/api/v1,v2返 false7Spring Cloud Gateway 的GlobalFilter只对代理路由生效看不到网关自处理的RestController端点用 GlobalFilter 实现会发现付费 controller 接口过期时根本拦不住返 200 不返 11001必须用WebFilter包住整个 WebFlux 链代理路由 controller 都过——LicenseExpiredGlobalFilter implements WebFilter10. 对外契约与依赖10.1 我提供给别人的Outgoing契约形式谁在用GET /api/license/statusHTTP APIGateway 自处理platformLicenseBanner降级错误体{status_code, status_message}HTTP 响应终端用户 / 调用方过期时10.2 我依赖别人的Incoming依赖形式风险点PRO 模式 platform 流量穿过 Gateway部署拓扑若某部署让 platform 直连后端Banner 取不到状态需回方案 ALicenseLoader解密结果{version, expireDay}Gateway 内部license JSON 字段名变更会影响 daysRemaining 计算10.3 边界情况Gateway 多实例时各实例按自身内存状态各自应答license 检查逻辑天然每实例独立无需共享。severity warning含daysRemaining恰为 30critical含恰为 7expired含恰为 0。pro 版本daysRemaining为空前端不渲染 Banner。11. 测试与可观测design §7Gateway 单元测试daysRemaining/severity 映射含 30/7/0 边界以及 31/30/8/7/1/0/-1、pro、null 等取值LicenseExpiredGlobalFilter—— 网关付费接口oauth2/sensitive/group/getkey被拦 vs 透传/api/v1,v2及/api/license放行配置异常进入降级而非退出mockBishengConfig.getLicense返坏值断言不抛/不退出、holder.expiredtrue。手动验证构造一张已过期 trial license或改系统时间 / mockLicenseLoader启动 Gateway → 确认进程不退出、网关付费接口返 11001 错误体、透传/api/v1,v2与/api/license/status正常放行超管登录 platform 看到红色置顶 Banner改成 20 天后到期看黄色 warning。前端Banner 按 severity 渲染快照非超管 / normal / 404 不渲染。可观测降级触发、上次检查结果打 INFO 日志含 daysRemaining/severity。12. 实施拆解四个 Wave、九个任务详见 tasks.md分为可并行的四个 WaveWave任务内容覆盖 ACWave 1无依赖可并行T001LicenseStatusHolder severity 映射ComponentAtomicReference/volatile线程安全AC-05, AC-07Wave 1T002前端 i18n 文案en-US/zh-Hans/ja三语言 license 提醒 keyAC-11Wave 2依赖 T001T003LicenseLoader改造写状态 去掉两处System.exit(0)配置异常 severity 记expiredAC-01, AC-04, AC-05Wave 2T004LicenseControllerGET /api/license/status路径在/api/v1之外AC-06Wave 2T005LicenseExpiredGlobalFilterisGatewayPaidEndpoint()判定拦/放order 取高优先级AC-02, AC-03Wave 3T006Gateway 单元测试severity 边界、过滤器白名单、配置异常降级AC-01~AC-04, AC-07Wave 3T007前端license.tscontrollergetLicenseStatus()非 2xx/404 → nullAC-09Wave 3T008LicenseBanner组件 MainLayout接入仅超管可见按 severity 渲染i18n 文案AC-08~AC-11Wave 4T009端到端手动验证过期进程不退出、11001 错误体、透传放行、红/黄 Banner、非超管不展示AC-01~AC-11说明Gateway 改动在独立仓库bisheng-gatewayJava / Maven前端在 bisheng 仓库platform需分别提交后端 Python 零改动简化方案。Gateway 侧采用 Test-First前端以手动验证为主。13. 后续改进 / 明确不做的方向不做后端缓存兜底Gateway 彻底挂时无法解释过期——出现硬需求再回方案 A。不做续期入口 / license 在线激活。不做pro 版到期提醒pro 永久有效暂无意义。相关文档索引规格与验收标准spec.md设计真相决策、契约、坑design.md执行与落档tasks.mdGateway 架构总览docs/architecture/11-gateway.md前端实现证据LicenseBanner.tsx、license.ts、MainLayout.tsx、request.ts【免费下载链接】bishengBISHENG is an open LLM devops platform for next generation Enterprise AI applications. Powerful and comprehensive features include: GenAI workflow, RAG, Agent, Unified model management, Evaluation, SFT, Dataset Management, Enterprise-level System Management, Observability and more.项目地址: https://gitcode.com/GitHub_Trending/bi/bisheng创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考