← 返回差标管理

TravelMind 差标引擎 API 文档

版本: 1.0.0

基础 URL: https://www.trvaelmind.online/api/agent

更新: 2026-07-15 (P7 文档落地)

---

通用约定

鉴权

白名单鉴权 (requireAdmin 中间件): 5 个写端点 + 1 个只读 audit 端点 = 6 个需要鉴权

• 鉴权方式: userId 参数 (在 body 或 query)
• 白名单来源 (按优先级): TRAVELMIND_ADMIN_USERS 环境变量 → approvalStore._trips 中所有 userId 兜底
• 默认白名单 (兜底): 15 人, 包含 E1001/E1002/E1003 等
• 不带 userId: HTTP 403 { success: false, error: "Forbidden" }
• 不在白名单: HTTP 403

通用响应格式


{

  "success": true,

  "data": { ... }

}

错误响应:


{

  "success": false,

  "error": "错误描述",

  "message": "补充说明 (可选)"

}

---

端点列表 (12 个)

一、公开端点 (3 个, 无需鉴权)

1. POST `/policy/check`

用途: 老版 PolicyAgent, 调用 policyAgent.execute(). 用于旧前端表单兼容性, 新代码请用 /policy/check-now

请求体:


{

  "destination": "北京",

  "userLevel": "M4",

  "days": 2,

  "estimatedBudget": 1500

}

响应示例:


{

  "success": true,

  "data": {

    "compliant": true,

    "allowedTransportTypes": ["flight", "train", "bus"],

    "maxAllowedPrice": { "flight": 1500, "train": 800, "bus": 300 },

    "allowedClasses": { "flight": "economy", "train": "second" }

  }

}

错误码: 500 内部异常

---

2. GET `/policy/standard`

用途: 获取目的地城市的差标标准 (PolicyAgent 兼容接口)

Query 参数:

destination (必需): 城市名, 如 "北京"

响应示例:


{

  "success": true,

  "data": {

    "compliant": true,

    "allowedTransportTypes": ["flight", "train", "bus"],

    "maxAllowedPrice": { "flight": 1500, "train": 800, "bus": 300 },

    "allowedClasses": { "flight": "economy", "train": "second" }

  }

}

---

3. POST `/policy/check-now` ⭐ 推荐

用途: 实时检查行程方案是否合规. 新代码都用这个

请求体:


{

  "plan": {

    "hotel": { "name": "北京汉庭", "price": 350 },

    "transport": [

      { "type": "train", "seatType": "二等座", "price": 200 }

    ],

    "destination": "北京"

  },

  "ctx": {

    "destination": "北京",

    "userLevel": "M4"

  }

}

响应示例 (合规):


{

  "success": true,

  "data": {

    "compliant": true,

    "violations": [],

    "limits": {

      "hotelDailyLimit": 600,

      "hotelLimitSource": "北京._default",

      "trainAllowedClass": "second",

      "mealSubsidyEstimate": 100,

      "mealSubsidyTier": "A"

    },

    "specialApprovalRequired": false,

    "degraded": false,

    "checkDurationMs": 0,

    "configVersion": "1.0.0",

    "checkedAt": "2026-07-15T10:46:33.951Z"

  }

}

响应示例 (违规):


{

  "success": true,

  "data": {

    "compliant": false,

    "violations": [

      {

        "rule": "hotel",

        "severity": "block",

        "message": "酒店每晚 ¥1500 超限 ¥600 (北京._default)",

        "suggestion": "建议选择每晚 ≤ ¥600 的酒店"

      }

    ],

    "specialApprovalRequired": true,

    "degraded": false,

    ...

  }

}

规则详解:

rule severity 触发 说明
hotel_price_missing block 酒店无价格 (price ≤ 0 / undefined)
hotel warn / block 超限 1.2 倍内=warn, 超 1.2 倍=block
train_class_unknown warn 席别名无法识别
train_class warn / block 超 1 级=warn, 超 2 级=block
flight_cabin warn / block 商务舱/头等舱=block
flight_discount warn 机票折扣超 limit

降级: 配置损坏 / 异常 → { degraded: true, compliant: true (放行) }, 不阻断主流程

---

二、只读端点 (1 个, 需鉴权)

4. GET `/policy/rules` ⚙️ 需鉴权

用途: 读取差标配置全量 JSON

Query 参数:

userId (必需): 白名单用户 ID

响应示例:


{

  "success": true,

  "data": {

    "rules": {

      "version": "1.0.0",

      "updatedAt": "2026-07-15",

      "cityTiers": {

        "A": ["北京", "上海", "广州", ...],

        "B": ["义乌", "温州", ...],

        "C": ["建德", "淳安", ...]

      },

      "hotel": {

        "_default_by_tier": { "A": 600, "B": 400, "C": 300 },

        "北京": { "_default": 600 },

        ...

      },

      "meal": { "_default_by_tier": { "A": 100, "B": 100, "C": 60 } },

      "taxi": { "perRideCap": 30, "dailyCap": 100, ... },

      "flight": { "discountCap": 0.6, "allowedCabin": "economy" },

      "train": { "allowedClass": "second" },

      "loan": { "formula": "hotel_daily * nights + transport_total" }

    }

  }

}

---

三、写入端点 (5 个, 需鉴权, 自动审计 + 冒烟测试 + 自动备份)

共同特性:

• 写前冒烟测试: 跑 15 个用例, regressionCount ≥ 1 → 拒收 (HTTP 409)
• 自动备份: 写到 config/backups/policy-rules.json.bak-{ISO timestamp}.json
• 自动审计: 写到 config/audit.log (JSONL, 含 before/after 完整快照)

5. POST `/policy/rules/hotel` ⚙️ 需鉴权

用途: 改酒店限价. 支持单城市覆盖 或 tier 默认

请求体:


{

  "userId": "E1001",

  "updates": {

    "上海": { "value": 555, "isCity": true },

    "tier_A": { "value": 650, "isTier": true }

  }

}

• key 为城市名 → 单城市覆盖 (如 "上海")
• key 以 tier_ 开头 → tier 默认 (A/B/C), 后端自动剥前缀

响应: { success: true, data: { message, smoke: { blockCount, totalBlock, results } } }

错误码:

• 400: 数值无效 (非数字 或 ≤ 0)
• 400: tier 不是 A/B/C
• 409: 冒烟测试拒收 (regressionCount ≥ 1)

---

6. POST `/policy/rules/meal` ⚙️ 需鉴权

用途: 改餐补

请求体:


{

  "userId": "E1001",

  "updates": {

    "tier_A": { "value": 120, "isTier": true }

  }

}

⚠️ 已知 Bug: P4 测试残留, 传 tier_* 报 "无效餐补". 临时方案: 直接改 JSON 文件 + 重启服务

---

7. POST `/policy/rules/flight` ⚙️ 需鉴权

用途: 改机票规则

请求体:


{

  "userId": "E1001",

  "updates": {

    "discountCap": 0.55,

    "allowedCabin": "premium"

  }

}

字段:

discountCap: 折扣上限 (0.6 = 6 折)
allowedCabin: economy / premium / business / first

---

8. POST `/policy/rules/train` ⚙️ 需鉴权

用途: 改火车规则

请求体:


{

  "userId": "E1001",

  "updates": {

    "allowedClass": "first"

  }

}

字段:

allowedClass: second (二等座) / first (一等座) / business (商务座)

---

9. POST `/policy/rules/cities` ⚙️ 需鉴权

用途: 增删城市分级 (A/B/C)

请求体:


{

  "userId": "E1001",

  "ops": [

    { "action": "add", "city": "南京", "tier": "A" },

    { "action": "remove", "city": "拉萨", "tier": "C" },

    { "action": "move", "city": "温州", "from": "B", "to": "A" }

  ]

}

action 类型:

add: 城市加到 tier (重复加 = 幂等 noop)
remove: 从 tier 移除
move: 从 from tier 移到 to tier

---

四、备份端点 (2 个, 需鉴权)

10. GET `/policy/rules/backups` ⚙️ 需鉴权

用途: 列出所有历史备份

Query 参数:

userId (必需)

响应:


{

  "success": true,

  "data": {

    "backups": [

      "policy-rules.json.bak-2026-07-15_10-46-33-951Z.json",

      "policy-rules.json.bak-2026-07-15_10-46-33-172Z.json",

      "policy-rules.json.bak-2026-07-15_10-46-32-711Z.json",

      "policy-rules.json.bak-2026-07-15_10-46-31-205Z.json",

      "policy-rules.json.bak-2026-07-15_cleanup-junk-keys.json"

    ]

  }

}

---

11. POST `/policy/rules/restore` ⚙️ 需鉴权

用途: 恢复到指定备份

请求体:


{

  "userId": "E1001",

  "backup": "policy-rules.json.bak-2026-07-15_10-46-33-951Z.json"

}

⚠️: backup 必须是 GET /backups 返回的完整文件名, 不支持 "latest" 简写

---

五、审计端点 (1 个, 需鉴权)

12. GET `/policy/audit` ⚙️ 需鉴权

用途: 查 admin 操作审计日志 (谁在什么时间改了什么)

Query 参数:

userId (必需)
?limit=50 (可选, 默认 50)
?action=/policy/rules/hotel (可选, 按 action 筛)
?userId=E1001userId 鉴权参数同位置 → 用 qUserId 区分

- 实际: 鉴权 userId 和查询 userId 共用 query, 故查其他人历史请用 action 过滤

响应:


{

  "success": true,

  "data": {

    "entries": [

      {

        "ts": "2026-07-15T10:44:54.552Z",

        "userId": "E1001",

        "action": "/policy/rules/train",

        "path": "/policy/rules/train",

        "before": { /* 完整旧配置 */ },

        "after": { /* 完整新配置 */ },

        "ip": "::ffff:127.0.0.1",

        "ua": "curl/7.81.0"

      }

    ],

    "total": 3,

    "stats": {

      "total": 15,

      "byUser": { "E1001": 8, "E1002": 5, "E1003": 2 },

      "byAction": {

        "/policy/rules/hotel": 4,

        "/policy/rules/train": 3,

        "/policy/rules/flight": 3,

        "/policy/rules/cities": 2,

        "/policy/rules/meal": 1,

        "/policy/rules/restore": 2

      }

    }

  }

}

---

完整流程示例

1. 业务人员发起一次酒店改价


# Step 1: 看现状

curl 'https://www.trvaelmind.online/api/agent/policy/rules?userId=E1001' | jq .data.rules.hotel



# Step 2: 改上海到 ¥555

curl -X POST 'https://www.trvaelmind.online/api/agent/policy/rules/hotel' \

  -H 'Content-Type: application/json' \

  -d '{"userId":"E1001","updates":{"上海":{"value":555}}}'



# Step 3: 验证生效

curl -X POST 'https://www.trvaelmind.online/api/agent/policy/check-now' \

  -H 'Content-Type: application/json' \

  -d '{"plan":{"hotel":{"name":"上海希尔顿","price":700},"transport":[],"destination":"上海"},"ctx":{"destination":"上海"}}'



# Step 4: 查审计

curl 'https://www.trvaelmind.online/api/agent/policy/audit?userId=E1001&limit=5'

2. 出问题: 恢复到上一次


# Step 1: 列出备份

curl 'https://www.trvaelmind.online/api/agent/policy/rules/backups?userId=E1001' | jq .data.backups



# Step 2: 选最近的备份恢复

BACKUP=$(curl -s 'https://www.trvaelmind.online/api/agent/policy/rules/backups?userId=E1001' | jq -r '.data.backups[0]')

curl -X POST 'https://www.trvaelmind.online/api/agent/policy/rules/restore' \

  -H 'Content-Type: application/json' \

  -d "{\"userId\":\"E1001\",\"backup\":\"$BACKUP\"}"

---

错误码参考

HTTP 含义 触发场景
200 成功 -
400 请求参数错 数值无效 / tier 不是 A/B/C
403 鉴权失败 不带 userId / 不在白名单
404 端点不存在 -
409 冒烟测试拒收 regressionCount ≥ 1
500 内部异常 配置文件损坏 / 写失败

---

数据存储

文件 路径 大小限制 切分策略
配置 config/policy-rules.json - 写前自动备份
备份 config/backups/policy-rules.json.bak-*.json 无上限 保留所有历史
审计 config/audit.log (JSONL) 5MB 超 5MB 切到 audit.log.{timestamp}

---

已知问题 (P7 文档落地时同步记录)

# 描述 临时方案 修复计划
1 meal 端点传 tier_* 报 "无效餐补" 直接改 JSON + 重启 P5 修 (合并 patchRules 同款容错)
2 restore 不支持 "latest" 简写 必须传完整文件名 P6: 加 alias
3 audit 查询参数 userId 与鉴权 userId 冲突 用 action 过滤查他人 P6: 改用 qUserId

---

文档维护: 改 policy 端点时同步更新本文件

位置: /var/www/travelmind/admin/api-docs.html (Web 版)

离线版: ~/.openclaw/workspace/policy-api-docs.md