feat: 根目录文档、脚本、gitignore

This commit is contained in:
2026-10-08 16:15:33 +08:00
commit e98660ce4e
284 changed files with 26838 additions and 0 deletions
+317
View File
@@ -0,0 +1,317 @@
# -*- 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)