已有 JeecgBoot 企业后台想增加 AI 助理,真正困难的通常不是让模型调用接口,而是让 Agent 在不绕过租户隔离、原有权限和业务校验的前提下完成真实写操作。本文用“查询一个用户”和“冻结或解冻一个用户”两个动作,拆解一条最小、可审查的接入路径。
很多团队第一次尝试给 JeecgBoot 后台接入 AI 时,会从一个很自然的方案开始:
用户提出要求
-> 大模型理解意图
-> 把 JeecgBoot 接口当成工具
-> 模型生成参数并调用接口
只做查询时,这条链路很快就能跑通。用户可以直接问:
- “查一下用户
1024的资料。” - “这个账号现在是正常还是冻结状态?”
- “帮我找出当前租户下需要处理的用户。”
但当需求变成“把这个用户冻结”,问题就不再只是工具调用。
冻结账号会改变真实业务状态。系统必须知道 Agent 代表谁行动、当前属于哪个租户、操作人是否拥有原菜单权限、目标用户是否属于同一租户、批准的目标和状态有没有变化,以及重试是否会形成第二次业务意图。
所以,JeecgBoot 接入 AI 助理的核心问题不是:
如何让大模型调用 JeecgBoot API?
而是:
如何让 Agent 只触达明确开放的业务能力,同时继续由 JeecgBoot 决定这次操作最终能不能成立?
本文给出一条“一读一写”的最小推演。它不是 JeecgBoot 官方集成,也不代表上游采用或认可;它是一份基于公开代码边界整理的社区接入配方,用来帮助已有 JeecgBoot 项目评审自己的 Agent 接入方式。
一、不要把整个后台 API 一次性交给模型
一套 JeecgBoot 企业后台通常包含用户、角色、部门、租户、字典、工作流和大量业务模块。一个具体 AI 助理真正需要的能力,往往只占其中很小一部分。
例如,用户治理助手第一阶段只开放:
| 业务动作 | 原接口 | 原权限 | Agent 能力 |
|---|---|---|---|
| 查询用户详情 | GET /sys/user/queryById |
system:user:queryById |
system.user.read |
| 冻结或解冻用户 | PUT /sys/user/frozenBatch |
system:user:frozenBatch |
system.user.status.update |
它不应该顺便看到:
listAll这类可能绕过租户过滤的全量接口;- 批量删除用户;
- 重置或修改密码;
- 可以直接携带后台 Token 调用的原始管理接口。
这一步解决的是 Reach,也就是当前 Agent 最多可以触达什么。
Reach 不是最终权限。即使 system.user.status.update 已进入当前工具白名单,JeecgBoot 仍然要判断当前操作人是否有原权限、目标用户是否属于当前租户、状态变化是否允许成立。
二、正确拓扑不是“Agent 直连后台”
更稳妥的最小拓扑是:
AI 助理 / Agent
-> BailingHub 受治理路由
- 能力白名单
- 可信行动主体
- 风险与审批
- 参数快照
- 幂等与审计
-> JeecgBoot 薄适配层
- 验证签名
- 恢复租户上下文
- 复核原 Shiro 权限
- 校验操作人与目标用户的租户关系
- 执行前重新读取业务状态
-> JeecgBoot 原有 Service
这里的“薄适配层”不是另一套业务系统,也不应该复制 JeecgBoot 的用户、租户和权限逻辑。
它只负责把受治理任务安全地还原成一次原系统调用:验证来自控制面的可信上下文,恢复 JeecgBoot 所需的执行环境,然后复用原有 Service 完成最终业务判断和写入。
三、租户和操作主体不能由模型填写
一个危险但常见的接口设计是:
{
"tenant_id": 8,
"operator_user_id": 1001,
"target_user_id": 1024,
"status": 2
}
其中 target_user_id 和 status 可以是当前业务意图的一部分;但 tenant_id 和 operator_user_id 不能仅仅因为模型生成了格式正确的值,就被当成可信身份。
模型输出、提示词和普通工具参数都是不可信请求内容。可信主体应该来自已经认证的会话、服务端身份上下文或签名票据,并且不能被模型参数覆盖。
例如,可以在受信链路里固定主体格式:
<tenant_id>:<operator_user_id>
薄适配层从签名保护的主体头中读取这两个值,恢复 TenantContext,并在请求结束后清理上下文。模型只提交“要查询谁”或“要把谁改成什么状态”,不能选择自己代表哪个租户、哪个管理员行动。
四、恢复 TenantContext 之后,还要检查成员关系
只恢复租户 ID 还不够。
适配层至少要分别验证:
- 操作人是当前租户的有效成员;
- 目标用户也是当前租户的有效成员;
- 两条成员关系当前都处于正常状态;
- 目标不是应被特殊保护的系统管理员;
- 执行前重新读取目标用户,避免审批等待期间状态已经变化。
这里尤其要避免一种偷懒做法:只统计用户与租户的关系记录“是否存在”。
关系存在,并不能证明成员关系仍然有效。已经禁用、退出或状态异常的成员,不应因为数据库里还有一条关系记录就继续获得操作资格。
因此,租户隔离不是把一个 tenant_id 填进请求,也不是只在列表查询中增加过滤条件。对于 Agent 写操作,它必须在最终执行边界重新成立。
五、Agent 治理不能替代 JeecgBoot 原权限
BailingHub 可以声明当前路由允许 system.user.status.update,并要求高风险操作先审批;但它不应该重新实现 JeecgBoot 的菜单权限体系。
薄适配层仍需复核原权限:
查询用户 -> system:user:queryById
冻结用户 -> system:user:frozenBatch
这样做有两个直接价值。
第一,管理员在 JeecgBoot 中撤销权限后,Agent 链路也会随之失效,不需要在两个系统里维护一套重复 RBAC。
第二,BailingHub 只控制 Agent 的可触达范围和治理过程,不会成为最终业务授权方。原系统已有的角色、租户、对象状态和业务规则继续生效。
可以把责任边界浓缩为一句话:
BailingHub 管 Agent 最多能走到哪里,JeecgBoot 管这个主体此刻到底能不能做。
六、冻结用户为什么需要绑定参数的审批
“冻结用户”不是一个抽象按钮。一次审批至少应绑定:
- 当前租户;
- 可信操作主体;
- 目标用户;
- 目标状态,正常或冻结;
- 稳定任务标识;
- 审批有效期与适用策略版本。
假设审批者看到的是:
{
"tenant_id": 8,
"target_user_id": 1024,
"status": 2
}
审批通过后,如果 Agent 把目标改成 2048,或者把状态从“冻结”改成“解冻”,原审批必须失效。不能只因为工具名仍然是 system.user.status.update 就继续执行。
同样,审批等待期间如果出现以下变化,也应拒绝执行或重新审批:
- 操作人的原权限被撤销;
- 操作人或目标用户退出当前租户;
- 目标用户已经被删除或状态已变化;
- 审批已过期;
- 当前策略版本已经改变。
Human-in-the-loop 的价值不只是弹出一个“确认”按钮,而是把人看到并批准的精确业务意图,与最终执行的精确参数绑定起来。
七、HTTP 重试不能代替业务幂等
冻结请求可能已经在 JeecgBoot 中成功写入,但响应在返回途中丢失。此时上游看到超时,如果直接生成一个新请求再次执行,就可能把一次业务意图变成两次独立动作。
因此,写操作需要稳定的幂等键:
X-Bailing-Idempotency-Key: <stable-business-request-id>
同一次业务意图的重试必须复用同一个键;新的冻结或解冻请求必须使用新键。幂等结果应当保存在业务边界或薄适配层可恢复的位置,而不是只存在于模型上下文里。
还要注意,状态更新在数学上“重复写同一个值结果相同”,不代表整条业务链天然幂等。重复请求仍可能重复触发通知、审计、工作流或其他副作用。
八、最终写入仍然调用原 Service
通过审批、参数校验和幂等检查以后,适配层最终应调用 JeecgBoot 现有用户 Service,而不是绕过业务层直接更新数据库。
这样可以继续复用:
- 原用户状态定义;
- 原缓存清理;
- 原业务校验;
- 原事务与扩展逻辑;
- 原系统对最终状态的所有权。
控制面返回“审批通过”,不等于业务操作必然成功。执行时 JeecgBoot 如果发现权限、租户关系或目标状态已经不符合要求,仍然应该拒绝。
这不是两层重复校验,而是两层在回答不同问题:
控制面:这个 Agent 场景是否允许尝试这项能力?
业务系统:这个主体此刻是否有权对这个对象完成这项操作?
九、最小接入步骤
如果要在现有 JeecgBoot 项目中验证这条路径,可以按下面顺序推进:
- 先只选择一个查询动作和一个可恢复写动作;
- 在 JeecgBoot 服务层前增加薄适配接口,不把管理后台 Token 给模型;
- 为适配层配置独立 HMAC Secret;
- 从签名上下文恢复可信主体和租户,不接受模型覆盖;
- 复核原 Shiro 权限与双方租户成员关系;
- 把查询和冻结声明成两个独立 Agent 能力;
- 冻结或解冻操作要求审批、参数快照和稳定幂等键;
- 最终调用原
SysUserService,不直写数据库; - 用测试租户、测试账号完成负向验证后,再考虑扩大能力范围。
不要在第一阶段开放整个用户管理模块。先证明这一读一写的边界能够稳定成立,后续增加角色调整、部门变更或工作流动作时,才有可复用的基线。
十、上线前至少验证这十种情况
- 有查询权限的有效租户成员可以读取同租户测试用户;
- 无可信主体、错误签名和过期时间戳全部拒绝;
- 即使全局用户 ID 存在,也不能查询其他租户用户;
- 没有
system:user:frozenBatch权限的主体不能发起冻结; - 冻结任务在批准前不能触达 JeecgBoot 原服务;
- 批准后更换目标用户、租户或目标状态时必须重新审批;
- 同一幂等键重试不能形成第二个业务意图;
- 审批等待期间成员关系失效时必须拒绝;
- 系统管理员等受保护账号不能被冻结;
listAll、删除和密码类接口不出现在 Agent 工具清单中。
这份清单里,成功路径只占一小部分。真正决定方案能否进入生产的,是错误主体、跨租户、参数漂移、权限撤销和重复执行这些负向路径。
结语
JeecgBoot 接入 AI 助理,不需要推倒原有权限体系,也不应该让 Agent 绕过它。
更合理的演进方式是:Agent 平台负责理解任务,治理控制面限制能力范围、审批和审计,薄适配层恢复可信业务上下文,JeecgBoot 原服务继续持有租户隔离、原权限、业务状态和最终授权。
当这条边界成立以后,企业才是在“给现有后台增加 AI 助理”,而不是在原系统旁边再造一套由模型掌权的影子后台。
如果你的 JeecgBoot 项目也在接入 AI、MCP 或工作流,最值得先回答的问题是:
你准备开放的第一个真实写操作是什么?在最终写入前,哪一层会重新确认可信主体、租户、原权限和精确参数?
延伸阅读与可验证配方
上述 JeecgBoot 材料是独立社区配方,不是官方集成或上游背书。生产使用前,仍需按自己的 JeecgBoot 版本、租户模型和业务规则完成实现与验证。