简介:面向ERP实施顾问与Tiptop二次开发人员的集成参考资料,以新单据接入为例,讲解Tiptop与Easyflow(OA签核)之间如何通过XML实现单据签核的双向联动。文档从环境准备、数据表7个签核字段检查、Schema更新、单据性质与抛转信息维护,到4GL/PER程序调整,完整梳理了新单据接入Easyflow的12个关键步骤;同时对AWS公共模块中的配置、服务调用、日志与状态查询,以及对应各作业的抛转模块分工做了说明,并配有集成测试与常见错误列表,便于实施时按图索骥。读者可参照其中样例,完成单头单身栏位映射、生成并修改抛转作业源文件,并通过p_link/p_zz/p_ze完成菜单与提示信息配置,最终编译执行验证签核全链路。资源为1个PDF文件,大小1.94MB,内容以步骤说明、界面操作和配置要点为主,适合正在实施Tiptop OA集成或需要排查签核异常的技术人员参考。已有1515人学习下载。
1. 审核通过却进不了 OA 收件箱:Tiptop 与 Easyflow 集成的第一道坎
做过 Tiptop 与 Easyflow(OA)单据签核集成的开发者,多半都栽过同一个跟头:Tiptop 里点了审核,弹窗提示抛转成功,单据状态也变成了“S 送签中”,可打开易拓收件箱,里面空空如也;或者签核人明明在 Easyflow 里批完了,Tiptop 这边签核状态却纹丝不动,还停在送签中。这种“一半通一半不通”的集成问题最难缠——它不是某个单一环节出错,而是 Tiptop 端抛转、XML 组串、Easyflow 表单解析、回写服务四段链路里任何一处断掉都会出现同样症状,排查时经常一个环节查半天,最后发现根因在最不起眼的配置上。这篇笔记就按“先环境、再数据、后抛转、最后 OA 端”的顺序,把集成测试阶段最常见的报错和踩坑点一次说清。文中的步骤和代码均来自实际项目集成手册,照着改就能用。
2. 环境与前提排查:三个 IP 配置决定集成通不通
2.1 .profile 里的 EFSITEIP / EFSITENAME / EFSOAP 配错,抛转必然失败
Tiptop 与 Easyflow 的通信走的是 SOAP,Tiptop 端所有抛转动作最终都要通过 Appserver 去访问 Easyflow 的网关地址。而这个地址不是写在某个配置文件里的,而是在 Tiptop 启动用户的 .profile 环境变量里。这个坑最隐蔽的地方在于:它不是写在程序里,很多开发者第一次排查时根本想不到去看环境变量。
#EasyFlow Server IP EFSITEIP=192.168.0.250; export EFSITEIP #EasyFlow Server Name EFSITENAME=EF2KWeb; export EFSITENAME #EasyFlow SOAP Location EFSOAP=http://192.168.0.250/EF2KGateWay/EF2KGateWay.WSDL; export EFSOAP三个变量的分工很明确:EFSITEIP是 Easyflow 服务器的 IP,EFSITENAME是 Easyflow 的站点名,EFSOAP是网关 WSDL 的完整地址。实际项目里最容易配错的是第三个——有人只改到 IP,忘了把端口和EF2KGateWay路径带上,结果 Tiptop 侧报 SOAP 连接失败;还有人三个变量写的是同一个 IP,但 Easyflow 服务器上实际跑了多个站点,导致请求打到了错误的站点上。
检查方法也很直接:在 Tiptop 服务器上执行env | grep EF,确认三个变量都已经生效,并且值与你手上拿到的 Easyflow 服务器信息一致。改完 .profile 后要重新登录或source .profile再重启 Appserver,环境变量才会被新启动的进程读到。注意不要直接在命令行 export 完就测试,Tiptop 的 Appserver 进程如果没重启,用的还是旧环境变量,这是“明明改对了却还是报错”的高发原因。
2.2 Appserver 没启动:最容易被忽略的“黑匣子”
Tiptop 与 Easyflow 的交互依赖 Appserver,Tiptop for Browser 版装好后,必须先运行startweb把 Appserver 拉起来。这个前提看起来简单,但实际踩坑率极高——尤其是服务器重启之后,Tiptop 的 Console 界面能打开,作业也能进,但一执行审核就卡住或直接超时,因为审核代码里调用的 SOAP 服务根本没有可用的进程在监听。
判断是不是这个原因,最有效的办法是看 Appserver 的启动日志,或者直接ps -ef | grep相关进程。如果 Appserver 确实没起来,执行startweb后等十几秒再测试审核抛转。集成测试阶段我一般建议把 Appserver 启动加到服务器开机脚本里,避免每次重启后都手动拉起。这个“黑匣子”式的问题最难定位就在于:报错信息往往只会给一个笼统的“连线失败”或“Service Unavailable”,不会告诉你其实是 Appserver 没监听。
2.3 服务端集成包没装:客户端配置全对也连不上
还有一个先决条件经常被漏掉:Easyflow 服务器端必须安装 Tiptop 与 Easyflow 的集成包,并且安装完成后 Easyflow 端才有EF2KGateWay这个服务。这个环节出了问题,现象非常迷惑——Tiptop 端三个环境变量全对,Appserver 也起来了,审核时却仍然连不上,日志里报的是 SOAP 访问异常。
验证方法可以直接在 Tiptop 服务器上用 curl 或浏览器访问EFSOAP里配的那个 WSDL 地址,如果能正常返回 XML 格式的 WSDL 内容,说明网关服务通了;如果返回 404 或连接拒绝,基本可以断定集成包没装好或对应站点没起来。这个检查建议放在所有集成测试的第一步,五分钟就能排除掉一大类“网络不通”的误判,而不是像第一次接触集成的人那样,先翻防火墙、再查路由,折腾一圈最后发现是服务端压根没有这个服务。
3. 数据表与单据性质:7 个签核栏位一个都不能少
3.1 pmkmksg 与 pmk25:两个关键栏位缺失的典型报错
Tiptop 里一张单据要能走电子签核,其对应的数据表必须包含 7 个签核相关栏位。前两个是关键栏位:是否签核标记和签核状态,这两个缺失的话,作业编译时或审核执行时会直接报错,或者界面上根本显示不出签核相关的栏目。
| 栏位 | 名称 | 关键性 | 作用 |
|---|---|---|---|
| pmkmksg | 是否签核 | 关键 | 该单据是否要走电子签核 |
| pmk25 | 签核状态 | 关键 | 当前签核所处状态 |
| pmksign | 签核等级 | 非关键 | 多级签核时使用 |
| pmkdays | 签核完成天数 | 非关键 | 统计签核耗时 |
| pmkprit | 签核优先顺序 | 非关键 | 排序用 |
| pmksseq | 已签核顺序 | 非关键 | 系统自动维护 |
| pmksmax | 应签核顺序 | 非关键 | 系统自动维护 |
上面以 pmk_file 请购单为例,实际上每支作业的数据表栏位名都不同,比如出货单是 ta_sfbefstat、ta_sfbmksg 这类以 ta_ 开头的客制栏位。检查数据表时不要只找同名栏位,需要根据每支作业的实际命名来确认。如果表里缺栏位,需要按客制规范手工创建,不要试图绕过——后面抛转作业生成的 SQL 和 XML 都以这 7 个栏位为基础,缺一个就会导致组出来的 XML 少字段,Easyflow 端解析时就对不上。
3.2 签核状态取值表:状态字符和含义对不上时的困惑
签核状态栏位的取值是一套约定的字符编码,集成过程中经常遇到状态值理解错位的情况。比如界面上显示“开立”,但表里存的是 0;显示“送签中”,表里存的是 S。如果不熟悉这套映射,排查时很容易被界面提示误导。
| 取值 | 含义 | 说明 |
|---|---|---|
| 0 | 开立 | 单据初始状态 |
| 1 | 已核准 | 签核完成 |
| 6 | 结案 | 流程结束 |
| 9 | 作废 | 单据作废 |
| S | 送签中 | 已抛转,等待签核 |
| R | 送签退回 | 签核人退回 |
| W | 抽单 | 已从 Easyflow 抽回 |
签核状态机的基本流转是 0(开立)→ S(送签中)→ 1(已核准),退回走 R,作废走 9,结案走 6。调试时如果发现单据状态卡在某个值不动,先确认当前处于哪个状态,再看对应的流转条件是否满足。比如审批人已经在 Easyflow 里核准了,Tiptop 这边还是 S,问题出在回写环节;如果压根没进 Easyflow,状态不会变成 S,还是 0。这套取值规律是集成调试中判断“问题出在哪一段”的核心依据。
3.3 单据别没有勾选“是否签核”:后续操作全部无效
数据表栏位齐了,也不代表所有单据都会走签核。Tiptop 里同一支作业可以按单据别区分是否签核,这个开关在单据性质设置作业里——制造模块是 smy_file,销售子系统是 oay_file,不同子系统用的资料表不一样,设置前一定要先确认当前子系统对应的表。
在单据性质设置界面(比如通用单据性质设置作业 asmi300)里找到“是否签核”选项,勾选上并保存。需要注意一个细节:如果这张单据已经以未签核的单别开立,之后再去修改单别性质是不生效的,必须新建或选择“是否签核”为选中状态的单据别。这个坑的实际表现是:审核后状态正常变成 S,但 Easyflow 里搜不到新表单;或者在易拓集成向导里选工厂后,单据类别列表里压根看不到你想要的单别——因为向导只会列出“是否签核”已勾选的单据类别。
3.4 schema 更新与客制栏位:r.s2 dsx 和 p_perlang 的连带操作
数据表结构变更后,还需要更新 schema 资料(r.s2 dsx),并重新编译相关作业。如果单据上有客制栏位需要显示,还要运行 p_perlang 维护栏位显示。这一步漏掉的表现是:作业界面能进,但新加的签核栏位显示不出来,或者显示成乱码/星号。
在 Tiptop 环境里,跑 p_perlang 时要注意语言别,简体、繁体、英文都要维护一遍,否则切语言后栏位描述会丢。这一步不需要改程序逻辑,但它是界面显示的最后一道工序,漏了会让前面所有数据表工作看起来“没生效”。另外,新增作业的话还要在 p_link 里增加对 aws 的链接,在 p_ze 里维护新增的提示信息,这两个表不维护,作业上会出现找不到程序或提示信息为空的情况。
4. 抛转作业 aws_xxx.4gl:XML 对不上就卡在“送签中”
4.1 aws_efcfg 自动生成后的三处必修代码
aws 模块下的抛转作业(aws_xxx.4gl)与 Tiptop 各作业一一对应,比如 aws_aapt110.4gl 对应请购作业、aws_asfi301.4gl 对应工单作业。这些文件不需要从零手写,而是在 aws_efcfg 维护窗体里配置好单头、单身栏位后,点击“产生范式程序”自动生成的。但自动生成只是给了你一个骨架,实际项目中几乎每支都要手工改。
CASE WHEN sr.oga00='1' CALL cl_getmsg('aws-003',g_lang) RETURNING g_msg WHEN sr.oga00='2' CALL cl_getmsg('aws-004',g_lang) RETURNING g_msg WHEN sr.oga00='3' CALL cl_getmsg('aws-005',g_lang) RETURNING g_msg WHEN sr.oga00='4' CALL cl_getmsg('aws-006',g_lang) RETURNING g_msg WHEN sr.oga00='5' CALL cl_getmsg('aws-007',g_lang) RETURNING g_msg END CASE LET l_oga00 = g_msg这段代码的作用是把数据库里的 oga00 取值翻译成显示用的中文信息,再塞进 XML。为什么要这样改?因为 Tiptop 表里存的是编码(1、2、3…),而 Easyflow 表单上要显示的是中文(比如单据种类名称),直接抛编码过去,签核人看到的表单就是一堆数字。逻辑说明:通过 cl_getmsg 按编码取消息文本,再赋值给中间变量 l_oga00,后续拼 XML 时用的是 l_oga00 而不是 sr.oga00。参数说明:sr 是存放抛转中间信息的 RECORD,l_ 开头的变量是需要手工增加的中间变量,g_lang 是当前语言环境。
自动生成的文件里,需要重点检查三类情况:一是数据库栏位需要加工后再传入 XML 的,比如上例的状态码转文本;二是选取栏位与 XML 栏位数量不一致的,多了要去掉、少了要补;三是从其他表 JOIN 替代原栏位的,比如客户编号 oga04 要显示为客户名称 occ02,这时要在参照字段里设置,让生成的 SQL 自动把 occ_file 表 JOIN 进来,组 XML 时用 occ02 替代 oga04 的位置。我一般会在生成后先跑一遍编译,把语法错误清掉,再对照 XML 输出逐字段核对,而不是直接进 OA 端联调——那样报错信息太模糊,定位成本高。
4.2 XML 与 ColumnSet.xml 不一致:名称必须完全对应
抛转作业组出来的 XML 字符串,最终要被 Easyflow 端的 ColumnSet.xml 解析。这个文件里配置了每个表单接收哪些 XML 节点,节点名称必须与 Tiptop 抛出的 XML 完全一致。一个常见的理解误区是:很多人以为顺序也要一致,其实顺序无关,名称才是硬性要求。
<oga00>開立</oga00> <oaydesc>請購單</oaydesc> <oga01>PO000123</oga01> <oga04>某供應商名稱</oga04> <addr>地址一 地址二 地址三</addr> <ouuser>ADMIN</ouuser>注意这个例子里的ouuser,手册里特别强调:XML 里必须抛转一个资料所有者 xxxuser 栏位,这个栏位不显示在电子表单上,但 Easyflow 要拿它作为填单人解析流程。漏掉这个节点,常见表现是单据能进 Easyflow,但流程起不来,审批人收件箱里始终看不到。我见过有人在 ColumnSet.xml 里手误把 ouuser 拼成 oauser,结果 Tiptop 抛转成功、Easyflow 也收到了,但流程就是不走,查了半天才发现是节点名拼写不一致。
aws_efcfg 界面上的“传送栏位资讯”按钮虽然能自动生成 ColumnSet.xml,但实际项目里不建议用——它完全按 Tiptop 数据库里的设置重建文件,而数据库字段和实际要抛的 XML 很难保证完全一致,重建后反而容易把人工维护好的内容冲掉。这个文件的维护方式应该是:复制现有单据的 Program 段,手工修改节点名称和位置。
4.3 标准作业审核流程改造:送签后原审核代码不再执行
抛转作业改好了只是第一步,Tiptop 标准作业本身也要改。核心是在原有审核函数中插入一段判断,让单据在需要签核时先走抛转,而不是继续执行原来的审核事务。
IF g_success = 'Y' THEN IF g_oga.ta_ogamksg = 'Y' AND g_oga.ogaconf = 'N' THEN CALL t600_ef() IF g_success = 'N' THEN ROLLBACK WORK END IF RETURN #如果要走签核流程则下面的审核过程就不自动进行了 END IF END IF这段代码的位置很讲究,必须放在原审核段“完成所有判断但尚未开始事务性操作”的位置——也就是还没有任何 UPDATE 之前。逻辑说明:先判断是否抛转成功(g_success),再判断该单据是否需要签核(g_oga.ta_ogamksg = 'Y')以及当前是否未确认(g_oga.ogaconf = 'N'),满足条件就调用 xxx_ef() 函数执行抛转。参数说明:ta_ogamksg 是自定义的“是否签核”栏位,ogaconf 是审核状态栏位,g_success 是抛转函数的返回值。
这段逻辑带来的一个隐蔽变化是:一旦单据走了电子签核,原审核段后面的事务性操作全部不会再执行,因为函数里直接 RETURN 了。所以如果原审核代码里有必须执行的操作(比如更新库存、生成后续单据),需要把那部分代码拷贝生成一个新的函数,并新增一个 Action(比如 confirm_ef),仅当签核状态为 1(已核准)时允许执行。这里建议是“拷贝”而不是“移动”,保证不走签核的单据仍然走原审核流程,对系统原有行为影响最小。这个坑不提前处理的话,上线后会出现“单据签核完了,但后续单据没有自动生成”的连锁问题。
4.4 签核状态浏览 Action:改错单据号码就白抄
除了审核流程改造,每支作业还建议增加一个浏览签核状态的 Action,相关代码可以从标准作业里拷贝。这类代码有个特点:结构完全一样,但每个作业要改的单据号码栏位和签核状态栏位不同,抄的时候改错一个,点击后要么没反应,要么误报。
FUNCTION i301_approval_status() IF g_aza.aza23 matches '[ Nn]' THEN #未设定与 EasyFlow 签核 CALL cl_err('aza23','mfg3551',0) RETURN END IF IF g_sfb.sfb01 IS NULL OR g_sfb.sfb01 = ' ' THEN #尚未查询资料 CALL cl_err('', -400, 0) RETURN END IF IF g_sfb.ta_sfbefstat NOT MATCHES "[S1WR]" THEN RETURN END IF CALL aws_efstat(g_sfb.sfb01) END FUNCTION这段代码实际调用的是 aws_efstat 公共函数,把当前单据号码传过去,查询 Easyflow 端的签核状态并显示。需要修改的部分只有两处:单据号码栏位(这里是 sfb01,换成目标作业的实际单据号码)和签核状态栏位(这里是 ta_sfbefstat)。另外注意[S1WR]这个匹配条件——只有状态是 S(送签中)、1(已核准)、W(抽单)、R(退回)时才允许查询,状态为 0(开立)时直接返回。这是因为单据还没送签时查 Easyflow 没有意义。抄这段代码时最容易漏掉的是第一段 aza23 的判断,这是判断单据别是否已设定 Easyflow 签核的,不判断的话,未设定签核的单据也会发起查询,白白增加一次无效的 SOAP 调用。
5. Easyflow 端配置:表单出不来、审批流不走的常见问题排查
5.1 表单目录改名后,Init 文件里的旧单号没替换干净
Easyflow 端的新表单通常不是从零设计,而是复制一个已有的 Tiptop 表单目录,再全局替换单号。存放位置在Program Files\EF2Kweb\ASP\CHT\Forms,比如把 TT_SO1 整个目录复制为 TT_WO1,然后改目录名、改目录下所有文件名。这一步做完,真正的坑在文件内容:TT_WO1_Approve_Init.ASP、TT_WO1_Create_Init.ASP、TT_WO1_Display_Init.ASP 以及 Grid.ASP 这四个文件里的所有 “TT_SO1” 字符串都要替换成 “TT_WO1”。
现象:替换漏了之后,表单能打开,但数据不显示,或显示的还是旧表单的栏位结构。原因:Init 文件里写死了旧单号的 XML 解析逻辑,新表单的数据进来后按旧规则解析,自然对不上。解决:替换时不要只用编辑器的“全部替换”一次了事,替换完后再全局搜一遍 “SO1” 确认没有残留。这类问题排查时往往要花不少时间,因为报错不一定明显,可能只是某个字段显示为空。
5.2 控件 ID 命名规则:xxx_1 和 xxx_2 弄反导致数据填充为空
Forms.ASP 是电子表单的外观文件,通常用网页编辑工具调整布局。这里最容易踩的坑是控件 ID 命名:每个栏位有两组 ID,标签的命名规则是 xxx_1(如 sfb01_1),控件本身的命名规则是 xxx_2(如 sfb01_2)。Easyflow 系统会自动按照栏位名从 XML 中取数据,解析 ID 后填充到对应位置。命名规则错了,表单上就看不到数据。
| ID 类型 | 命名规则 | 示例 | 作用 |
|---|---|---|---|
| 标签 | 栏位名_1 | sfb01_1 | 显示名称 |
| 控件 | 栏位名_2 | sfb01_2 | 承载数据值 |
实际项目的血泪经验是:调布局时顺手改了控件的 ID,或者复制粘贴后忘改后缀,结果表单打开一片空白。检查手段也很简单——用浏览器开发者工具看控件 ID,或者直接看 Forms.ASP 源码里 id 属性是否与 ColumnSet.xml 里的栏位名对应。这里还有一个容易混淆的点:一般多行文本框和选择框两种控件足够用了,日期用选择框,其他都用文本框,不要为了美观引入特殊控件,否则解析逻辑要跟着改,得不偿失。单身表格是 OCX 嵌入的,不需要在 Forms.ASP 里特别设置,但注意不要把 OCX 的 ID 也按 xxx_2 规则去改,它有自己的命名约定。
5.3 表单性质里“显示否”和生效日没设置:表单死活不显示
表单文件配好、ColumnSet.xml 也填了,但 Easyflow 左侧清单里就是看不到新表单。这个问题的根因通常不在文件,而在表单性质维护。进入“管理程序清单 → 流程基本数据 → 表单性质维护操作”,找到对应表单,如果界面上各项目没设置正确,表单不会出现在清单里。
两个重点:一是“显示否”的设置,如果这个项目被置为否,表单直接隐藏;二是生效日和失效日,失效日如果被误填了过去的日期,表单也会消失。表单如果永久生效,忽略失效日即可,但生效日不能为空。这个界面跟 Tiptop 的单据性质设置是两回事,Tiptop 那边决定了“要不要抛”,这里决定了“抛过来后能不能显示”,两者都要配,漏一个就断一条链路。
5.4 没建权限文件:能看到清单却打不开表单
表单出现在清单里,但点进去提示无权限,这是另一个独立问题。表单性质维护页面滚动到最下面,有一个“新建权限文件”的入口,必须为新表单设置权限属性,否则就算表单已生效,用户也无法打开。
这个问题的迷惑性在于:表单清单能显示,容易让人误以为配置已经完成,结果点开才发现权限没配。处理方式很简单,在权限文件里把需要访问该表单的角色或用户加上即可。实际项目里经常是只给管理员配了权限,签核人(比如审批流程里的 439 用户)没加,导致测试时管理员能打开、实际签核人打不开,来回扯皮半天才发现是权限文件漏了人。
5.5 忘抛 xxxuser 栏位与表单关联缺失:流程起不来的两个隐蔽原因
最后一类高频问题是:单据成功进入 Easyflow,也显示在收件箱里了,但发起流程时报错,或者流程根本不会按预期流转。这里有两个隐蔽原因值得单独列出来。
第一个原因是 XML 里没抛 xxxuser 栏位。这个栏位不显示在表单上,但 Easyflow 要拿它作为填单人解析流程的依据。没有它,系统不知道这张单据是谁填的,流程就起不来。检查方法:看 Tiptop 抛转作业生成的 XML 字符串里,是否包含以 user 结尾的栏位节点。
第二个原因是表单关联没建。在“电子表单模块 → 管理程序清单 → 自动向导 → 易拓集成向导”里,点击“新增表单关联”,选择 tiptop 程序代号、工厂代号,再选择对应的 Easyflow 电子表单代号。完成关联后,Tiptop 抛转过来的单据才能准确落到对应的电子表单上。如果这步没做,即使前面全部配置正确,单据也到不了签核人手里。做关联时有个便利点:tiptop 程序代号是系统自动列出的(所有有抛转机制的作业都会出现),工厂代号选择后,下方会列出该作业中已勾选“是否签核”的单据类别——如果这里列表是空的,回头检查 3.3 小节里说的单据性质设置。
6. 快速定位:集成测试失败时按这张顺序表排查
集成测试阶段,我习惯把问题分成“Tiptop 没抛出去”“抛了但 Easyflow 没收到”“收到了但显示不对”“签核后回写失败”四类,每类对应不同的排查入口。下面这张表是我在实际项目中沉淀下来的排查顺序,按顺序走一遍,大多数问题能在十分钟内定位到具体环节。
| 症状 | 第一排查点 | 第二排查点 | 第三排查点 |
|---|---|---|---|
| 审核后状态不变,无抛转提示 | Appserver 是否启动 | 单据别是否勾选签核 | 数据表 7 栏位是否齐全 |
| 提示抛转成功但 OA 收件箱空 | 服务端集成包与 WSDL 可访问性 | 易拓集成向导表单关联 | 表单性质和权限文件 |
| 表单收到但数据为空 | 控件 ID 命名 xxx_1 / xxx_2 | ColumnSet.xml 节点名 | 抛转 XML 是否含该节点 |
| 签核后 Tiptop 状态不变 | 签核状态栏位名是否一致 | 回写服务是否登记 | XML 中 xxxuser 是否抛出 |
验证抛转是否真的发生,最快的办法是在 Tiptop 端看 aws 抛转作业的运行日志,确认 XML 字符串已经组出来;然后在 Easyflow 端查收件箱、查流程实例。把两端日志时间对上,就能判断是 Tiptop 没发出去还是 Easyflow 没收到。审核后立刻查签核状态栏位的值——变成 S 说明已经发给 Easyflow 了,还是 0 说明抛转逻辑根本没执行。
从那以后,我每次接手新的 Tiptop 与 Easyflow 集成单,都会强制走一遍这套排查:先 curl 一下 WSDL 地址确认网关活着,再确认 Appserver 进程在跑,然后查数据表栏位、查单据性质勾选、查表单关联,最后才打开编辑器改代码。这个顺序帮我省掉了大量“查了半天发现是环境问题”的时间。希望这篇笔记也能帮你少走几步弯路。
本文还有配套的精品资源,点击获取