审批流程设计


1. 介绍

在银行相关业务场景中, 为了保护客户账户信息或资金的安全, 业务的开展往往需要经过严格的审批流程, 只有经过高级经理或行长审批完成后才允许业务交互核心生效. 而不同级别的部门又存在不同场景的业务, 审批流程又不尽相同, 这时候就需要一套完整的审批流程系统来进行统一调度和管理. 在我的工作经验中, 很多银行项目都会采用工作流workflow技术进行流程设计, 这是一个可行的流程设计框架. 但我今天要写的方案不是采用框架实现, 而是只用db设计来控制审批流程的走向, 为了适应不同场景的审批流程, 将设计成可配置化的审批节点.

2. 功能描述

在实际审批流程的业务场景的, 会附属很多扩展功能来给审批功能注入灵魂, 实现业务的电子痕迹管理. 主要功能涉及以下几个方面:

  • 审批任务推送
  • 审批任务领取
  • 已审批任务查看
  • 查询下一审批节点
  • 查询下一审批任务的权限审批用户
  • 审批任务未提交归还
  • 审批任务提交
  • 查看审批意见
  • 查看退回理由
  • 审批任务未领取, 进行撤回
  • 审批流程取消
  • 审批流程跟踪查询

3. 库表设计

3.1 流程编号历史表

表名: MY_PROCESS_NO_HISTORY_INFO

字段英文名字段中文名数据类型/长度约束备注
ID非业务主键VARCHAR(32)主键
PROCESS_NO流程编号VARCHAR(32)
TRACE_NO业务受理号VARCHAR(32)
CREATE_TIME创建时间TIMESTAMP

当业务需要定制化流程编号规则时, 就不能使用UUID, DB序列号或主键自增的方式获取, 需要根据业务指定的规则创建, 为了保证多线程请求时, 生成的流程编号的唯一性, 使用流程编号历史表进行管理.

3.2 流程节点参数表

表名: MY_NODE_ROLE_PARAM

字段英文名字段中文名数据类型/长度约束备注
BUSI_TYPE业务类型VARCHAR(2)联合主键BUSI_TYPE+NODE_ID
NODE_ID节点IDVARCHAR(32)联合主键BUSI_TYPE+NODE_ID
PRE_NODE_ID上一节点IDVARCHAR(32)
NEXT_NODE_ID下一节点IDVARCHAR(32)
ROLE_ID角色IDVARCHAR(2)
ROLE_NAME角色名称VARCHAR(100)
FUN_CODEPC端功能码VARCHAR(12)
EXTERNAL_FUN_CODE移动端功能码VARCHAR(12)
IS_LAST_NODE是否最后节点VARCHAR(2)默认N
CREATE_TIME创建时间TIMESTAMP

流程节点参数表, 用来维护审批流程的节点信息, 审批流程按照表里维护的节点顺序进行即可.

3.3 业务类型参数表

表名: MY_BUSI_PARAM

字段英文名字段中文名数据类型/长度约束备注
BUSI_TYPE业务类型IDVARCHAR(2)主键
BUSI_NAME业务类型名称(简体中文)VARCHAR(100)
BUSI_NAME_EN业务类型名称(英文)VARCHAR(100)
BUSI_NAME_HK业务类型名称(繁体中文)VARCHAR(100)

业务类型参数表, 用来记录业务类型的枚举-枚举值, 相当于码表, 进行业务类型的统一管理.

3.4 流程申请信息主表

表名: MY_PROCESS_INFO

字段英文名字段中文名数据类型/长度约束备注
PROCESS_NO流程编号VARCHAR(32)主键
BUSI_TYPE业务类型VARCHAR(2)
TRACE_NO业务受理号VARCHAR(32)
TLR_ID经办柜员号VARCHAR(7)
TLR_NAME经办柜员名称VARCHAR(100)
ORG_CODE经办机构号VARCHAR(11)
ORG_NAME经办机构名称VARCHAR(100)
BRANCH_CODE省直分行号VARCHAR(11)
BRANCH_NAME省直分行名称VARCHAR(100)
SUB_BRANCH_CODE辖属分行号VARCHAR(11)
SUB_BRANCH_NAME辖属分行名称VARCHAR(100)
APPLY_DATE申请会计日期VARCHAR(8)yyyyMMdd
BUSI_STATUS业务流程状态VARCHAR(2)0-流程进行中 3-注销 4-审批完成 5-驳回
DOC_ID影像IDVARCHAR(32)
ACC_DATE记账会计日期VARCHAR(8)
VCH_NO记账流水号VARCHAR(32)
CREATE_TIME创建时间TIMESTAMP
FINISH_TIME完成时间TIMESTAMP
OLD_PROCESS_NO旧流程编号VARCHAR(32)对已完成流程的业务做修改或删除申请时使用
EXIST_SIGN是否会签VARCHAR(2)Y-是 N-否, 默认N
CHANNEL_TYPE申请渠道VARCHAR(2)1-柜面端 2-移动端
REMAKR1备用字段VARCHAR(100)

流程申请信息主表, 用来记录发起业务申请流程的基本信息, 包含发起的用户, 部门, 时间, 导入影像等信息. 通过流程状态控制业务流程的完成度.

3.5 业务交易信息表

表名: MY_TRADE_INFO

字段英文名字段中文名数据类型/长度约束备注
IDIDVARCHAR(32)主键非业务主键
PROCESS_NO流程编号VARCHAR(32)
SEQ_NO序号VARCHAR(10)
ACCOUNT账号VARCHAR(32)查询用
TRADE_INFO交易信息VARCHAR(7000)Json字符串
CREATE_TIME创建时间TIMESTAMP
REMARK1备用字段VARCHAR(100)

这是一张公共交易信息表, 不同界面的交易信息都可以封装成Json字符串存在TRADE_INFO字段中, 如果涉及到条件查询功能就需要单独设计分录表来存储定制化的交易信息.

3.6 任务信息表

表名: MY_TASK_INFO

字段英文名字段中文名数据类型/长度约束备注
TASK_ID任务IDVARCHAR(32)主键
APP_CODE系统编号VARCHAR(8)
PROCESS_NO流程编号VARCHAR(32)
LAUNCH_DATE发起会计日期VARCHAR(8)yyyyMMdd
SIGN_NO会签子流程编号VARCHAR(32)会签任务才会有该字段信息
DEAL_CONTENT任务节点名称VARCHAR(300)
TASK_EFFECT_DATE任务时效日期VARCHAR(8)yyyyMMdd
FUNC_CODE功能码VARCHAR(12)
TASK_STATUS任务状态VARCHAR(2)0-已办 1-待办 2-取消
PARAMTERSParamters参数VARCHAR(1000)Json字符串
CREATE_ORG_CODE发起机构号VARCHAR(11)
CREATE_ORG_NAME发起机构名称VARCHAR(300)
CREATE_BRANCH_CODE发起分行号VARCHAR(11)
CREATE_BRANCH_NAME发起分行名称VARCHAR(300)
CREATE_TIME创建时间TIMESTAMP

任务信息表用户记录审批流程中推送的任务(包含会签任务), 并根据审批结果记录任务的状态, 客户端的体现就是任务池, 任务池可以设计成待办任务池和已办任务池; 未处理的任务展示在待办任务池中, 已经处理的任务展示在已办任务池中, 取消状态的任务不做展示. 其中PARAMTERS字段可以根据业务场景存放自己需要的扩展字段, 比如: processNo, taskId, nodeId, roleId, taskOwner, IsApply等.

3.7 任务用户信息表

表名: MY_TASK_USER_INFO

字段英文名字段中文名数据类型/长度约束备注
TASK_ID任务IDVARCHAR(32)联合主键TASK_ID+USER_CODE
USER_CODE权限用户VARCHAR(7)联合主键TASK_ID+USER_CODE
TASK_FINAL_STATUS用户任务状态VARCHAR(2)0-已办 1-待办 2-取消
UPDATE_TIME时间戳TIMESTAMP

这张表用来记录审批任务推送的具体权限用户, 将任务主体信息与用户进行关联. 并通过用户任务状态管理用户对任务的处理权限. 任务的领取要做抢占式领取,防止同一笔任务的重复提交; 任务的释放要做抢占式释放, 防止任务无法处理.

抢占式领取: 任务同时推送给有权限的A, B用户, 如果此时A用户领取了任务, 那么需要将B用户的任务状态改为取消, 效果就是B用户在待办任务池看不到该笔任务, 防止两人同时领取进行重复提交.

抢占式释放: 任务同时推送给有权限的A, B用户, 如果此时A用户领取了任务, A用户没有做提交操作, 直接退出界面, 这时需要将A, B用户的用户任务状态都改为待办状态, 效果就是将这笔任务归还到待办任务池, A, B用户都可以再次在待办任务池中看到这笔任务.

3.8 流程跟踪信息表

表名: MY_PROCESS_TRACK_INFO

字段英文名字段中文名数据类型/长度约束备注
IDIDVARCHAR(32)非业务主键
TASK_ID任务IDVARCHAR(32)
PROCESS_NO流程编号VARCHAR(32)
NODE_ID节点IDVARCHAR(32)
ROLE_ID角色IDVARCHAR(2)
ROLE_NAME角色名称VARCHAR(300)
TASK_RECEIVE_TIME任务领取时间TIMESTAMP
TASK_COMMIT_TIME任务提交时间TIMESTAMP
TLR_ID任务提交柜员号VARCHAR(7)
TLR_NAME任务提交柜员名称VARCHAR(300)
ORG_CODE机构号VARCHAR(11)
ORG_NAME机构名称VARCHAR(300)
BRANCH_CODE分行号VARCHAR(11)
BRANCH_NAME分行名称VARCHAR(300)
CREATE_TIME创建时间TIMESTAMP
CHANNEL_TYPE渠道类型VARCHAR(2)1-PC端 2-移动端

这张表用来记录每一笔任务的操作痕迹信息, 用在流程跟踪信息电子化追踪查询的功能中.

3.9 审批意见信息表

表名: MY_JUDGE_INFO

字段英文名字段中文名数据类型/长度约束备注
IDIDVARCHAR(32)非业务主键
PROCESS_NO流程编号VARCHAR(32)
TLR_ID审批提交柜员号VARCHAR(7)
TLR_NAME审批提交柜员名称VARCHAR(300)
ORG_CODE机构号VARCHAR(11)
ORG_NAME机构名称VARCHAR(300)
BRANCH_CODE分行号VARCHAR(11)
BRANCH_NAME分行名称VARCHAR(300)
APPROVE_RSLT审批结果VARCHAR(2)
SUGGEST审批意见/退回理由VARCHAR(1000)Y-审批通过;N-审批拒绝
NODE_ID审批节点IDVARCHAR(32)
ROLE_ID审批角色IDVARCHAR(2)
ROLE_NAME审批角色名称VARCHAR(300)
CREATE_TIME创建时间TIMESTAMP

这张表用来记录权限用户在审批界面填写的审批意见或退回理由, 提供审批意见信息查询.

3.10 会签流程信息表

表名: MY_SIGN_PROCESS_INFO

字段英文名字段中文名数据类型/长度约束备注
IDIDVARCHAR(32)
BUSI_TYPE业务类型VARCHAR(2)
SIGN_NO会签子流程编号VARCHAR(32)
PROCESS_NO主流程编号VARCHAR(32)可能关联多个会签子流程
ORG_CODE机构号VARCHAR(11)
ORG_NAME机构名称VARCHAR(300)
TLR_ID柜员号VARCHAR(7)
TLR_NAME柜员名称VARCHAR(300)
CS_ORG_CODE会签机构号VARCHAR(11)主流程可能关联多个会签机构
NODE_ID节点IDVARCHAR(32)
ROLE_ID角色IDVARCHAR(2)
ROLE_NAME角色名称VARCHAR(300)
SIGN_STATUS会签状态VARCHAR(2)
CS_FUN_CODE会签功能码VARCHAR(12)
CREATE_TIME创建时间TIMESTAMP
CHANNEL_TYPE渠道类型VARCHAR(2)1-PC端 2-移动端

这张表用来控制会签子流程的进行, 会签就是将审批任务的内容推送给部门中的用户传阅, 会签完成后继续回到主流程审批. 在中间的某个审批节点可以由用户主动选择发起会签或根据业务规定在指定的审批节点自动发起会签, 发起会签时需要选择会签的部门(可以选择多个会签部门), 每个会签部门都是一个会签子流程, 然后会签部门主管再将会签任务提交到部门内部传阅, 每个会签任务ID都不一样, 当每个部门内部会签任务完成, 当前会签子流程结束; 当所有会签子流程完成, 才允许回到主流程继续流转审批, 直至流程结束. 会签流程的控制需要结合会签流程信息表, 任务用户信息表, 任务用户信息表, 流程跟踪信息表等.

会签节点: 会签发起, 会签流转(审核)

3.11 db关联关系图

在这里插入图片描述

4. 接口设计

审批流程功能需要合理的接口设计才能保证流程的正常流转和痕迹追踪. 下面是公共接口设计:

公共响应头:

字段中文名字段英文名数据类型长度必输说明
响应头replyInformationObject->Map
返回类型responseTypeString1E-失败;N-成功
返回码responseCodeString12
返回码responseMsgString100

4.1 获取流程编号 [getProcessNo]

请求字段

字段中文名字段英文名数据类型长度必输说明
柜员号tlrIdString7
机构号orgCodeString11
功能码funCodeString12

返回字段

字段中文名字段英文名数据类型长度必输说明
响应头replyInformationObject->Map
流程编号processNoString32

4.2 获取下一审批节点/机构信息 [getNextNodeOrg]

请求字段

字段中文名字段英文名数据类型长度必输说明
流程编号processNoString32isApply为Y时非必输, 其他情况都必输
柜员号tlrIdString7
机构号orgCodeString11
节点IDnodeIdString32
是否发起节点isApplyString1Y-是 ; N-否
业务类型busiTypeString2

返回字段

字段中文名字段英文名数据类型长度必输说明
响应头replyInformationObject->Map
审批节点信息roleList循环开始树结构
角色名称roleNameString300
下一角色IDnextRoleIdString2
下一节点IDnextNodeIdString32
下一节点类型nextNodeTypeString1F-正向; B-退回
父机构parentNodeList循环开始树结构
父机构IDparentOrgCodeString11
父机构名称parentOrgNameString300
子机构列表children循环开始
子机构IDorgCodeString11
子机构名称orgNameString300
子机构列表children循环结束
父机构parentNodeList循环结束
审批节点信息roleList循环结束

4.3 获取下一审批用户列表信息 [getReceiveUserList]

请求字段

字段中文名字段英文名数据类型长度必输说明
机构号orgCodeString11
柜员号tlrIdString7
下一节点IDnextNodeIdString32如果funCode为空,就按照节点ID查询功能码
下一审批机构号nextOrgCodeString11
业务类型busiTypeString2
查询功能码funCodeString12

返回字段

字段中文名字段英文名数据类型长度必输说明
响应头replyInformationObject->Map
用户信息列表userList循环开始
用户编号userCodeString7
用户名称userNameString100
用户信息列表userList循环结束

4.4 流程发起提交 [processLaunch]

请求字段

字段中文名字段英文名数据类型长度必输说明
流程编号processNoString32
机构号orgCodeString11
柜员号tlrIdString7
柜员名称tlrNameString100
业务类型busiTypeString2
下一角色IDnextRoleIdString2
下一节点IDnextNodeIdString32
下一机构IDnextOrgCodeString11
下一审批用户列表approveUserList循环开始
审批用户编号aprUserCodeString7
审批用户名称aprUserNameString100
下一审批用户列表approveUserList循环结束
影像IDdocIdString32
交易信息tradeInfoListString或List2000json字符串方式使得不同界面共用该字段; 但是如果是大数量的列表,还是使用List
是否保存退出isSaveString1Y-是; N-否
是否需要会签existSignString1Y-是; N-否

返回字段

字段中文名字段英文名数据类型长度必输说明
响应头replyInformationObject->Map

4.5 待办任务领取 [taskReceive]

请求字段

字段中文名字段英文名数据类型长度必输说明
柜员号tlrIdString7
柜员名称tlrNameString100
是否已办任务doneStatusbooleantrue-已办; false-待办
流程编号processNoString32
任务IDtaskIdString32

返回字段

字段中文名字段英文名数据类型长度必输说明
响应头replyInformationObject->Map
功能码funCodeString12
申请人applyTlrString7
申请人名称applyTlrNameString100
申请机构applyOrgCodeString11
申请机构名称applyOrgNameString100
节点IDnodeIdString32
影像IDdocIdString32
交易信息tradeInfoListString2000

4.6 已办任务查看 [tradeInfoDisplay]

合并到taskReceive接口中, 使用doneStatus字段区分待办任务和已办任务.

4.7 任务归还 [taskReturn]

请求字段

字段中文名字段英文名数据类型长度必输说明
柜员号tlrIdString7
机构号orgCodeString11
流程编号processNoString32
任务IDtaskIdString32

返回字段

字段中文名字段英文名数据类型长度必输说明
响应头replyInformationObject->Map

4.8 流程审批提交 [processApprove]

请求字段

字段中文名字段英文名数据类型长度必输说明
柜员号tlrIdString7
柜员名称tlrNameString100
机构号orgCodeString11
机构名称orgNameString100
流程编号processNoString32
任务IDtaskIdString32
当前节点IDnodeIdString32
审批结果approveRsltString1Y-审批通过; N-审批拒绝
下一角色IDnextRoleIdString2
下一节点IDnextNodeIdString32
下一审批机构/用户 nextTaskOwnerList 循环开始
下一任务归属机构/用户taskOwnerString11可以是柜员号或机构号
下一审批机构/用户 nextTaskOwnerList 循环结束
退回理由rejectReasonString300审批结果为”N”时必输.
审批意见suggestString300审批结果为”Y”是必输
业务类型busiTypeString2
交易信息tradeInfoListString或List1000json字符串方式使得不同界面共用该字段; 但是如果是大数量的列表,还是使用List

返回字段

字段中文名字段英文名数据类型长度必输说明
响应头replyInformationObject->Map

4.9 审批意见查看 [apSuggestQuery]

请求字段

字段中文名字段英文名数据类型长度必输说明
流程编号processNoString32

返回字段

字段中文名字段英文名数据类型长度必输说明
响应头replyInformationObject->Map
审批意见列表suggestList循环开始
机构号orgCodeString11
机构名称orgNameString100
角色名称roleNameString100
审批意见suggestString300
审批时间approveTimeString100
审批人tlrIdString7
审批人名称tlrNameString100
审批结果approveRsltString1Y-审批通过;N-审批拒绝
审批意见列表suggestList循环结束

4.10 退回理由查看 [backReasonQuery]

请求字段

字段中文名字段英文名数据类型长度必输说明
流程编号processNoString32

返回字段

字段中文名字段英文名数据类型长度必输说明
响应头replyInformationObject->Map
退回理由列表rejectReasonList循环开始
机构号orgCodeString11
机构名称orgNameString100
角色名称roleNameString100
退回理由rejectReasonString300
退回时间rejectTimeString100
审批结果approveRsltString1Y-审批通过;N-审批拒绝
退回人tlrIdString7
退回人名称tlrNameString100
退回理由列表rejectReasonList循环结束

如果要将审批意见和退回理由合并展示, 也可以提供一个接口,将上面两个接口逻辑合并即可.

4.11 流程取消(注销) [processCancel]

请求字段

字段中文名字段英文名数据类型长度必输说明
柜员号tlrIdString7
机构号orgCodeString11
流程编号processNoString32
任务IDtaskIdString32

返回字段

字段中文名字段英文名数据类型长度必输说明
响应头replyInformationObject->Map

4.12 流程撤回 [cancelTaskToLastNode]

请求字段

字段中文名字段英文名数据类型长度必输说明
柜员号tlrIdString7
柜员名称tlrNameString100
机构号orgCodeString11
机构名称orgNameString100
流程编号processNoString32
已办任务IDtaskIdString32
业务类型busiTypeString2

返回字段

字段中文名字段英文名数据类型长度必输说明
响应头replyInformationObject->Map

该接口的使用场景, A用户提交任务到下一个审批任务节点给B用户, B用户没有领取任务之前, A用户可以在已办任务界面将流程撤回上个审批节点, B用户的审批任务被取消, 并给A用户自己重新推送一个上个审批节点的审批任务.

任务撤回的功能场景:

场景1: 经办提交到初审

场景2: 初审提交到复审

场景3: 初审退回到经办

场景4: 复审提交到终审

场景5: 复审退回到经办

场景6: 复审退回到初审

场景7: 终审退回到经办

场景8: 终审退回到复审

需求分析:

1, 只能撤回下一节点(或退回上一节点或退回经办节点)的任务, 不能跨节点撤回(上下节点关系判断).

2, 最新任务状态校验, 已领取或已处理不能撤回

3, 撤回操作, 取消当前交易下一审批节点的待办任务(最新任务, 但不能是跨节点任务), 给当前已办柜员(撤回操作人)重新推送已办任务任务对应审批节点的待办任务.

4, 撤回操作完成, 取消当前的已办任务, 不能重复撤回.

5, 更新流程跟踪信息, 最新任务还没领取之前在流程跟踪记录表中没有记录, 需要将该笔任务的撤回操作记录在流程跟踪表.

4.13 查询下一审批节点的权限用户信息 [queryNextNodeTaskUserInfo]

可以在已办任务界面添加按钮, 触发查询下一审批任务的权限审批用户.

请求字段

字段中文名字段英文名数据类型长度必输说明
流程编号processNoString32
柜员号tlrIdString7
机构号orgCodeString11
业务类型busiTypeString2

返回字段

字段中文名字段英文名数据类型长度必输说明
响应头replyInformationObject->Map
流程最新任务归属机构taskOwnOrgCodeString11
流程最新任务归属机构名称taskOwnOrgNameString100
流程最新任务审批节点名称nodeNameString100
最新任务权限用户信息列表userInfoList循环开始
用户编号userCodeString7
用户名称userNameString100
最新任务权限用户信息列表userInfoList循环结束

4.14 流程跟踪信息查询 [processTrackQuery]

请求字段

字段中文名字段英文名数据类型长度必输说明
流程编号processNoString32

返回字段

字段中文名字段英文名数据类型长度必输说明
响应头replyInformationObject->Map
流程跟踪信息列表processTrackList循环开始
节点名称nodeNameString100
分行号branchCodeString11
分行名称branchNameString100
处理部门orgCodeString11
处理部门名称orgNameString100
领取任务时间taskReceiveTimeString20
提交任务时间taskCommitTimeString20
处理动作dealActionString100
处理人tlrIdString7
处理人名称tlrNameString100
流程跟踪信息列表processTrackList循环结束

5. 落地实现

就写几个涉及公共流程的接口思路.

5.1 获取下一审批节点/机构信息 [getNextNodeOrg]

在这里插入图片描述

5.2 获取下一审批用户列表信息 [getReceiveUserList]

在这里插入图片描述

根据功能码+机构号查询, 即查询机构下面具有功能码权限的用户. 涉及到功能码, 角色, 用户等表的关联查询. 功能码相当于界面访问权限, 绑定的是界面的url路径, 然后通过角色分配给用户. 下面是关系图:

在这里插入图片描述

5.3 流程发起提交 [processLaunch]

在这里插入图片描述
在这里插入图片描述

5.4 待办任务领取 [taskReceive]

在这里插入图片描述

5.5 任务归还 [taskReturn]

在这里插入图片描述

5.6 流程审批提交 [processApprove]

在这里插入图片描述

5.7 流程取消(注销) [processCancel]

在这里插入图片描述

6. 业务演示

涉及的数据库表及流程维护, 以下是测试数据, 不涉及客户数据.

1). 在MY_BUSI_PARAM表中新增维护一个业务类型, 比如46-批处理挂账后销账.
在这里插入图片描述

2). 在MY_NODE_ROLE_PARAM表中新增维护流程节点信息.

在这里插入图片描述

3). 提交一笔流程申请, 在MY_PROCESS_INFO主表生成一笔流程信息. apply_no就是process_no
在这里插入图片描述

4). 同时在MY_TRADE_INFO交易信息表保存界面的交易信息, 以Json字符串格式保存在TRADE_INFO字段中.
在这里插入图片描述

5). 同时推送审批任务给有权限的用户, 在MY_TASK_INFO任务信息表和MY_TASK_USER_INFO任务用户信息表保存任务信息.
在这里插入图片描述
在这里插入图片描述

6). 同时会生成一笔流程跟踪信息.
在这里插入图片描述

7). 后面的审批任务领取, 归还, 提交, 撤回等操作都是围绕这几张表进行.

7. 部分业务流程图

7.1 印鉴卡催缴

在这里插入图片描述

7.2 人行账户比对

在这里插入图片描述

7.3 客户信息下载

在这里插入图片描述

7.4 对公钱包限额调整

在这里插入图片描述

7.5 客户优惠利率维护

在这里插入图片描述


文章作者: 王子
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 王子 !
评论
  目录