You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
837 lines
51 KiB
837 lines
51 KiB
# -*- coding: utf-8 -*-
|
|
"""bruno-sync 冷启动全量生成:crm-backend-matt 源码 → e:/code/crm-api-docs
|
|
按 SKILL.md Step 2 模板生成;grouping=tag,无 tag 接口回退 urlPattern。
|
|
"""
|
|
import json, os
|
|
|
|
WSROOT = r"e:\code\crm-api-docs" # Bruno 3 workspace 根(workspace.yml 所在)
|
|
ROOT = os.path.join(WSROOT, "collections", "crm-api-docs") # collection 根(bruno.json 所在)
|
|
SEQ = {}
|
|
|
|
import shutil
|
|
|
|
# Bruno 3 workspace 脚手架幂等确保(skill first-run 产出,已存在则不碰)
|
|
os.makedirs(os.path.join(WSROOT, "collections"), exist_ok=True)
|
|
|
|
def _ensure(rel, text):
|
|
_p = os.path.join(WSROOT, rel)
|
|
if not os.path.isfile(_p):
|
|
os.makedirs(os.path.dirname(_p), exist_ok=True)
|
|
with open(_p, "w", encoding="utf-8", newline="\n") as _f:
|
|
_f.write(text)
|
|
|
|
_ensure("workspace.yml",
|
|
'opencollection: 1.0.0\ninfo:\n name: "crm-api-docs"\n type: workspace\n\n'
|
|
'collections:\n - name: "crm-api-docs"\n path: "collections/crm-api-docs"\n')
|
|
_ensure("collections/crm-api-docs/bruno.json",
|
|
'{"version":"1","name":"crm-api-docs","type":"collection","ignore":["node_modules",".git"]}')
|
|
_ensure("collections/crm-api-docs/environments/local.bru",
|
|
"vars {\n baseUrl: http://localhost:8080\n token: \n}\n")
|
|
|
|
# 菜单化重组(ADR-0024)后清理旧 generated 文件夹,再全量重建
|
|
for _old in ("线索管理", "列偏好", "线索规则-公海池配置"):
|
|
_p = os.path.join(ROOT, _old)
|
|
if os.path.isdir(_p):
|
|
shutil.rmtree(_p)
|
|
|
|
def w(rel, text):
|
|
full = os.path.join(ROOT, rel.replace("/", os.sep))
|
|
os.makedirs(os.path.dirname(full), exist_ok=True)
|
|
with open(full, "w", encoding="utf-8", newline="\n") as f:
|
|
f.write(text)
|
|
|
|
def env(data=None, msg="success"):
|
|
d = {"code": 0, "success": True, "message": msg, "data": data}
|
|
return json.dumps(d, ensure_ascii=False, indent=2)
|
|
|
|
def table(headers, rows):
|
|
out = ["| " + " | ".join(headers) + " |", "|" + "|".join([" --- "] * len(headers)) + "|"]
|
|
out += ["| " + " | ".join(str(c) for c in r) + " |" for r in rows]
|
|
return "\n".join(out)
|
|
|
|
PH = ["参数", "类型", "必填", "默认", "说明"]
|
|
RH = ["字段", "类型", "说明"]
|
|
|
|
# ---------- 公共参数/结构 ----------
|
|
BASE_PARAM = [ # BaseParam
|
|
("current", "int", "否", "1", "当前页码,从 1 开始", "1"),
|
|
("size", "int", "否", "10", "每页条数,上限 500", "10"),
|
|
("keyword", "string", "否", "-", "模糊搜索关键字,匹配列由接口定义", "张"),
|
|
("orderBy", "string", "否", "-", "排序字段,驼峰或下划线均可", "createTime"),
|
|
("asc", "boolean", "否", "false", "是否升序(默认新数据在前)", "false"),
|
|
]
|
|
LEAD_PAGE_PARAM = [
|
|
("viewType", "string", "否", "MANAGE", "视图类型:PUBLIC_POOL/MY_LEAD/MY_FOLLOW/MANAGE,空/非法值回落 MANAGE", "PUBLIC_POOL"),
|
|
("statusIn", "int[]", "否", "-", "线索状态多值筛选(逗号分隔,如「已被领取」传 3,4)", "3,4"),
|
|
("poolId", "string", "否", "-", "公海池ID筛选", "1"),
|
|
("deptId", "string", "否", "-", "归属部门ID筛选", "1"),
|
|
("isUrgent", "int", "否", "-", "是否加急筛选(0/1)", "1"),
|
|
("feedbackStatus", "int", "否", "-", "反馈情况筛选(1=有效 2=无效 3=未反馈)", "1"),
|
|
("channelCode", "string", "否", "-", "渠道字典 code 筛选", "channel_web"),
|
|
("brandCode", "string", "否", "-", "品牌字典 code 筛选", "brand_a"),
|
|
("productCode", "string", "否", "-", "需求产品字典 code 筛选", "product_b"),
|
|
("provinceCode", "string", "否", "-", "省份国标 code 筛选", "110000"),
|
|
]
|
|
LEAD_WRITE_FIELDS = [ # LeadDTO 写入侧(create/edit 表单)
|
|
("leadName", "string", "否", "-", "线索名称", "张三的咨询"),
|
|
("phone", "string", "否", "-", "联系电话", "13800000000"),
|
|
("wechat", "string", "否", "-", "微信号", "wx_zhang"),
|
|
("email", "string", "否", "-", "邮箱", "zhang@example.com"),
|
|
("provinceCode", "string", "否", "-", "省份国标 code", "110000"),
|
|
("cityCode", "string", "否", "-", "市/区国标 code", "110100"),
|
|
("address", "string", "否", "-", "详细地址", "朝阳区xx路1号"),
|
|
("channelCode", "string", "否", "-", "渠道字典 code", "channel_web"),
|
|
("brandCode", "string", "否", "-", "品牌字典 code", "brand_a"),
|
|
("productCode", "string", "否", "-", "需求产品字典 code", "product_b"),
|
|
("sceneCode", "string", "否", "-", "需求场景字典 code", "scene_c"),
|
|
("consultContent", "string", "否", "-", "咨询内容", "想了解产品报价"),
|
|
("isUrgent", "int", "否", "-", "是否加急 0/1", "1"),
|
|
]
|
|
LEAD_RESP_ROWS = [ # LeadDTO 出参骨架
|
|
("id", "string", "线索ID(雪花,字符串下发)"),
|
|
("createTime", "datetime", "创建时间"),
|
|
("updateTime", "datetime", "更新时间"),
|
|
("leadName", "string", "线索名称"),
|
|
("phone", "string", "联系电话"),
|
|
("wechat", "string", "微信号"),
|
|
("email", "string", "邮箱"),
|
|
("provinceCode", "string", "省份国标 code"),
|
|
("cityCode", "string", "市/区国标 code"),
|
|
("address", "string", "详细地址"),
|
|
("channelCode", "string", "渠道字典 code"),
|
|
("brandCode", "string", "品牌字典 code"),
|
|
("productCode", "string", "需求产品字典 code"),
|
|
("sceneCode", "string", "需求场景字典 code"),
|
|
("consultContent", "string", "咨询内容"),
|
|
("status", "int", "线索状态(1-7:1 未分发 2 待领取 3 已领取 4 跟进中 5 已转商机 6 过期失效 7 线索作废)"),
|
|
("isUrgent", "int", "是否加急 0/1"),
|
|
("poolId", "string", "所属公海池ID"),
|
|
("poolNameSnapshot", "string", "公海池名称快照"),
|
|
("deptId", "string", "归属部门ID"),
|
|
("ownerUserId", "string", "领取人ID"),
|
|
("ownerNameSnapshot", "string", "领取人姓名快照"),
|
|
("claimTime", "datetime", "领取时刻"),
|
|
("recycleDeadline", "datetime", "回收截止时刻"),
|
|
("expireDeadline", "datetime", "失效截止时刻"),
|
|
("feedbackStatus", "int", "最新反馈情况:1=有效 2=无效 3=未反馈"),
|
|
("feedbackContent", "string", "最新反馈内容"),
|
|
("feedbackTime", "datetime", "最新反馈时刻"),
|
|
("provinceName", "string", "省份名称(出参回显)"),
|
|
("cityName", "string", "市/区名称(出参回显)"),
|
|
("followCount", "int", "关注人数(出参回显,现算)"),
|
|
("followed", "boolean", "当前用户是否已关注(出参回显)"),
|
|
]
|
|
LEAD_EXAMPLE = {
|
|
"id": "1", "createTime": "2026-08-14 10:00:00", "updateTime": "2026-08-14 10:00:00",
|
|
"leadName": "张三的咨询", "phone": "13800000000", "wechat": "wx_zhang",
|
|
"email": "zhang@example.com", "provinceCode": "110000", "cityCode": "110100",
|
|
"address": "朝阳区xx路1号", "channelCode": "channel_web", "brandCode": "brand_a",
|
|
"productCode": "product_b", "sceneCode": "scene_c", "consultContent": "想了解产品报价",
|
|
"status": 2, "isUrgent": 1, "poolId": "1", "poolNameSnapshot": "华北公海池",
|
|
"deptId": "10", "ownerUserId": "100", "ownerNameSnapshot": "李四",
|
|
"claimTime": "2026-08-14 10:00:00", "recycleDeadline": "2026-08-21 10:00:00",
|
|
"expireDeadline": "2026-09-13 10:00:00", "feedbackStatus": 3,
|
|
"feedbackContent": None, "feedbackTime": None,
|
|
"provinceName": "北京市", "cityName": "北京市", "followCount": 0, "followed": False,
|
|
}
|
|
|
|
def page_rows(item_rows):
|
|
return [("content[]", "array<" + item_rows[0][0].split("[]")[0] + ">", "数据列表(元素结构见下)")] if False else None
|
|
|
|
# 四菜单副本绑定(与 bruno-sync.config.json menuBindings 同源,ADR-0024)
|
|
MENUS = [
|
|
("线索管理/线索公海", "PUBLIC_POOL", "lead.public_pool", "公海"),
|
|
("线索管理/我的线索", "MY_LEAD", "lead.my_lead", "我的线索"),
|
|
("线索管理/我的关注", "MY_FOLLOW", "lead.my_follow", "我的关注"),
|
|
("线索管理/线索管理", "MANAGE", "lead.manage", "线索管理"),
|
|
]
|
|
MENU_EXPAND = {"线索管理": MENUS, "列偏好": MENUS} # 触发键:folder 命中则四菜单各出一份副本
|
|
|
|
def bindv(params, pname, value):
|
|
"""把 params 中 pname 行的示例值替换为菜单绑定值,说明追加「本页固定传」"""
|
|
if not params or value is None:
|
|
return params
|
|
return [(p[0], p[1], p[2], p[3], p[4] + "(本页固定传 %s)" % value, value) if p[0] == pname else p
|
|
for p in params]
|
|
|
|
def bru(folder, fname, name, method, url, kind="none", params=None, desc="",
|
|
resp_rows=None, data_example="__NULL__", req_auth="inherit", multipart_file=None,
|
|
nonenvelope_note=None, login_script=False):
|
|
"""kind: query | form | multipart | none;params=[(name,type,req,default,note,example)]
|
|
folder 命中 MENU_EXPAND 时按四菜单各出一份副本,viewType/scopeKey 绑定为菜单专属值。"""
|
|
if folder in MENU_EXPAND:
|
|
for mfolder, vt, sk, label in MENU_EXPAND[folder]:
|
|
p2 = bindv(bindv(params, "viewType", vt), "scopeKey", sk)
|
|
d2 = "【%s 页】%s" % (label, desc)
|
|
if params and any(pp[0] == "viewType" for pp in params):
|
|
d2 += " 本页 viewType 固定 %s。" % vt
|
|
_write_one(mfolder, fname, name, method, url, kind, p2, d2, resp_rows,
|
|
data_example, req_auth, multipart_file, nonenvelope_note, login_script)
|
|
return
|
|
_write_one(folder, fname, name, method, url, kind, params, desc, resp_rows,
|
|
data_example, req_auth, multipart_file, nonenvelope_note, login_script)
|
|
|
|
def _write_one(folder, fname, name, method, url, kind="none", params=None, desc="",
|
|
resp_rows=None, data_example="__NULL__", req_auth="inherit", multipart_file=None,
|
|
nonenvelope_note=None, login_script=False):
|
|
seq = SEQ.get(folder, 0) + 1
|
|
SEQ[folder] = seq
|
|
lines = []
|
|
lines.append("meta {")
|
|
lines.append(" name: %s" % name)
|
|
lines.append(" type: http")
|
|
lines.append(" seq: %d" % seq)
|
|
lines.append(" tags: [")
|
|
lines.append(" generated")
|
|
lines.append(" ]")
|
|
lines.append("}")
|
|
lines.append("")
|
|
m = method.lower()
|
|
body_kind = {"query": "none", "form": "formUrlEncoded", "multipart": "multipartForm", "none": "none"}[kind]
|
|
lines.append("%s {" % m)
|
|
lines.append(" url: {{baseUrl}}%s" % url)
|
|
lines.append(" body: %s" % body_kind)
|
|
lines.append(" auth: %s" % req_auth)
|
|
lines.append("}")
|
|
lines.append("")
|
|
if kind == "query" and params:
|
|
lines.append("params:query {")
|
|
for p in params:
|
|
lines.append(" %s: %s" % (p[0], p[5]))
|
|
lines.append("}")
|
|
lines.append("")
|
|
if kind == "form" and params:
|
|
lines.append("body:form-urlencoded {")
|
|
for p in params:
|
|
lines.append(" %s: %s" % (p[0], p[5]))
|
|
lines.append("}")
|
|
lines.append("")
|
|
if kind == "multipart" and params:
|
|
lines.append("body:multipart-form {")
|
|
for p in params:
|
|
if p[0] == multipart_file:
|
|
lines.append(" %s: @本地文件" % p[0])
|
|
else:
|
|
lines.append(" %s: %s" % (p[0], p[5]))
|
|
lines.append("}")
|
|
lines.append("")
|
|
if login_script:
|
|
lines.append("script:post-response {")
|
|
lines.append(" if (res.body && res.body.success) {")
|
|
lines.append(' bru.setEnvVar("token", res.body.data.token);')
|
|
lines.append(" }")
|
|
lines.append("}")
|
|
lines.append("")
|
|
# ---- docs ----
|
|
lines.append("docs {")
|
|
lines.append(" # %s" % name)
|
|
lines.append(" `%s %s`" % (method.upper(), url))
|
|
lines.append(" %s" % desc)
|
|
lines.append("")
|
|
if params:
|
|
lines.append(" ## 请求参数")
|
|
lines.append("")
|
|
doc_table = " " + table(PH, [(p[0], p[1], p[2], p[3], p[4]) for p in params])
|
|
lines.append(doc_table.replace("\n", "\n "))
|
|
lines.append("")
|
|
lines.append(" ## 请求示例")
|
|
lines.append("")
|
|
auth_line = " 请求头:无需鉴权" if req_auth == "none" else " 请求头:Authorization: Bearer {{token}}"
|
|
lines.append(auth_line)
|
|
if kind == "query":
|
|
qs = "&".join("%s=%s" % (p[0], p[5]) for p in params)
|
|
lines.append("")
|
|
lines.append(" GET %s?%s" % (url, qs))
|
|
elif kind == "form":
|
|
lines.append(" Content-Type: application/x-www-form-urlencoded")
|
|
lines.append("")
|
|
lines.append(" POST %s" % url)
|
|
lines.append(" " + "&".join("%s=%s" % (p[0], p[5]) for p in params))
|
|
elif kind == "multipart":
|
|
lines.append(" Content-Type: multipart/form-data")
|
|
lines.append("")
|
|
lines.append(" POST %s" % url)
|
|
for p in params:
|
|
if p[0] == multipart_file:
|
|
lines.append(" %s=@本地文件" % p[0])
|
|
else:
|
|
lines.append(" %s=%s" % (p[0], p[5]))
|
|
lines.append("")
|
|
lines.append(" ## 响应 data 结构")
|
|
lines.append("")
|
|
if nonenvelope_note is not None:
|
|
lines.append(" %s" % nonenvelope_note)
|
|
lines.append("")
|
|
elif resp_rows is None:
|
|
lines.append(" null")
|
|
lines.append("")
|
|
else:
|
|
lines.append(" " + table(RH, resp_rows).replace("\n", "\n "))
|
|
lines.append("")
|
|
lines.append(" ## 响应示例")
|
|
lines.append("")
|
|
if nonenvelope_note is not None:
|
|
lines.append(" %s" % nonenvelope_note)
|
|
elif data_example == "__NULL__":
|
|
lines.append(" // 示例数据,字段结构以上表为准,非真实返回")
|
|
lines.append(" " + env(None).replace("\n", "\n "))
|
|
else:
|
|
lines.append(" // 示例数据,字段结构以上表为准,非真实返回")
|
|
lines.append(" " + env(data_example).replace("\n", "\n "))
|
|
lines.append("}")
|
|
w(folder + "/" + fname + ".bru", "\n".join(lines) + "\n")
|
|
|
|
def page_of(item_example, item_rows, item_name):
|
|
"""构造 PageResult 的 resp_rows + data_example"""
|
|
rows = [
|
|
("content[]", "array<object>", "%s 列表(元素字段见下,缩进行)" % item_name),
|
|
] + [(" " + r[0], r[1], r[2]) for r in item_rows] + [
|
|
("total", "long", "总条数"),
|
|
("size", "long", "每页条数"),
|
|
("current", "long", "当前页码(从 1 开始)"),
|
|
("pages", "long", "总页数"),
|
|
("empty", "boolean", "是否为空结果(total == 0)"),
|
|
]
|
|
data = {"content": [item_example], "total": 1, "size": 10, "current": 1, "pages": 1, "empty": False}
|
|
return rows, data
|
|
|
|
BATCH_LEAD_ROWS = [
|
|
("total", "int", "处理总数(successCount + failCount)"),
|
|
("successCount", "int", "成功条数"),
|
|
("failCount", "int", "失败条数"),
|
|
("failures[]", "array<object>", "失败明细列表"),
|
|
(" id", "string", "失败的线索 id"),
|
|
(" reason", "string", "失败原因(语义枚举):STATUS_NOT_ALLOWED 状态不允许 / NOT_OWNER 非持有人 / CLAIM_RULE_DENIED 领取规则拒绝 / OVER_DAILY_LIMIT 超日领上限 / OVER_HOLD_LIMIT 超持有上限 / ALREADY_CONVERTED 已转商机 / CONCURRENT_MODIFIED 并发冲突 / UNKNOWN 未归类"),
|
|
(" message", "string", "失败文案"),
|
|
]
|
|
BATCH_LEAD_EXAMPLE = {
|
|
"total": 2, "successCount": 1, "failCount": 1,
|
|
"failures": [{"id": "2", "reason": "STATUS_NOT_ALLOWED", "message": "当前线索状态不允许该操作"}],
|
|
}
|
|
|
|
# ==================== 线索管理(23) ====================
|
|
F = "线索管理"
|
|
bru(F, "线索分页", "线索分页", "POST", "/api/lead/page", "form", BASE_PARAM + LEAD_PAGE_PARAM,
|
|
"线索分页(四视图:PUBLIC_POOL 公海 / MY_LEAD 我的线索 / MY_FOLLOW 我的关注 / MANAGE 线索管理),数据隔离由 @DataScope 自动注入。",
|
|
*page_of(LEAD_EXAMPLE, LEAD_RESP_ROWS, "LeadDTO"))
|
|
bru(F, "线索详情", "线索详情", "GET", "/api/lead/detail", "query",
|
|
[("id", "string", "是", "-", "线索ID", "1")],
|
|
"线索详情。", LEAD_RESP_ROWS, LEAD_EXAMPLE)
|
|
bru(F, "历史记录", "历史记录", "GET", "/api/lead/history", "query",
|
|
[("leadId", "string", "是", "-", "线索ID", "1")],
|
|
"线索操作历史(只展示 5 种:CLAIM 领取 / RELEASE 释放 / FEEDBACK 反馈 / CONVERT 转商机 / EDIT 编辑)。",
|
|
[
|
|
("[]", "array<object>", "历史记录列表"),
|
|
(" id", "string", "日志ID"),
|
|
(" leadId", "string", "线索ID"),
|
|
(" opType", "string", "操作类型(CLAIM/RELEASE/FEEDBACK/CONVERT/EDIT)"),
|
|
(" opTime", "datetime", "操作时间"),
|
|
(" opUserId", "string", "操作人ID"),
|
|
(" opUserName", "string", "操作人姓名(出参回显)"),
|
|
(" opUserDeptName", "string", "操作人部门名称快照"),
|
|
(" detail", "string", "操作详情(JSON 文本)"),
|
|
],
|
|
[{"id": "11", "leadId": "1", "opType": "CLAIM", "opTime": "2026-08-14 10:00:00",
|
|
"opUserId": "100", "opUserName": "李四", "opUserDeptName": "销售一部", "detail": "{}"}])
|
|
bru(F, "新建线索", "新建线索", "POST", "/api/lead/create", "form",
|
|
LEAD_WRITE_FIELDS + [("claimOnCreate", "boolean", "否", "false", "true=销售新增即领取(已领取态)", "false")],
|
|
"新建线索;claimOnCreate=true 时创建即领取。",
|
|
[("(data)", "string", "新线索ID(雪花,字符串下发)")], "9007199254740993")
|
|
bru(F, "编辑线索", "编辑线索", "POST", "/api/lead/edit", "form",
|
|
[("id", "string", "是", "-", "线索ID", "1")] + LEAD_WRITE_FIELDS,
|
|
"编辑线索(非终态可编辑)。", None)
|
|
bru(F, "删除线索", "删除线索", "POST", "/api/lead/delete", "form",
|
|
[("id", "string", "是", "-", "线索ID", "1")],
|
|
"删除线索(已转商机禁止删除)。", None)
|
|
bru(F, "领取线索", "领取线索", "POST", "/api/lead/claim", "form",
|
|
[("id", "string", "是", "-", "线索ID", "1")],
|
|
"领取线索(待领取 → 已领取)。", None)
|
|
bru(F, "分配线索到池", "分配线索到池", "POST", "/api/lead/assign-pool", "form",
|
|
[("id", "string", "是", "-", "线索ID", "1"), ("poolId", "string", "是", "-", "目标公海池ID", "1")],
|
|
"分配线索到池(未分发 → 待领取)。", None)
|
|
bru(F, "分配线索到销售", "分配线索到销售", "POST", "/api/lead/assign-user", "form",
|
|
[("id", "string", "是", "-", "线索ID", "1"), ("userId", "string", "是", "-", "目标销售用户ID", "100")],
|
|
"分配线索到销售(未分发/待领取 → 已领取;作废态可自环重新分配)。", None)
|
|
FEEDBACK_FIELDS = [
|
|
("id", "string", "否", "-", "反馈记录ID(编辑草稿时传)", "1"),
|
|
("leadId", "string", "是", "-", "线索ID", "1"),
|
|
("feedbackStatus", "int", "提交必填", "-", "反馈情况:1=有效 2=无效", "1"),
|
|
("content", "string", "否", "-", "反馈内容", "客户有明确意向"),
|
|
("productCode", "string", "否", "-", "需求产品字典 code", "product_b"),
|
|
("attachmentIds", "string(json数组)", "否", "-", "附件文件ID列表(JSON 数组字符串)", "[\"9001\"]"),
|
|
]
|
|
bru(F, "保存反馈草稿", "保存反馈草稿", "POST", "/api/lead/feedback-draft", "form", FEEDBACK_FIELDS,
|
|
"保存反馈草稿(不改状态机,可反复保存)。", None)
|
|
bru(F, "提交反馈", "提交反馈", "POST", "/api/lead/feedback-submit", "form", FEEDBACK_FIELDS,
|
|
"提交反馈(已领取 → 跟进中;反馈无效时 → 线索作废)。", None)
|
|
bru(F, "转商机", "转商机", "POST", "/api/lead/convert", "form",
|
|
[("id", "string", "是", "-", "线索ID", "1"),
|
|
("opportunityName", "string", "是", "-", "商机名称", "张三-产品B采购意向"),
|
|
("industryCode", "string", "是", "-", "行业字典 code(opportunity.industry)", "industry_mfg"),
|
|
("partyA", "string", "否", "-", "甲方(自由文本)", "某某公司"),
|
|
("intendedCustomer", "string", "是", "-", "意向客户(自由文本)", "张三"),
|
|
("regionCode", "string", "是", "-", "地区国标 code", "110100"),
|
|
("remark", "string", "否", "-", "备注", "-")],
|
|
"线索转商机(已领取/跟进中 → 已转商机)。", None)
|
|
bru(F, "释放线索", "释放线索", "POST", "/api/lead/release", "form",
|
|
[("id", "string", "是", "-", "线索ID", "1")],
|
|
"释放线索(已领取/跟进中 → 待领取,回公海池)。", None)
|
|
bru(F, "激活线索", "激活线索", "POST", "/api/lead/activate", "form",
|
|
[("id", "string", "是", "-", "线索ID", "1")],
|
|
"激活线索(过期失效 → 恢复失效前状态,计时重置)。", None)
|
|
bru(F, "关注线索", "关注线索", "POST", "/api/lead/follow", "form",
|
|
[("id", "string", "是", "-", "线索ID", "1")],
|
|
"关注线索(进入「我的关注」视图)。", None)
|
|
bru(F, "取消关注", "取消关注", "POST", "/api/lead/unfollow", "form",
|
|
[("id", "string", "是", "-", "线索ID", "1")],
|
|
"取消关注。", None)
|
|
bru(F, "视图统计卡片", "视图统计卡片", "POST", "/api/lead/stats", "form", BASE_PARAM + LEAD_PAGE_PARAM,
|
|
"视图统计卡片:与 /page 同一套筛选口径(viewType + 筛选 + @DataScope 部门天花板),按状态分组计数;current/size 不参与统计。",
|
|
[
|
|
("total", "int", "线索总量"),
|
|
("claimed", "int", "已被领取(status IN 已领取/跟进中)"),
|
|
("converted", "int", "已转商机"),
|
|
("todayNew", "int", "今日新增"),
|
|
("undistributed", "int", "未分发"),
|
|
],
|
|
{"total": 100, "claimed": 40, "converted": 10, "todayNew": 5, "undistributed": 20})
|
|
|
|
# ---- 线索批量(ADR-0023:非原子、逐条 CAS、部分成功) ----
|
|
IDS = [("ids", "string[]", "是", "-", "线索ID列表(逗号分隔)", "1,2")]
|
|
bru(F, "批量领取", "批量领取", "POST", "/api/lead/claim-batch", "form", IDS,
|
|
"批量领取(非原子、逐条 CAS、部分成功)。", BATCH_LEAD_ROWS, BATCH_LEAD_EXAMPLE)
|
|
bru(F, "批量分配到池", "批量分配到池", "POST", "/api/lead/assign-pool-batch", "form",
|
|
IDS + [("poolId", "string", "是", "-", "目标公海池ID", "1")],
|
|
"批量分配到池(未分发 → 待领取)。", BATCH_LEAD_ROWS, BATCH_LEAD_EXAMPLE)
|
|
bru(F, "批量分配到销售", "批量分配到销售", "POST", "/api/lead/assign-user-batch", "form",
|
|
IDS + [("userId", "string", "是", "-", "目标销售用户ID(统一分配给同一销售)", "100")],
|
|
"批量分配到销售。", BATCH_LEAD_ROWS, BATCH_LEAD_EXAMPLE)
|
|
bru(F, "批量释放", "批量释放", "POST", "/api/lead/release-batch", "form", IDS,
|
|
"批量释放(已领取/跟进中 → 待领取)。", BATCH_LEAD_ROWS, BATCH_LEAD_EXAMPLE)
|
|
bru(F, "批量激活", "批量激活", "POST", "/api/lead/activate-batch", "form", IDS,
|
|
"批量激活(过期失效 → 恢复失效前状态)。", BATCH_LEAD_ROWS, BATCH_LEAD_EXAMPLE)
|
|
bru(F, "批量删除", "批量删除", "POST", "/api/lead/delete-batch", "form", IDS,
|
|
"批量删除(已转商机记失败,不中断整批)。", BATCH_LEAD_ROWS, BATCH_LEAD_EXAMPLE)
|
|
|
|
# ==================== 线索管理/线索设置(5,即公海池配置页,ADR-0024 改名) ====================
|
|
POOL_RESP_ROWS = [
|
|
("id", "string", "公海池ID"),
|
|
("createTime", "datetime", "创建时间"),
|
|
("updateTime", "datetime", "更新时间"),
|
|
("poolName", "string", "公海池名称(全局唯一)"),
|
|
("deptId", "string", "归属销售部门ID(一部门一池)"),
|
|
("teamDeptId", "string", "归属团队部门ID(冗余)"),
|
|
("claimRule", "int", "领取规则:1=成员可领+管理员分配 2=仅管理员分配"),
|
|
("recycleDays", "int", "超时回收天数 N(≥1)"),
|
|
("expireDays", "int", "线索失效天数 M(≥1)"),
|
|
("dailyClaimLimit", "int", "每日领取上限(≥0,0=无限)"),
|
|
("holdLimit", "int", "个人持有上限(≥1)"),
|
|
("provinceRegionIds", "array<string>", "负责省份区划ID列表(sys_region level=1)"),
|
|
("cityRegionIds", "array<string>", "负责市/区区划ID列表(sys_region level=2/3)"),
|
|
("ownerUserId", "string", "负责人用户ID(1池1负责人)"),
|
|
("collaboratorUserIds", "array<string>", "协作人用户ID列表"),
|
|
("deptName", "string", "归属部门名称(出参回显)"),
|
|
("ownerUserName", "string", "负责人姓名(出参回显)"),
|
|
]
|
|
POOL_EXAMPLE = {
|
|
"id": "1", "createTime": "2026-08-14 10:00:00", "updateTime": "2026-08-14 10:00:00",
|
|
"poolName": "华北公海池", "deptId": "10", "teamDeptId": "10", "claimRule": 1,
|
|
"recycleDays": 7, "expireDays": 30, "dailyClaimLimit": 20, "holdLimit": 100,
|
|
"provinceRegionIds": ["110000"], "cityRegionIds": ["110100"], "ownerUserId": "100",
|
|
"collaboratorUserIds": ["101"], "deptName": "销售一部", "ownerUserName": "李四",
|
|
}
|
|
POOL_WRITE_FIELDS = [
|
|
("id", "string", "编辑必填", "-", "公海池ID(新建不传)", "1"),
|
|
("poolName", "string", "是", "-", "公海池名称(全局唯一)", "华北公海池"),
|
|
("deptId", "string", "是", "-", "归属销售部门ID(一部门一池)", "10"),
|
|
("teamDeptId", "string", "否", "-", "归属团队部门ID(冗余)", "10"),
|
|
("claimRule", "int", "是", "-", "领取规则:1=成员可领+管理员分配 2=仅管理员分配", "1"),
|
|
("recycleDays", "int", "是", "-", "超时回收天数 N(≥1)", "7"),
|
|
("expireDays", "int", "是", "-", "线索失效天数 M(≥1)", "30"),
|
|
("dailyClaimLimit", "int", "是", "-", "每日领取上限(≥0,0=无限)", "20"),
|
|
("holdLimit", "int", "是", "-", "个人持有上限(≥1)", "100"),
|
|
("provinceRegionIds", "string[]", "否", "-", "负责省份区划ID列表(逗号分隔)", "110000"),
|
|
("cityRegionIds", "string[]", "否", "-", "负责市/区区划ID列表(逗号分隔)", "110100"),
|
|
("ownerUserId", "string", "是", "-", "负责人用户ID(1池1负责人)", "100"),
|
|
("collaboratorUserIds", "string[]", "否", "-", "协作人用户ID列表(逗号分隔)", "101"),
|
|
]
|
|
FP = "线索管理/线索设置"
|
|
bru(FP, "公海池分页列表", "公海池分页列表", "POST", "/api/rule/pool/page", "form",
|
|
BASE_PARAM + [("deptId", "string", "否", "-", "归属部门ID筛选", "10"),
|
|
("claimRule", "int", "否", "-", "领取规则筛选", "1")],
|
|
"公海池分页列表。", *page_of(POOL_EXAMPLE, POOL_RESP_ROWS, "LeadPoolDTO"))
|
|
bru(FP, "公海池详情", "公海池详情", "GET", "/api/rule/pool/detail", "query",
|
|
[("id", "string", "是", "-", "公海池ID", "1")],
|
|
"公海池详情(含省份/市/区/人员)。", POOL_RESP_ROWS, POOL_EXAMPLE)
|
|
bru(FP, "新建编辑公海池", "新建/编辑公海池", "POST", "/api/rule/pool/saveOrUpdate", "form", POOL_WRITE_FIELDS,
|
|
"新建/编辑公海池(id 为空新建、非空编辑)。", None)
|
|
bru(FP, "删除公海池", "删除公海池", "POST", "/api/rule/pool/delete", "form",
|
|
[("id", "string", "是", "-", "公海池ID", "1")],
|
|
"删除公海池(软删除,级联清关联表;池下有活跃线索拒删)。", None)
|
|
bru(FP, "批量删除公海池", "批量删除公海池", "POST", "/api/rule/pool/delete-batch", "form",
|
|
[("ids", "string[]", "是", "-", "公海池ID列表(逗号分隔)", "1,2")],
|
|
"批量删除公海池(非原子、逐条、部分成功)。",
|
|
[
|
|
("total", "int", "处理总数(successCount + failCount)"),
|
|
("successCount", "int", "成功条数"),
|
|
("failCount", "int", "失败条数"),
|
|
("failures[]", "array<object>", "失败明细列表"),
|
|
(" poolId", "string", "失败的公海池 id"),
|
|
(" reason", "string", "失败原因(语义枚举):NOT_EXIST 池不存在或已删除 / HAS_ACTIVE_LEAD 池下有活跃线索 / UNKNOWN 未归类"),
|
|
(" message", "string", "失败文案"),
|
|
],
|
|
{"total": 2, "successCount": 1, "failCount": 1,
|
|
"failures": [{"poolId": "2", "reason": "NOT_EXIST", "message": "公海池不存在"}]})
|
|
|
|
# ==================== 线索规则-行政区划(2) ====================
|
|
REGION_ROWS = [
|
|
("[]", "array<object>", "区划列表"),
|
|
(" id", "string", "区划ID"),
|
|
(" code", "string", "区划国标 code"),
|
|
(" name", "string", "区划名称"),
|
|
(" parentId", "string", "上级区划ID"),
|
|
(" level", "int", "层级:1=省 2=市 3=区"),
|
|
]
|
|
REGION_EXAMPLE = [{"id": "110000", "code": "110000", "name": "北京市", "parentId": "0", "level": 1}]
|
|
FR = "线索规则-行政区划"
|
|
bru(FR, "按上级查子区划", "按上级查子区划", "GET", "/api/rule/region/list", "query",
|
|
[("parentId", "string", "否", "-", "上级区划ID(不传则查省级)", "110000")],
|
|
"按上级ID查子区划(级联下拉数据源)。", REGION_ROWS, REGION_EXAMPLE)
|
|
bru(FR, "按层级查区划", "按层级查区划", "GET", "/api/rule/region/level", "query",
|
|
[("level", "int", "是", "-", "层级:1=省 2=市 3=区", "1")],
|
|
"按层级查区划。", REGION_ROWS, REGION_EXAMPLE)
|
|
|
|
# ==================== 列偏好(2) ====================
|
|
FC = "列偏好"
|
|
bru(FC, "读取列偏好", "读取列偏好", "GET", "/api/preference/get", "query",
|
|
[("scopeKey", "string", "是", "-", "偏好范围 code(如 lead.public_pool)", "lead.public_pool")],
|
|
"读取当前用户在某 scope 的列偏好(用户私有,userId 取自登录态;无偏好时 data 为 null)。",
|
|
[
|
|
("visibleKeys", "array<string>", "用户勾选的字段 key 列表(语义为「显示」)"),
|
|
("columnOrder", "array<string>", "用户拖拽后的字段 key 顺序列表"),
|
|
],
|
|
{"visibleKeys": ["leadName", "phone"], "columnOrder": ["phone", "leadName"]})
|
|
bru(FC, "保存列偏好", "保存列偏好", "POST", "/api/preference/save", "form",
|
|
[("scopeKey", "string", "是", "-", "偏好范围 code", "lead.public_pool"),
|
|
("visibleKeys", "string[]", "否", "-", "显示字段 key 列表(逗号分隔)", "leadName,phone"),
|
|
("columnOrder", "string[]", "否", "-", "字段顺序列表(逗号分隔)", "phone,leadName")],
|
|
"保存当前用户在某 scope 的列偏好(显隐+顺序,一次写入)。", None)
|
|
|
|
# ==================== 认证 / 登录(方法级 tags=登录 + 类级 @Tag=认证,各一份副本) ====================
|
|
USER_INFO_ROWS = [
|
|
("id", "string", "用户ID"),
|
|
("username", "string", "用户显示名"),
|
|
("mobile", "string", "手机号"),
|
|
("email", "string", "邮箱"),
|
|
("avatar", "string", "头像URL"),
|
|
("deptId", "string", "归属部门ID"),
|
|
("deptName", "string", "归属部门名称"),
|
|
("menus", "array", "当前用户菜单树(仅登录和 /me 时填充,同 GET /api/system/menus/tree)"),
|
|
]
|
|
USER_INFO_EXAMPLE = {
|
|
"id": "100", "username": "李四", "mobile": "13800000000", "email": "li@example.com",
|
|
"avatar": "http://minio内网地址:9000/crm/avatar/1.png", "deptId": "10", "deptName": "销售一部", "menus": [],
|
|
}
|
|
LOGIN_DATA = {"token": "eyJhbGciOiJIUzI1NiJ9.example.token", "userInfo": USER_INFO_EXAMPLE}
|
|
LOGIN_ROWS = [
|
|
("token", "string", "JWT token,后续请求放入请求头:Authorization: Bearer {token}"),
|
|
("userInfo", "object", "用户信息(字段见下,缩进行)"),
|
|
] + [(" " + r[0], r[1], r[2]) for r in USER_INFO_ROWS]
|
|
for _f in ("登录", "认证"):
|
|
bru(_f, "钉钉登录", "钉钉登录", "POST", "/api/auth/login/dingtalk", "form",
|
|
[("authCode", "string", "是", "-", "前端扫码/免登拿到的三方 authCode", "auth-code-from-dingtalk")],
|
|
"钉钉登录(扫码/免登通用);响应后自动把 token 写入环境变量。",
|
|
LOGIN_ROWS, LOGIN_DATA, req_auth="none", login_script=True)
|
|
bru(_f, "注销", "注销", "POST", "/api/auth/logout", "none", None,
|
|
"注销当前 token(删 Redis 会话,强制下线)。", None)
|
|
bru(_f, "获取当前用户信息", "获取当前登录用户信息", "GET", "/api/auth/me", "none", None,
|
|
"获取当前登录用户信息(含菜单树)。", USER_INFO_ROWS, USER_INFO_EXAMPLE)
|
|
|
|
# ==================== system(无 tag,回退 urlPattern:/api/{module}/{resource}) ====================
|
|
SM = "system/menus"
|
|
bru(SM, "菜单树", "当前用户菜单树", "GET", "/api/system/menus/tree", "none", None,
|
|
"当前用户可见菜单树(按角色过滤,前端据此控制路由/UI)。结构同「权限资源树」但只含可见节点。",
|
|
[("[]", "array<object>", "ResourceNodeDTO 扁平数组(前端按 parentId 组树),字段见「权限点管理/资源树列表」")],
|
|
[])
|
|
SD = "system/depts"
|
|
DEPT_ROWS = [
|
|
("id", "string", "部门ID"),
|
|
("parentId", "string", "上级部门 ID,根节点为 0"),
|
|
("deptName", "string", "部门名称"),
|
|
("sort", "int", "排序"),
|
|
("leaderUserId", "string", "部门负责人用户 ID"),
|
|
("children[]", "array<object>", "子部门列表(同 DeptDTO 结构,可嵌套)"),
|
|
]
|
|
DEPT_EXAMPLE = {"id": "10", "parentId": "0", "deptName": "销售一部", "sort": 1,
|
|
"leaderUserId": "100", "children": [{"id": "11", "parentId": "10", "deptName": "销售一组",
|
|
"sort": 1, "leaderUserId": "101", "children": []}]}
|
|
bru(SD, "部门树", "部门树", "GET", "/api/system/depts/tree", "none", None,
|
|
"全量部门树(前端组织选择器数据源)。", DEPT_ROWS, DEPT_EXAMPLE)
|
|
bru(SD, "保存部门", "保存部门", "POST", "/api/system/depts/save", "form",
|
|
[("id", "string", "编辑必填", "-", "部门ID(新建不传)", "1"),
|
|
("parentId", "string", "是", "-", "上级部门 ID,根节点为 0", "0"),
|
|
("deptName", "string", "是", "-", "部门名称", "销售二部"),
|
|
("sort", "int", "否", "-", "排序", "2"),
|
|
("leaderUserId", "string", "否", "-", "部门负责人用户 ID", "100")],
|
|
"新建/编辑部门。", None)
|
|
bru(SD, "删除部门", "删除部门", "POST", "/api/system/depts/delete", "form",
|
|
[("id", "string", "是", "-", "部门ID", "1")],
|
|
"删除部门(部门下仍有成员拒删)。", None)
|
|
SU = "system/users"
|
|
USER_LIST_ROWS = [
|
|
("content[]", "array<object>", "用户列表"),
|
|
(" id", "string", "用户ID"),
|
|
(" username", "string", "姓名"),
|
|
(" account", "string", "登录账号(OA 来源,只读)"),
|
|
(" title", "string", "职务(OA 来源,只读)"),
|
|
(" mobile", "string", "手机号"),
|
|
(" primaryDeptId", "string", "主部门ID"),
|
|
(" primaryDeptName", "string", "主部门名"),
|
|
(" partTimeDepts[]", "array<object>", "兼职部门(主部门之外)"),
|
|
(" id", "string", "部门ID"),
|
|
(" name", "string", "部门名"),
|
|
(" employmentStatus", "string", "在职状态 active/resigned"),
|
|
(" enabled", "boolean", "账号是否启用(与在职状态分离,ADR-0007)"),
|
|
(" roles[]", "array<object>", "所持角色"),
|
|
(" id", "string", "角色ID"),
|
|
(" name", "string", "角色名"),
|
|
(" lastLoginTime", "datetime", "最后登录时间,null=从未登录"),
|
|
("total", "long", "总条数"), ("size", "long", "每页条数"),
|
|
("current", "long", "当前页码"), ("pages", "long", "总页数"), ("empty", "boolean", "是否空结果"),
|
|
]
|
|
USER_LIST_EXAMPLE = {"content": [{"id": "100", "username": "李四", "account": "lisi", "title": "销售",
|
|
"mobile": "13800000000", "primaryDeptId": "10", "primaryDeptName": "销售一部",
|
|
"partTimeDepts": [{"id": "20", "name": "市场部"}], "employmentStatus": "active", "enabled": True,
|
|
"roles": [{"id": "3", "name": "销售"}], "lastLoginTime": "2026-08-14 09:00:00"}],
|
|
"total": 1, "size": 10, "current": 1, "pages": 1, "empty": False}
|
|
bru(SU, "用户分页", "用户分页", "POST", "/api/system/users/page", "form",
|
|
BASE_PARAM + [("deptId", "string", "否", "-", "部门ID,选中节点含全部子孙;null=全公司", "10"),
|
|
("employmentStatus", "string", "否", "-", "在职状态 active/resigned/unassigned;null=不限", "active"),
|
|
("loginFrom", "date", "否", "-", "最后登录起(含当天 00:00)", "2026-08-01"),
|
|
("loginTo", "date", "否", "-", "最后登录讫(含当天 23:59:59)", "2026-08-14")],
|
|
"用户管理-用户列表分页(需 ADMIN 角色)。", USER_LIST_ROWS, USER_LIST_EXAMPLE)
|
|
bru(SU, "用户统计", "用户统计", "GET", "/api/system/users/stats", "query",
|
|
[("deptId", "string", "否", "-", "部门ID(口径含子孙);不传=全公司", "10")],
|
|
"用户管理-四指标卡统计(需 ADMIN 角色);口径跟随选中部门子树。",
|
|
[("deptCount", "int", "部门数(选中节点及其子孙)"),
|
|
("activeCount", "long", "在职用户数"),
|
|
("resignedCount", "long", "离职用户数"),
|
|
("unassignedCount", "long", "待分配用户数(在职且无角色)")],
|
|
{"deptCount": 5, "activeCount": 20, "resignedCount": 2, "unassignedCount": 3})
|
|
bru(SU, "分配用户角色", "分配用户角色", "POST", "/api/system/users/assign-roles", "form",
|
|
[("userId", "string", "是", "-", "用户ID", "100"),
|
|
("roleIds", "string", "否", "-", "角色ID列表(逗号分隔,全量替换)", "3,4")],
|
|
"给用户分配角色(全量替换,需 ADMIN 角色)。", None)
|
|
bru(SU, "分配用户部门", "分配用户部门", "POST", "/api/system/users/assign-depts", "form",
|
|
[("userId", "string", "是", "-", "用户ID", "100"),
|
|
("primaryDeptId", "string", "否", "-", "主部门 ID(可空)", "10"),
|
|
("deptIds", "string", "否", "-", "兼职部门 ID 列表(逗号分隔,全量替换)", "20,21")],
|
|
"原子设置用户的主部门与全部兼职部门(全量替换)。", None)
|
|
|
|
# ==================== 系统管理/权限管理/角色管理(6) ====================
|
|
FRM = "系统管理/权限管理/角色管理"
|
|
ROLE_ROWS = [
|
|
("id", "string", "角色ID"),
|
|
("createTime", "datetime", "创建时间"),
|
|
("updateTime", "datetime", "更新时间"),
|
|
("roleName", "string", "角色名称"),
|
|
("roleCode", "string", "角色编码"),
|
|
("sort", "int", "排序"),
|
|
("remark", "string", "备注"),
|
|
("builtin", "boolean", "是否内置角色"),
|
|
("resourceIds", "array<string>", "已补全祖先的完整授权集合(仅 detail 出参)"),
|
|
("moduleScopes[]", "array<object>", "角色×模块数据范围档位列表(仅 detail 出参,page 为 null)"),
|
|
(" moduleCode", "string", "模块编码(如 lead)"),
|
|
(" dataScope", "int", "档位:1 仅本人 / 2 本部门 / 3 本部门及下属 / 4 全部"),
|
|
]
|
|
ROLE_EXAMPLE = {"id": "3", "createTime": "2026-08-14 10:00:00", "updateTime": "2026-08-14 10:00:00",
|
|
"roleName": "销售", "roleCode": "ROLE_SALES", "sort": 1, "remark": "一线销售",
|
|
"builtin": False, "resourceIds": ["1", "2"],
|
|
"moduleScopes": [{"moduleCode": "lead", "dataScope": 1}]}
|
|
bru(FRM, "角色分页", "分页查询角色", "POST", "/api/roles/page", "form", BASE_PARAM,
|
|
"分页查询角色(keyword 匹配角色名称;moduleScopes 为 null)。",
|
|
*page_of({k: v for k, v in ROLE_EXAMPLE.items() if k not in ("resourceIds", "moduleScopes")},
|
|
[r for r in ROLE_ROWS if not r[0].startswith(" ") and not r[0].startswith("resource") and not r[0].startswith("moduleScopes")],
|
|
"RoleDTO"))
|
|
bru(FRM, "新增编辑角色", "新增或编辑角色", "POST", "/api/roles/saveOrUpdate", "form",
|
|
[("id", "string", "编辑必填", "-", "角色ID(新建不传)", "1"),
|
|
("roleName", "string", "是", "-", "角色名称", "销售"),
|
|
("roleCode", "string", "是", "-", "角色编码", "ROLE_SALES"),
|
|
("sort", "int", "否", "-", "排序", "1"),
|
|
("remark", "string", "否", "-", "备注", "一线销售"),
|
|
("moduleScopesJson", "string(json)", "否", "-", "模块档位 JSON 字符串:[{\"moduleCode\":\"...\",\"dataScope\":1}]", "[{\"moduleCode\":\"lead\",\"dataScope\":1}]")],
|
|
"新增/编辑角色(含每模块数据范围档位,全量替换)。", None)
|
|
bru(FRM, "角色详情", "角色详情", "GET", "/api/roles/detail", "query",
|
|
[("roleId", "string", "是", "-", "角色ID", "3")],
|
|
"角色详情(含授权资源集合 + 每模块档位)。", ROLE_ROWS, ROLE_EXAMPLE)
|
|
bru(FRM, "分配角色权限", "分配角色权限资源", "POST", "/api/roles/assign-resources", "form",
|
|
[("roleId", "string", "是", "-", "角色ID", "3"),
|
|
("resourceIds", "string", "否", "-", "资源节点ID列表(逗号分隔,祖先自动补全,全量替换)", "1,2,3")],
|
|
"分配角色权限资源(祖先补全 + 全量替换)。", None)
|
|
bru(FRM, "删除角色", "删除角色", "POST", "/api/roles/delete", "form",
|
|
[("id", "string", "是", "-", "角色ID", "3")],
|
|
"删除角色(级联清理关联表;内置角色拒删)。", None)
|
|
bru(FRM, "可配数据权限模块", "列出可配数据权限模块", "GET", "/api/data-scope/modules", "none", None,
|
|
"列出可配数据权限模块(status=enabled,按 sort 排序;渲染角色配置表格前先拉取)。",
|
|
[("[]", "array<object>", "模块列表"),
|
|
(" code", "string", "模块编码(如 lead)"),
|
|
(" name", "string", "模块名称"),
|
|
(" sort", "int", "排序"),
|
|
(" builtin", "boolean", "是否内置")],
|
|
[{"code": "lead", "name": "线索", "sort": 1, "builtin": False}])
|
|
|
|
# ==================== 系统管理/权限点管理(4) ====================
|
|
FRC = "系统管理/权限点管理"
|
|
RES_ROWS = [
|
|
("id", "string", "节点ID"),
|
|
("createTime", "datetime", "创建时间"),
|
|
("updateTime", "datetime", "更新时间"),
|
|
("parentId", "string", "父节点 ID,根节点传 null/0"),
|
|
("name", "string", "节点名称"),
|
|
("type", "string", "节点类型:CATALOG 目录 / MENU 菜单 / BUTTON 权限点"),
|
|
("sort", "int", "排序"),
|
|
("description", "string", "描述/备注"),
|
|
("route", "string", "前端路由路径,menu 必填"),
|
|
("perms", "string", "权限码(如 crm:user:list),button 必填"),
|
|
("denyBehavior", "string", "无权限时的前端行为:hide / disable,button 必填"),
|
|
("apiUrl", "string", "接口 URL,button 必填"),
|
|
("status", "string", "权限点状态:enabled 启用 / disabled 停用"),
|
|
("icon", "string", "图标 URL,catalog/menu 可选"),
|
|
("children[]", "array<object>", "子节点列表(仅 listAll 返回时填充,同结构可嵌套)"),
|
|
]
|
|
RES_EXAMPLE = {"id": "1", "createTime": "2026-08-14 10:00:00", "updateTime": "2026-08-14 10:00:00",
|
|
"parentId": "0", "name": "系统管理", "type": "CATALOG", "sort": 1,
|
|
"description": "系统管理目录", "route": None, "perms": None, "denyBehavior": None,
|
|
"apiUrl": None, "status": "enabled", "icon": None,
|
|
"children": [{"id": "2", "parentId": "1", "name": "菜单管理", "type": "MENU", "sort": 1,
|
|
"route": "/system/menu", "status": "enabled", "children": []}]}
|
|
bru(FRC, "资源树列表", "获取权限资源树", "GET", "/api/resources/list", "none", None,
|
|
"全量资源树(扁平数组,前端按 parentId 组树;需 ADMIN 角色)。", RES_ROWS, [RES_EXAMPLE])
|
|
bru(FRC, "新增编辑资源节点", "新增编辑一级菜单/二级菜单/权限点", "POST", "/api/resources/saveOrUpdate", "form",
|
|
[("id", "string", "编辑必填", "-", "节点ID(新建不传)", "1"),
|
|
("parentId", "string", "是", "-", "父节点 ID,根节点传 null/0", "0"),
|
|
("name", "string", "是", "-", "节点名称", "系统管理"),
|
|
("type", "string", "是", "-", "节点类型:CATALOG/MENU/BUTTON", "CATALOG"),
|
|
("sort", "int", "否", "-", "排序", "1"),
|
|
("description", "string", "否", "-", "描述/备注", "-"),
|
|
("route", "string", "menu 必填", "-", "前端路由路径", "/system/menu"),
|
|
("perms", "string", "button 必填", "-", "权限码(如 crm:user:list)", "crm:role:list"),
|
|
("denyBehavior", "string", "button 必填", "-", "无权限时前端行为:hide/disable", "hide"),
|
|
("apiUrl", "string", "button 必填", "-", "接口 URL", "/api/roles/page"),
|
|
("status", "string", "否", "-", "权限点状态:enabled/disabled", "enabled"),
|
|
("icon", "string", "否", "-", "图标 URL", "http://minio内网地址:9000/crm/icon/1.png")],
|
|
"新增/编辑资源节点(含校验;需 ADMIN 角色)。", RES_ROWS, RES_EXAMPLE)
|
|
bru(FRC, "删除资源节点", "删除一级菜单/二级菜单/权限点", "POST", "/api/resources/delete", "form",
|
|
[("id", "string", "是", "-", "节点ID", "1")],
|
|
"删除节点(含子节点检查 + 级联清理;需 ADMIN 角色)。", None)
|
|
FILE_INFO_ROWS = [
|
|
("fileId", "string", "文件唯一引用凭据(crm_file_info 主键),业务表只存它"),
|
|
("originalName", "string", "原始文件名"),
|
|
("size", "long", "文件大小(字节)"),
|
|
("contentType", "string", "MIME 类型"),
|
|
("bizDomain", "string", "业务域"),
|
|
("creatorId", "string", "上传者(无登录态的内部上传为 null)"),
|
|
("createTime", "datetime", "上传时间"),
|
|
("thumbnailStatus", "string", "缩略图状态(PENDING/READY/FAILED/UNSUPPORTED)"),
|
|
]
|
|
FILE_INFO_EXAMPLE = {"fileId": "9001", "originalName": "报价单.png", "size": 102400,
|
|
"contentType": "image/png", "bizDomain": "lead", "creatorId": "100",
|
|
"createTime": "2026-08-14 10:00:00", "thumbnailStatus": "READY"}
|
|
bru(FRC, "上传图标", "上传图标", "POST", "/api/resources/icon/upload", "multipart",
|
|
[("file", "file", "是", "-", "图标文件(格式/大小校验)", "icon.png")],
|
|
"上传资源图标(需 ADMIN 角色)。", FILE_INFO_ROWS, FILE_INFO_EXAMPLE, multipart_file="file")
|
|
|
|
# ==================== 调试(验证专用)(1) ====================
|
|
bru("调试(验证专用)", "签发调试token", "【验证专用】按 userId 签发 JWT", "GET", "/api/auth/debug/token", "query",
|
|
[("userId", "string", "是", "-", "crm_auth_user.id", "100")],
|
|
"【验证专用 · 严禁进生产】按 userId 直接签发合法 JWT,绕过钉钉扫码;仅 verify profile 激活时端点存在(ADR-0014)。",
|
|
[("(data)", "string", "与钉钉登录同款 JWT,可直接放进 Authorization: Bearer 调试其他接口")],
|
|
"eyJhbGciOiJIUzI1NiJ9.debug.token", req_auth="none")
|
|
|
|
# ==================== 文件(9) ====================
|
|
FF = "文件"
|
|
bru(FF, "直传上传", "直传上传", "POST", "/api/file/upload", "multipart",
|
|
[("file", "file", "是", "-", "文件(超直传上限 10MB 走分片)", "报价单.png"),
|
|
("bizDomain", "string", "是", "-", "业务域(如 lead)", "lead")],
|
|
"直传小文件(超上限走分片三阶段)。", FILE_INFO_ROWS, FILE_INFO_EXAMPLE, multipart_file="file")
|
|
bru(FF, "文件详情", "文件详情", "GET", "/api/file/info", "query",
|
|
[("fileId", "string", "是", "-", "文件ID", "9001")],
|
|
"文件详情。", FILE_INFO_ROWS, FILE_INFO_EXAMPLE)
|
|
bru(FF, "下载文件", "下载文件", "GET", "/api/file/download", "query",
|
|
[("fileId", "string", "是", "-", "文件ID", "9001")],
|
|
"下载文件。",
|
|
nonenvelope_note="信封例外:成功返回二进制文件流(Content-Disposition 附件下载);失败返回标准 JSON 信封")
|
|
bru(FF, "预览地址", "预览地址", "GET", "/api/file/preview-url", "query",
|
|
[("fileId", "string", "是", "-", "文件ID", "9001")],
|
|
"预览地址(可直接 window.open,由 kkFileView 渲染)。",
|
|
[("(data)", "string", "kkFileView onlinePreview 完整 URL")],
|
|
"http://kkfileview内网地址:8012/onlinePreview?url=encoded")
|
|
bru(FF, "缩略图", "缩略图", "GET", "/api/file/thumbnail", "query",
|
|
[("fileId", "string", "是", "-", "文件ID", "9001")],
|
|
"缩略图(命中缓存返回 200 + max-age=86400,未就绪返回 202 占位图)。",
|
|
nonenvelope_note="信封例外:成功返回二进制图片流(200/202,带 Cache-Control);失败返回标准 JSON 信封")
|
|
bru(FF, "删除文件", "删除文件", "POST", "/api/file/delete", "form",
|
|
[("fileId", "string", "是", "-", "文件ID", "9001")],
|
|
"删除文件(逻辑删除)。", None)
|
|
bru(FF, "分片初始化", "分片上传-init", "POST", "/api/file/multipart/init", "form",
|
|
[("originalName", "string", "是", "-", "原始文件名", "视频演示.mp4"),
|
|
("totalSize", "long", "是", "-", "文件总大小(字节)", "104857600"),
|
|
("bizDomain", "string", "是", "-", "业务域", "lead"),
|
|
("uploadId", "string", "否", "-", "断点续传时传上次的 uploadId", "u-9001")],
|
|
"分片上传-init(带 uploadId 为断点续传,返回已上传分片序号列表)。",
|
|
[("uploadId", "string", "上传会话标识,后续 upload/complete 凭它"),
|
|
("chunkSize", "long", "分片大小(字节),除末片外每片必须为此值"),
|
|
("totalChunks", "int", "总片数"),
|
|
("uploadedChunks", "array<int>", "已上传分片序号列表(1 起),断点续传时只补缺失分片;新会话为空列表")],
|
|
{"uploadId": "u-9001", "chunkSize": 5242880, "totalChunks": 20, "uploadedChunks": [1, 2]})
|
|
bru(FF, "上传分片", "分片上传-上传单片", "POST", "/api/file/multipart/upload", "multipart",
|
|
[("uploadId", "string", "是", "-", "上传会话标识", "u-9001"),
|
|
("chunkNo", "int", "是", "-", "分片序号(1 起)", "3"),
|
|
("file", "file", "是", "-", "分片数据(非末片大小必须等于 init 下发的分片大小)", "chunk-3.bin")],
|
|
"分片上传-上传单片。", None, multipart_file="file")
|
|
bru(FF, "完成分片", "分片上传-complete", "POST", "/api/file/multipart/complete", "form",
|
|
[("uploadId", "string", "是", "-", "上传会话标识", "u-9001")],
|
|
"分片上传-complete(校验齐全后合并,返回文件详情)。", FILE_INFO_ROWS, FILE_INFO_EXAMPLE)
|
|
|
|
# ==================== collection.bru(全局契约摘要) ====================
|
|
collection = """auth {
|
|
mode: bearer
|
|
}
|
|
|
|
auth:bearer {
|
|
token: {{token}}
|
|
}
|
|
|
|
docs {
|
|
# CRM 接口全局契约
|
|
|
|
## 响应信封
|
|
所有接口统一返回 Result<T>:code / success / message / data;成败判定 `success == true`。HTTP 状态码业务失败也为 200,仅系统异常 500、路径不存在 404、授权拒绝 403。
|
|
|
|
## 错误码分段
|
|
0 成功;400xx 参数类;401xx 认证/身份;403xx 权限;404xx 资源不存在;405xx 请求方式;500xx 系统/外部调用;60001 通用业务错误;61001~61999 认证模块;62001~62999 文件模块;63001~63999 数据字典;64001~64999 公海池配置;65001~65999 线索业务。
|
|
|
|
## 传参约定
|
|
写操作 POST + 动作后缀,表单字段收参(application/x-www-form-urlencoded);读操作参数走 query;复杂字段(列表/对象)以 JSON 字符串放表单字段。Long 类型(雪花 ID)全局序列化为字符串,回传按字符串传。日期时间格式统一 `yyyy-MM-dd HH:mm:ss`(GMT+8)。
|
|
|
|
## 分页规范
|
|
分页入参继承 BaseParam(current=1 / size=10 上限 500 / keyword / orderBy / asc);分页出参 PageResult:content / total / size / current / pages / empty。
|
|
|
|
## 鉴权
|
|
请求头 `Authorization: Bearer {token}`;免鉴权:/api/auth/login/**、/api/auth/debug/token(verify 环境)。登录接口响应后自动写入环境变量 token。
|
|
|
|
## 页面归属规范(ADR-0024:菜单粒度)
|
|
接口按方法级 `@Operation(tags)` 打菜单 tag,tag 名即菜单文件夹(含 / 按目录嵌套),命名以产品原型菜单为准;无 tag 接口按 URL `/api/{module}/{resource}` 回退。一个接口挂 N 个菜单就在 N 个文件夹各有一份副本;线索域四菜单共用同一套接口,副本按 bruno-sync.config.json 的 menuBindings 绑定专属参数值:
|
|
线索公海=viewType:PUBLIC_POOL / scopeKey:lead.public_pool · 我的线索=MY_LEAD / lead.my_lead · 我的关注=MY_FOLLOW / lead.my_follow · 线索管理=MANAGE / lead.manage
|
|
副本由生成器幂等维护(重跑覆盖),手改会在下次同步时丢失。
|
|
}
|
|
"""
|
|
w("collection.bru", collection)
|
|
|
|
# ==================== 报告 ====================
|
|
print("=== 生成完成 ===")
|
|
total = 0
|
|
for folder, n in sorted(SEQ.items()):
|
|
print(" %-30s %d 个接口" % (folder, n))
|
|
total += n
|
|
print("合计 %d 个 .bru + collection.bru + environments/local.bru" % total)
|
|
|