具名业务后台与受控命令集成
tio-boot-admin-react 可以作为独立管理端,复用成熟的布局、表单、表格和路由能力;tio-boot-admin 负责框架账号登录,业务服务进一步关联具名自然人、当前资源权限与领域命令。这样的组合便于让多个业务模块共享登录与操作体验,同时保留每个模块的事实来源。
1. 从框架账号到业务身份
后台账号登录后取得框架凭证,再通过业务会话交换接口检查账号与自然人映射。返回业务会话时只提供需要的身份、权限与会话引用。不要把系统通用令牌当作具名审批人员。
推荐顺序为:账号登录 → 业务身份交换 → 当前授权 → 读取具体事项 → 受控命令。框架登录与业务会话可以具有独立的空闲期限、绝对期限和并发限制,便于按业务要求管理高权限工作台。
2. 同源代理与部署
管理端统一请求 /api/。开发代理将接口转发到本地后端,部署环境则由反向代理转发同一路径,避免在页面中固化运行地址。
import { defineConfig } from '@umijs/max';
export default defineConfig({
proxy: {
'/api/': {
target: process.env.TIO_BOOT_ADMIN_BACKEND_URL || 'http://127.0.0.1:10518',
changeOrigin: true,
},
},
mock: false,
});
后台可以使用浏览器标签页的 sessionStorage 保存业务会话。不同标签页按自己的登录处理;响应返回时检查当前会话是否仍是发起请求的会话,防止旧账号响应混入新账号界面。
3. 可复用请求组件
管理模板提供 controlledRequest.mjs,处理 code: 1 或 code: 200 的业务响应、Bearer 凭证、请求超时和会话切换。它保留 HTTP 状态与业务错误码,网络异常、服务端临时失败及不可解析响应统一视为结果待核,不自动重试写操作。
import {
controlledRequest,
isDefiniteRejection,
} from '@/utils/controlledRequest.mjs';
export async function createProposal(executorId: string, evidenceRef: string) {
const token = sessionStorage.getItem('business-session') || '';
const pending = {
operationKey: crypto.randomUUID(),
executorId,
evidenceRef,
};
// 实际工程应将键限定到当前具名账号和业务域,并先检查已有待核操作。
const storageKey = 'proposal-pending';
if (sessionStorage.getItem(storageKey)) {
throw new Error('请先查询原操作');
}
sessionStorage.setItem(storageKey, JSON.stringify(pending));
try {
const result = await controlledRequest('/api/business/admin/propose', pending, {
token,
desktop: window.innerWidth >= 1024,
isCurrent: () => sessionStorage.getItem('business-session') === token,
});
if (result && typeof result === 'object' && 'status' in result && result.status === 'SUCCEEDED') {
sessionStorage.removeItem(storageKey);
}
return result;
} catch (error) {
// 这里只处理首次发送。已发生过超时的重试失败不能覆盖更早原操作事实。
if (isDefiniteRejection(error)) {
sessionStorage.removeItem(storageKey);
}
throw error;
}
}
恢复查询使用原 operationKey 和命令类型。服务端查询原操作时,先重新核验当前登录及资源权限,再返回原结果引用。NOT_FOUND 表示当前尚未找到原记录,前端保留原编号;需要重试时发送同一份请求,不能悄悄生成新编号。
4. 独立批准与实际执行
对现金退款、权益授予和库存启用等业务,将提案、批准、指定执行、通道终局分别保存。批准人应与提案人和执行人满足业务要求的自然人独立性;同一人的两个账号不能形成双人审批。实际执行前复核批准有效期、对象版本、当前授权及利益冲突。
前端表格应分别展示批准进度和实际执行时间。原命令处理成功不一定代表外部退款成功;通道结果未知时继续显示待核,保留原执行号。
读接口可以用短事务完成会话续期与授权读取,支持一个页面并发加载多个工作区。涉及计量、库存和现金的写入仍由领域服务使用对象锁、数据库约束、版本与幂等校验保证。
5. 固定查询与最小授权
按具体对象限制读取范围,返回显式字段白名单。以联系核验为例,管理端只需要供给编号、原操作编号、来源、揭示事实及发生时间;联系值、加密正文和摘要无需进入通用表格。每次敏感用途读取记录具名操作者与对象引用。
以下示例展示受控授权流程已经核验人员与范围后,如何建立一条有明确期限的对象授权。调用者仍须在所在管理流程中验证授权资格。
package example.admin;
import java.sql.Timestamp;
import java.time.Instant;
import nexus.io.db.activerecord.Db;
import nexus.io.tio.utils.environment.EnvUtils;
import nexus.io.tio.utils.snowflake.SnowflakeIdUtils;
public final class ContactAuditGrant {
private ContactAuditGrant() {
}
public static long grant(long adminIdentityId, long postId, Instant now) {
int seconds = EnvUtils.getInt("contact.audit.grant.seconds", 0);
if (seconds <= 0) {
throw new IllegalStateException("请先配置经核验的授权期限");
}
long id = SnowflakeIdUtils.id();
Db.update("""
insert into mw_permission_grant(
id, admin_identity_id, permission_code, scope_type,
scope_key, valid_from, valid_until
) values (?, ?, 'CONTACT_AUDIT', 'POST', ?, ?, ?)
""", id, adminIdentityId, Long.toString(postId),
Timestamp.from(now), Timestamp.from(now.plusSeconds(seconds)));
return id;
}
}
这里的业务表是应用自定义示例,不是框架自动创建的通用授权表。业务接口在事务中核验有效期、撤销状态和对象范围,前端按钮只是交互辅助。
6. 子目录使用与验证
模板的安装准备脚本仅在自身包含 Git 元数据时安装提交钩子。作为更大项目的子目录使用时,安装过程保留父仓库的钩子设置,便于复用模板并独立构建。
请求组件提供可直接通过 Node 测试运行器执行的用例,覆盖成功响应、精确字符串编号、超时、响应丢失、服务端临时失败、会话切换和权限拒绝:
pnpm run test:controlled-request
业务项目还应结合隔离数据库验证双自然人审批、撤权、并发最后单位、原操作恢复,并通过浏览器检查桌面办理与窄屏只读。构建通过、合成通道通过和真实渠道验收分别记录。
