Skip to content

具名业务后台与受控命令集成 ​

tio-boot-admin-react 可以作为独立管理端,复用成熟的布局、表单、表格和路由能力;tio-boot-admin 负责框架账号登录,业务服务进一步关联具名自然人、当前资源权限与领域命令。这样的组合便于让多个业务模块共享登录与操作体验,同时保留每个模块的事实来源。

1. 从框架账号到业务身份 ​

后台账号登录后取得框架凭证,再通过业务会话交换接口检查账号与自然人映射。返回业务会话时只提供需要的身份、权限与会话引用。不要把系统通用令牌当作具名审批人员。

推荐顺序为:账号登录 → 业务身份交换 → 当前授权 → 读取具体事项 → 受控命令。框架登录与业务会话可以具有独立的空闲期限、绝对期限和并发限制,便于按业务要求管理高权限工作台。

2. 同源代理与部署 ​

管理端统一请求 /api/。开发代理将接口转发到本地后端,部署环境则由反向代理转发同一路径,避免在页面中固化运行地址。

typescript
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 状态与业务错误码,网络异常、服务端临时失败及不可解析响应统一视为结果待核,不自动重试写操作。

typescript
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. 固定查询与最小授权 ​

按具体对象限制读取范围,返回显式字段白名单。以联系核验为例,管理端只需要供给编号、原操作编号、来源、揭示事实及发生时间;联系值、加密正文和摘要无需进入通用表格。每次敏感用途读取记录具名操作者与对象引用。

以下示例展示受控授权流程已经核验人员与范围后,如何建立一条有明确期限的对象授权。调用者仍须在所在管理流程中验证授权资格。

java
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 测试运行器执行的用例,覆盖成功响应、精确字符串编号、超时、响应丢失、服务端临时失败、会话切换和权限拒绝:

shell
pnpm run test:controlled-request

业务项目还应结合隔离数据库验证双自然人审批、撤权、并发最后单位、原操作恢复,并通过浏览器检查桌面办理与窄屏只读。构建通过、合成通道通过和真实渠道验收分别记录。

MIT Licensed