Files
zentao-flow/tmp/gen_api_doc.py
T

318 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""生成《禅道 AI 通道接口文档》Word 对外交付版(3 接口 + token,不含 login)"""
from docx import Document
from docx.shared import Pt, RGBColor, Cm
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml.ns import qn
TOKEN = ("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9."
"eyJhY2NvdW50IjoiYWkiLCJwYXNzd29yZCI6ImUxMGFkYzM5NDliYTU5YWJiZTU2ZTA1N2YyMGY4ODNlIiwidXNlclR5cGUiOjN9."
"d43vA9pN_dhwwfeO0zg3_AaX76vEMXRYFZSc6AkYF7c")
doc = Document()
# 全局字体:正文宋体/Calibri,标题黑体
style = doc.styles['Normal']
style.font.name = 'Calibri'
style.font.size = Pt(10.5)
style.element.rPr.rFonts.set(qn('w:eastAsia'), '宋体')
def set_cn_font(run, name='宋体'):
run.font.name = name
run._element.rPr.rFonts.set(qn('w:eastAsia'), name)
def heading(text, level):
h = doc.add_heading(text, level=level)
for r in h.runs:
set_cn_font(r, '黑体')
r.font.color.rgb = RGBColor(0, 0, 0)
return h
def para(text, bold=False, size=10.5, color=None):
p = doc.add_paragraph()
r = p.add_run(text)
r.bold = bold
r.font.size = Pt(size)
if color:
r.font.color.rgb = color
set_cn_font(r)
return p
def code_block(text):
p = doc.add_paragraph()
p.paragraph_format.left_indent = Cm(0.5)
p.paragraph_format.space_before = Pt(4)
p.paragraph_format.space_after = Pt(4)
r = p.add_run(text)
r.font.name = 'Consolas'
r.font.size = Pt(9)
r._element.rPr.rFonts.set(qn('w:eastAsia'), 'Consolas')
return p
def table(headers, rows, widths=None):
t = doc.add_table(rows=1 + len(rows), cols=len(headers))
t.style = 'Table Grid'
for i, h in enumerate(headers):
cell = t.rows[0].cells[i]
cell.text = ''
r = cell.paragraphs[0].add_run(h)
r.bold = True
r.font.size = Pt(9.5)
set_cn_font(r)
for ri, row in enumerate(rows):
for ci, val in enumerate(row):
cell = t.rows[ri + 1].cells[ci]
cell.text = ''
r = cell.paragraphs[0].add_run(str(val))
r.font.size = Pt(9.5)
set_cn_font(r)
if widths:
for ci, w in enumerate(widths):
for row in t.rows:
row.cells[ci].width = Cm(w)
doc.add_paragraph()
return t
# ================= 封面信息 =================
title = doc.add_paragraph()
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
r = title.add_run('禅道 AI 通道接口文档')
r.bold = True
r.font.size = Pt(22)
set_cn_font(r, '黑体')
sub = doc.add_paragraph()
sub.alignment = WD_ALIGN_PARAGRAPH.CENTER
r = sub.add_run('(AI 框架 × 禅道系统 对接接口 · 对外提供版)')
r.font.size = Pt(12)
set_cn_font(r)
info = doc.add_paragraph()
info.alignment = WD_ALIGN_PARAGRAPH.CENTER
r = info.add_run('版本:v1.0 日期:2026-08-07 密级:内部(含永久令牌,注意保管)')
r.font.size = Pt(10)
set_cn_font(r)
doc.add_paragraph()
# ================= 1 接入信息 =================
heading('1. 接入信息', 1)
heading('1.1 服务地址', 2)
table(['环境', 'Base URL', '说明'],
[['测试环境', 'http://127.0.0.1:8085/zentao', '本地/联调使用'],
['生产环境', 'https://itsm.sino-assist.com/zentao', '正式环境']],
widths=[3, 8, 5])
para('所有接口路径均以 Base URL 为前缀拼接;请求与响应均为 UTF-8 编码。')
heading('1.2 鉴权方式(重要)', 2)
para('所有接口必须在请求头中携带访问令牌(token):')
code_block('Authorization: <token>')
para('注意:直接放 token 原文,不要加 "Bearer " 前缀。', bold=True)
para('本令牌为 AI 专用账号(账号名:ai)的永久令牌,不会过期,请妥善保管、勿外泄、勿提交到代码仓库:', bold=True)
code_block(TOKEN)
para('未携带或令牌无效时,接口返回:{"code":-1,"message":"请登录"}')
heading('1.3 统一响应格式', 2)
para('所有接口返回统一 JSON 结构:')
code_block('{\n "code": 0, // 0=成功;-1=失败(message 为失败原因)\n "message": "成功",\n "data": { ... } // 各接口不同,可能为 null\n}')
# ================= 2 接口一览 =================
heading('2. 接口一览', 1)
table(['#', '接口名称', '方法', '路径', '提交方式', '用途'],
[['1', '评估结果提交', 'POST', '/zentao/zt-story-expand/saveOrUpdate', 'JSON',
'提交需求的工作量评估指标(单元数量/复杂度/系数/工时)与验收标准'],
['2', 'AI 批量创建任务', 'POST', '/zentao/zt-task/aiBatchAdd', 'JSON',
'为指定需求批量创建研发/测试任务,重复任务自动跳过'],
['3', '文件上传并绑定', 'POST', '/zentao/common/uploadBind', 'multipart/form-data',
'上传文档并一步绑定到需求/会议,页面即时可见']],
widths=[1, 3.2, 1.6, 5.5, 3, 5])
# ================= 3 saveOrUpdate =================
heading('3. 接口一:评估结果提交 saveOrUpdate', 1)
code_block('POST {BaseURL}/zt-story-expand/saveOrUpdate\n'
'Content-Type: application/json\n'
'Authorization: <token>')
para('用途:需求评估完成后,将工作量评估指标与验收标准写入需求扩展信息。同一需求重复提交为覆盖更新(幂等)。')
heading('3.1 请求参数', 2)
table(['参数', '类型', '必填', '说明'],
[['storyId', 'Integer', '是', '需求 ID'],
['numberUnits', 'Integer', '评估时必填', '单元数量 S'],
['unitBusinessComplexity', 'String', '评估时必填', '单元业务复杂度 B,如 "2.2"'],
['technicalComplexityCoefficient', 'String', '评估时必填', '技术复杂度系数 F(T),如 "1.4"'],
['aiEfficiencyCoefficient', 'String', '评估时必填', 'AI 效率系数 G(A),如 "0.55"'],
['evaluationTime', 'Number', '评估时必填', '评估工时 W(人日),如 5.1'],
['workloadIndex', 'String', '否', '工作量指数'],
['requirementStatus', 'String', '否', 'inProgress(默认)/ finished;传 finished 表示需求完成,将锁定记录并结算当月工作量'],
['requirementCompletionDegree', 'String', '否', '完成度 "0"~"100";finished 时自动置 100'],
['acceptanceCriteria', 'String', '否', '验收标准(Markdown 文本)'],
['productPerson', 'String', '否', '产品人员(中文名)'],
['developPerson', 'String', '否', '开发人员(中文名)'],
['testPerson', 'String', '否', '测试人员(中文名)']],
widths=[4.5, 2, 2.2, 8])
heading('3.2 请求示例', 2)
code_block('{\n'
' "storyId": 9130,\n'
' "numberUnits": 3,\n'
' "unitBusinessComplexity": "2.2",\n'
' "technicalComplexityCoefficient": "1.4",\n'
' "aiEfficiencyCoefficient": "0.55",\n'
' "evaluationTime": 5.1,\n'
' "workloadIndex": "5.1",\n'
' "developPerson": "张三",\n'
' "testPerson": "李四"\n'
'}')
heading('3.3 响应示例', 2)
code_block('{ "code": 0, "message": "成功", "data": null }')
heading('3.4 注意事项', 2)
for t in ['storyId 为空时接口返回成功但不做任何处理(请务必确认已传 storyId);',
'需求一旦置为 finished,记录即锁定,后续提交将被拒绝(提示"该需求已完成,不可再修改");',
'同一 storyId 重复提交 = 覆盖更新,不会产生重复记录。']:
p = doc.add_paragraph(t, style='List Bullet')
for r in p.runs:
set_cn_font(r)
r.font.size = Pt(10)
# ================= 4 aiBatchAdd =================
heading('4. 接口二:AI 批量创建任务 aiBatchAdd', 1)
code_block('POST {BaseURL}/zt-task/aiBatchAdd\n'
'Content-Type: application/json\n'
'Authorization: <token>')
para('用途:为指定需求批量创建研发/测试任务。已存在的同名同类型任务自动跳过(防重),并在需求与任务的操作记录中留痕。')
heading('4.1 请求参数', 2)
table(['参数', '类型', '必填', '说明'],
[['storyId', 'Integer', '是', '需求 ID;需求不存在则整批拒绝'],
['tasks', 'Array', '是', '任务列表,不能为空'],
['tasks[].name', 'String', '是', '任务名称'],
['tasks[].type', 'String', '是', '任务类型:devel=开发 / test=测试,其他值整批拒绝'],
['tasks[].assignedTo', 'String', '否', '指派人账号(登录账号,非中文名)'],
['tasks[].aiEvaluationTime', 'Float', '否', 'AI 评估工时(小时),写入任务预计工时'],
['tasks[].planStartDate', 'String', '否', '预计开始日期,格式 yyyy-MM-dd'],
['tasks[].deadline', 'String', '否', '预计完成日期,格式 yyyy-MM-dd']],
widths=[4.5, 2, 2.2, 8])
heading('4.2 请求示例', 2)
code_block('{\n'
' "storyId": 9130,\n'
' "tasks": [\n'
' {"name": "后端接口开发", "type": "devel", "assignedTo": "zhangsan",\n'
' "aiEvaluationTime": 8, "planStartDate": "2026-08-10", "deadline": "2026-08-11"},\n'
' {"name": "接口测试", "type": "test", "assignedTo": "lisi",\n'
' "aiEvaluationTime": 4, "planStartDate": "2026-08-12", "deadline": "2026-08-12"}\n'
' ]\n'
'}')
heading('4.3 响应示例', 2)
code_block('{\n'
' "code": 0,\n'
' "message": "成功",\n'
' "data": {\n'
' "created": 2, // 实际新建任务数\n'
' "taskIds": [18565, 18566], // 新建任务 ID 列表\n'
' "skipped": ["接口测试"] // 因重名同类型被跳过的任务名\n'
' }\n'
'}')
heading('4.4 注意事项', 2)
for t in ['校验规则为"整批拒绝":任一任务不合法(类型错误/名称为空/日期格式错误等),本批全部不创建;',
'防重规则:同一需求下已存在同名且同类型(未删除)的任务 → 跳过并记入 skipped,不算失败;',
'全部命中防重时返回 code:0、created:0,skipped 列出全部任务名;',
'新任务初始状态为"未开始",创建人显示为 ai,操作记录可在需求/任务历史中查看。']:
p = doc.add_paragraph(t, style='List Bullet')
for r in p.runs:
set_cn_font(r)
r.font.size = Pt(10)
# ================= 5 uploadBind =================
heading('5. 接口三:文件上传并绑定 uploadBind', 1)
code_block('POST {BaseURL}/common/uploadBind\n'
'Content-Type: multipart/form-data\n'
'Authorization: <token>')
para('用途:上传文件并一步绑定到业务对象(需求/会议):文件入库的同时,自动刷新业务对象的文档链接字段,'
'并在其操作记录中留痕。需求详情页、会议纪要页等界面即时可见。')
heading('5.1 请求参数(表单字段)', 2)
table(['参数', '类型', '必填', '说明'],
[['file', 'File', '是', '上传的文件(支持 .md 等常见格式)'],
['objectType', 'String', '是', '业务对象类型,取值见 5.2 对照表'],
['objectId', 'Integer', '是', '业务对象 ID(需求 ID 或会议 ID)'],
['title', 'String', '否', '文件标题;不传默认取原始文件名(支持中文)'],
['reviewResult', 'String', '否', '仅 objectType=aiCodeReview 时有效:pass=审查通过 / reject=审查不通过']],
widths=[3, 2, 2.2, 9.3])
heading('5.2 objectType 取值对照表', 2)
table(['objectType', '含义', '绑定后效果'],
[['story', '需求文档(PRD 等)', '需求文档链接更新'],
['aiCodeReview', '代码审查报告', '审查报告链接更新;带 reviewResult 时同步审查状态'],
['aiWorkLog', '工作日志', '工作日志链接更新'],
['aiDocUpdate', 'AI 项目文档更新记录', '更新记录链接更新'],
['testCase', '测试用例', '测试用例链接更新'],
['testReport', '测试报告模版', '报告模版链接更新'],
['testReportSubmit', '测试报告提交', '报告提交链接更新(SOP 流程卡点依据)'],
['testOther', '其他测试文档', '其他文档链接更新'],
['meeting', '会议纪要', '会议纪要链接更新(objectId 填会议 ID)']],
widths=[3.5, 4.5, 8.5])
heading('5.3 请求示例', 2)
code_block('curl -X POST "http://127.0.0.1:8085/zentao/common/uploadBind" \\\n'
' -H "Authorization: <token>" \\\n'
' -F "file=@代码审查报告.md" \\\n'
' -F "objectType=aiCodeReview" \\\n'
' -F "objectId=9130" \\\n'
' -F "title=代码审查报告-v1" \\\n'
' -F "reviewResult=pass"')
heading('5.4 响应示例', 2)
code_block('{\n'
' "code": 0,\n'
' "message": "成功",\n'
' "data": {\n'
' "id": 1234,\n'
' "title": "代码审查报告-v1",\n'
' "extension": ".md",\n'
' "size": 5321,\n'
' "url": "/zentao/img/20260806170215xxxx.md", // 文件访问地址(相对路径)\n'
' "objecttype": "aiCodeReview",\n'
' "objectid": 9130,\n'
' "addedby": "ai",\n'
' "addeddate": "2026-08-06 17:02:15"\n'
' }\n'
'}')
heading('5.5 注意事项', 2)
for t in ['同一对象同一类型可多次上传形成文件列表,业务对象上的链接字段始终指向最新一份;',
'objectType/objectId 错误、对象不存在、类型不在对照表内,均会拒绝且不落盘(无残留);',
'返回的 url 为相对路径,访问时拼在系统域名后即可打开/下载。']:
p = doc.add_paragraph(t, style='List Bullet')
for r in p.runs:
set_cn_font(r)
r.font.size = Pt(10)
# ================= 6 错误码 =================
heading('6. 常见返回码速查', 1)
table(['code', 'message 示例', '含义与处理'],
[['0', '成功', '调用成功'],
['-1', '请登录', '未带 token 或 token 无效 → 检查 Authorization 头'],
['-1', '该接口仅AI框架通道可用(需ai账户token)', 'token 不是 ai 账号令牌 → 换用本文档 1.2 节令牌'],
['-1', '该需求已完成,不可再修改', '需求已 finished 锁定(接口一的锁定机制)'],
['-1', '需求不存在:xxx / 会议不存在:xxx', 'objectId 或 storyId 对应记录不存在 → 核对 ID'],
['-1', 'uploadBind不支持的objectType:xxx', 'objectType 取值错误 → 对照 5.2 表'],
['-1', '任务类型仅支持devel/test:xxx', '任务类型取值错误(接口二)'],
['-1', '日期格式错误,应为yyyy-MM-dd:xxx', '日期格式错误(接口二)']],
widths=[1.5, 7, 8])
doc.add_paragraph()
tail = doc.add_paragraph()
r = tail.add_run('—— 本文档含永久访问令牌,仅限授权对接人员持有,请勿外传 ——')
r.font.size = Pt(9)
r.font.color.rgb = RGBColor(0x99, 0x99, 0x99)
set_cn_font(r)
OUT = r'F:\zentao\1\PM\prds\ai-sop-20260723-1024\outputs\禅道AI通道接口文档_v1.0.docx'
doc.save(OUT)
print('saved:', OUT)