318 lines
15 KiB
Python
318 lines
15 KiB
Python
# -*- 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)
|