EBS定期成批复制实现详解:并发程序、排障与最佳实践
2026/9/16 2:58:47 网站建设 项目流程

EBS 里的“定期成批复制”,对做过 Oracle EBS 二开的顾问来说,应该是个再熟悉不过的活儿。这个功能难不在“复制”两个字本身,难在让它按业务节奏稳定跑起来:固定资产成批增加的数据要定期复制到总账接口表,工艺路线取数结果要每天复制到成本分析库,或者每个月初要把上期预算成批复制成当期预算。很多团队一开始都把定期成批复制当小功能处理,结果上线后频繁遇到并发请求挂起、主键重复、日志为空、权限报错。这篇文章我打算把 EBS 定期成批复制功能背后的设计思路、实现细节和我在现场踩过的坑完整整理一遍,给做 EBS 开发、财务流程顾问和运维的老伙计们一个可直接参考的版本。

1. 定期成批复制在 EBS 里到底是干什么的

1.1 它不是标准菜单,而是一类解决方案

先说一句容易让人迷惑的话:Oracle EBS 标准界面里其实没有“定期成批复制”这样一个菜单。你如果去问业务顾问“您说的成批复制是不是固定资产成批增加”,往往会得到两种答案:一种人指的是固定资产模块标准的 Mass Additions,另一种人指的是我们后期做的定期数据复制程序。标题里这两个热词经常被混在一起,但实际处理思路完全不同。

我这里讲的“定期成批复制”,指的是一类定期运行的并发程序,它的任务是按固定周期,从源表或视图中批量读取符合条件的业务数据,经过清洗、匹配、转换之后,写入一张目标表、中间表、接口表或外部文件。这样做有几个很常见的使用场景:一是给下游系统做数据快照,二是把某个模块的主数据分发到多个组织,三是把线上交易数据定期复制到分析库供报表读取。从技术形态上看,它就是一个用 PL/SQL 写的、由 EBS 并发管理器调度的批量程序。

这类功能在项目里被叫成“XX复制”、“XX同步”、“XX快照”的特别多,但内核都一样。我见过最典型的数据流是这样的:采购模块产生一批资产资本化的采购单,资产模块通过固定资产成批增加生成资产卡片,而我们开发一个每天凌晨跑的复制程序,把新增的资产行及其分配信息复制到财务共享接口表,供总账和预算系统第二天使用。这里的“复制”不是简单 insert,而是要处理过滤条件、状态判断、重复行和失败重跑,所以必须当做一个正经开发任务来设计。

1.2 为什么非要做成“定期”,而不是直接查视图

有朋友会问,既然只是看数,为什么不直接用视图或数据库链路去查呢?我遇到过很多次这种质疑,尤其是财务用户觉得,你们开发一套复制程序,表里多了一份数据,万一两边不一致怎么办?这个问题问得很有道理,但在真实业务里,定期落表往往比实时查视图更受欢迎。

原因有三个。第一,EBS 源表的表结构非常复杂,比如固定资产分配信息要关联资产、账簿、成本分配、地点等十几张表,直接让外部系统查视图容易把业务系统拖垮,尤其月末折旧期间,查询压力一大就容易锁表。复制程序在非业务高峰跑,把结果落到单独的接口表里,下游查询只碰快照表,互不干扰。第二,复制快照可以为后续审计和追溯提供依据。假设每月都会跑一次“月末资产快照复制”,哪怕源表的数据后来被修改了,接口表里仍保留当月当时的版本,这就回答了财务“你当时看到的数是什么样的”这个灵魂问题。第三,下游系统接口往往只接受文件或表,不能直连 EBS 的 AP 数据库,落表是最稳的集成方式。

当然,如果把“定期”理解为越低频越好,那也是另一个极端。实际项目中常见周期是每天、每周或每月一次,也有按小时跑一次的,但高频复制通常意味着表设计要足够轻量,并且要考虑并发程序执行时长。我曾经接手过一个“按小时复制工艺路线取数”的需求,源表是几百万行的工艺路线版本表,目标表也是同样的体量,一开始做全量复制,每次要跑将近半小时,后面改成只复制最近 24 小时变更的增量数据,执行时间缩短到三分钟,这才符合用户预期。

2. 功能设计全景:从需求到正式功能的结构

2.1 六个核心设计环节

在 EBS 里落地一个定期成批复制功能,我习惯按照下面六个环节去拆解,缺一个后面都会补坑。

第一是源数据定义。要搞清楚复制什么,从哪里取数,条件是什么。比如固定资产成批增加,要明确是取资产模块里所有成功新增的资产,还是只取还在“草稿”状态的资产;工艺路线取数要明确是按最后更新日期增量取数,还是取所有启用状态的工艺路线。这些条件要逐个问清楚,写成技术映射文档,不能凭感觉。

第二是目标表设计。目标表并不等于源表照抄。通常我会加三个公共字段:batch_id、request_id、create_date。batch_id 用来标记一次复制任务的批次,request_id 对应并发请求号,create_date 是这条记录落库的时间。有了这三个字段,重跑、追溯、清理都方便。目标表结构最好稳定,因为下游已经按它开发了接口。

第三是参数映射与派生规则。EBS 并发请求可以传参,比如账套、库存组织、期间、模式、日志级别等。参数进来之后,PL/SQL 里要处理默认值、校验和转换。比如用户不输“期间”就默认当前会计期,用户输“全量”和“增量”要走不同的 WHERE 条件。

第四是执行日志与统计。程序至少要输出三样东西:复制了哪些数据、共处理多少行、跳过了多少行、报错了多少行。这些信息写到 EBS 请求日志里,用 FND_FILE.PUT_LINE 输出。很多人只写“Success”就完了,等出问题的时候根本不知道程序处理到哪一步。

第五是并发调度。通过请求集、周期性提交,让程序在指定时间自动跑。这个环节最容易出问题的是没考虑业务日历,比如月末折旧跑批和复制程序同时启动,两边抢资源。

第六是异常重跑的幂等性。所谓幂等,就是程序跑一次和跑十次结果一致。这要求在插入逻辑里做“有则更新,无则插入”,而不是一刀切全删全插。否则一旦某天请求失败,手工重跑就会产生重复数据,财务对不上账会很有意见。

2.2 参数化设计:一个好用程序的关键

一个好的定期成批复制程序,参数设计至少要覆盖这几种:源和目标的范围、复制模式、提交频率、调试开关。

我常用的一张参数表如下:

参数名类型必填用途与规则
P_LEDGER_IDNUMBER账套 ID,用于过滤多会计主体数据
P_ORG_IDNUMBER库存组织/资产组织 ID,为空表示全部
P_PERIODVARCHAR2会计期或业务期间,为空取当前期间
P_MODEVARCHAR2FULL 全量复制 / INCR 增量复制 / MERGE 合并更新
P_COMMIT_SIZENUMBER批量提交行数,默认 500
P_DEBUGVARCHAR2Y 时输出详细日志并启用 SQL Trace

第一次做这种程序时,我以为参数越少越好,后来发现不是。你永远不知道用户下次会提出什么“只跑某个部门”的新需求。有参数在,就不需要改代码改并发程序定义,只要在请求界面输入不同的值就行。

需要注意的是,P_PERIOD 不能简单依赖 SYSDATE,因为 EBS 的会计期和自然日并不完全同步。月末自动跑批时,SYSDATE 已经是次月一号,而业务数据可能还没过账到次月期间,这时候如果用 SYSDATE 取期间,会漏掉上月末最后一天的数据。我通常用 EBS 的 GL_PERIOD_STATUSES 表或者自定义参数,让调用者在请求参数里显式指定期间。

2.3 复制模式:全量、增量与合并更新

复制模式可以直接影响程序逻辑和性能,我一般分三种。

全量复制最适合小表或者一次性初始化。一条 DELETE + INSERT 结束,代码最简单,但数据量大时容易产生大量归档日志和锁。增量复制适合有更新日期字段的表,比如工艺路线表通常有 LAST_UPDATE_DATE,复制时 WHERE LAST_UPDATE_DATE >= NVL(上一次批次的最大日期, 某个安全起点)。合并更新适合业务希望接口表始终是最新状态的情况,用 MERGE INTO 或先 UPDATE 再 INSERT。

这三种模式各有坑。全量复制最大坑是删除目标表时会误删其他批次数据,所以删除条件一定要带上 batch_id 或者组织范围。增量复制最大坑是如果源表存在删除操作,增量取数永远发现不了被删的行,目标表里会残留“幽灵数据”。合并更新最大坑是性能,数据量大时逐行 match 会慢,必须给目标表建立合适的唯一索引。

我自己的选择标准是:业务要“按月留痕”,就用全量复制加批次字段;业务只要“当前值”,就用合并更新;源表没有明确更新时间字段,建议程序里判断主键是否存在,存在则更新,否则插入,这就是最朴素的幂等思路。

3. 核心实现细节:从并发程序到 PL/SQL

3.1 注册一个并发程序的四个步骤

在 EBS 里写好了 PL/SQL 包,要让它能从“请求提交”界面跑起来,必须经过注册这一步。流程并不复杂,但很多人第一次会漏掉某个步骤导致找不到程序。

第一步,在数据库中确认 PL/SQL 包已经编译通过,并且包名和存储过程名是普通字符,最好加上自己的应用前缀,比如 XX_FA_COPY_PKG。避免使用 EBS 标准前缀,防止补丁升级时被覆盖。

第二步,用“系统管理员”职责导航到“并发程序-可执行”,定义一个可执行。可执行名称填 XX 开头的命名,可执行方法选择“PL/SQL 过程”,可执行文件名填“包名.过程名”,比如 XX_FA_COPY_PKG.REPLICATE_FA_DATA。注意这里填错任何一个大小写,注册后都会报程序找不到。

第三步,定义并发程序。把刚才的可执行关联进来,再定义请求参数。参数名要和在 PL/SQL 过程里定义的入口参数完全一致,包括顺序和类型。这一步最容易犯的错误是参数名不一致,比如过程里叫 P_LEDGER_ID,并发程序参数里写 Ledger ID,虽然显示给用户的名字可以随意,但底层 Token 必须一致。

第四步,把并发程序挂到请求组或者请求集里。如果你希望用户能在“提交请求”界面看到它,就需要把它添加到对应职责的请求组中,否则用户没法提交。如果要定期自动运行,还要做成请求集,并在“周期性”里定义调度。完成这四步后,先手工提交一次,观察请求日志,确认输出正常,再挂自动调度。

3.2 核心复制逻辑的代码骨架

下面我给出一个常见的 PL/SQL 并发程序骨架,功能是按期间复制资产数据到接口表。代码不算复杂,但包含了并发程序、日志和幂等处理的关键要素。

CREATE OR REPLACE PACKAGE BODY xx_fa_copy_pkg IS PROCEDURE replicate_fa_data( errbuf OUT VARCHAR2, retcode OUT NUMBER, p_ledger_id IN NUMBER, p_period_name IN VARCHAR2, p_commit_size IN NUMBER DEFAULT 500, p_debug IN VARCHAR2 DEFAULT 'N' ) IS CURSOR c_fa_data IS SELECT fa.asset_id, fa.asset_number, fa.asset_description, fa.date_effective, fa.book_type_code, fdp.period_name, fdd.cost FROM fa_additions_b fa, fa_deprn_periods fdp, fa_distribution_history fdd WHERE fa.asset_id = fdd.asset_id AND fdd.period_counter = fdp.period_counter AND fdp.book_type_code = fa.book_type_code AND fdp.period_name = NVL(p_period_name, fdp.period_name) AND fa.ledger_id = p_ledger_id AND fa.status = 'A'; v_cnt NUMBER := 0; v_ins NUMBER := 0; v_upd NUMBER := 0; v_batch_id NUMBER; v_request_id NUMBER := fnd_global.conc_request_id; v_target_pk NUMBER; BEGIN SELECT xx_fa_copy_batch_s.NEXTVAL INTO v_batch_id FROM dual; IF p_debug = 'Y' THEN fnd_file.put_line(fnd_file.LOG, 'Batch ID: ' || v_batch_id); fnd_file.put_line(fnd_file.LOG, 'Parameter period: ' || p_period_name); END IF; FOR rec IN c_fa_data LOOP BEGIN SELECT COUNT(1) INTO v_target_pk FROM xx_fa_gl_iface WHERE asset_id = rec.asset_id AND period_name = rec.period_name; IF v_target_pk = 0 THEN INSERT INTO xx_fa_gl_iface( batch_id, request_id, asset_id, asset_number, description, period_name, cost, create_date ) VALUES ( v_batch_id, v_request_id, rec.asset_id, rec.asset_number, rec.asset_description, rec.period_name, rec.cost, SYSDATE ); v_ins := v_ins + 1; ELSE UPDATE xx_fa_gl_iface SET cost = rec.cost, request_id = v_request_id, update_date = SYSDATE WHERE asset_id = rec.asset_id AND period_name = rec.period_name; v_upd := v_upd + 1; END IF; v_cnt := v_cnt + 1; IF MOD(v_cnt, NVL(p_commit_size, 500)) = 0 THEN COMMIT; IF p_debug = 'Y' THEN fnd_file.put_line(fnd_file.LOG, 'Committed rows: ' || v_cnt); END IF; END IF; EXCEPTION WHEN OTHERS THEN fnd_file.put_line(fnd_file.LOG, 'Error at asset ' || rec.asset_id || ': ' || SQLERRM); END; END LOOP; COMMIT; fnd_file.put_line(fnd_file.OUTPUT, 'Total processed: ' || v_cnt); fnd_file.put_line(fnd_file.OUTPUT, 'Inserted: ' || v_ins); fnd_file.put_line(fnd_file.OUTPUT, 'Updated: ' || v_upd); retcode := 0; EXCEPTION WHEN OTHERS THEN fnd_file.put_line(fnd_file.LOG, 'Fatal error: ' || SQLERRM); retcode := 2; ROLLBACK; END replicate_fa_data; END xx_fa_copy_pkg;

这段代码演示了几个原则:用 OUT 参数 errbuf 和 retcode 给并发管理器返回状态;用 fnd_file 输出日志;用 batch_id 标记批次;先查再插是简单的幂等处理。整体逻辑不复杂,但实际项目中很多同类的复制程序连这几点都没做到。

3.3 批量提交、事务边界和性能控制

写这种成批复制程序,最需要控制的是事务边界。如果你在 FOR 循环里每插一行就 COMMIT,性能会非常差,而且日志会刷屏;如果一直不 COMMIT,数据量大时会把回滚段撑爆,其他业务还会看到一堆未提交锁。

我的经验是控制在 500 行左右 COMMIT 一次,这个数字不算绝对最优,但比较保险。如果源表数据量特别大,比如一次复制好几百万行,可以改成 1000 行一次,还要确保目标表索引不要太多,否则 INSERT 变慢。还有个细节是,COMMIT 后的数据不会随请求失败而回滚,因此程序必须保证重复执行不会产生重复数据。这也是我坚持用“存在则更新,不存在则插入”的最大原因。

另外,性能上可以考虑用 BULK COLLECT 配合 FORALL 替代单行 INSERT。单行游标在数据量小的时候没问题,但工艺路线取数经常一次几万行,逐行 INSERT 会慢得让用户怀疑人生。下面是个典型的批量处理片段:

DECLARE TYPE t_fa_tab IS TABLE OF xx_fa_gl_iface%ROWTYPE; v_batch t_fa_tab; BEGIN SELECT xxx, yyy, zzz BULK COLLECT INTO v_batch FROM source_table WHERE condition; FORALL i IN 1 .. v_batch.COUNT INSERT INTO xx_fa_gl_iface VALUES v_batch(i); COMMIT; END;

使用 BULK COLLECT 之前要注意内存占用,一次取几百万行进内存同样危险。稳妥的做法是限制 BATCH 10000 行,循环处理。性能调优没有银弹,建议在不同量级的数据上多测几次,找出当前环境的最优提交阈值。

3.4 定期调度怎么配

“定期”两个字最终体现在并发管理器上。EBS 里做周期性调度最常用的是“请求集”。你可以把复制程序关联进一个请求集,然后在“请求集-周期”里定义运行频率,比如每天凌晨 2 点、每周日、每月第一天。

配置时我强调三个点。第一,请求集的“用户”和“职责”要选择业务运行所在的职责,否则可能因为数据权限不同导致复制出来的数据范围不对。第二,如果复制程序和标准月末流程有依赖关系,必须用“而非”或“后置”步骤控制顺序,不能让复制程序抢在月末过账之前跑。第三,调度时间要避开数据库备份和归档高峰,否则日志切换频繁,程序运行时长会翻倍。

还要记住一点:EBS 并发管理器的“周期”调度是依赖并发管理器本身的时钟,不是数据库服务器的 SYSDATE。如果你改了应用层服务器时间或时区,调度时间会跟着变化,上线前要把这个因素考虑进去,免得用户凌晨蹲在电脑前等数据,结果发现调度没跑。

4. 典型应用场景:固定资产成批增加与工艺路线取数

4.1 场景一:固定资产成批增加后的卡片复制

在 EBS 固定资产模块中,“固定资产成批增加”是一个标准功能,通常叫 Mass Additions。它把采购、应付、库存等模块产生的资本化事务处理统一抓到一个队列中,资产模块按批生成资产卡片。这个过程会有很多中间状态,比如成批增加行可能处于“待处理”“已录入”“已过账”等状态。

我们的复制程序要做的,就是把已经成功生成的资产卡片信息,定期复制到财务共享接口表或者预算系统。这个场景下最关键的是状态过滤。曾经有个客户上线第一周,复制程序把成批增加队列里的未提交资产全部当成有效资产传给了总账,财务做固定资产对账时,总账余额和资产模块差了很大一截,排查了半天才发现是状态没过滤。

从表结构上看,成批增加的数据主要涉及 FA_MASS_ADDITIONS、FA_TRANSACTION_HEADERS、FA_ADDITIONS_B 等。通常要等资产编号生成后,再去取资产费用分配信息。复制条件里至少要包含账簿、过期日期、成本、分配行。我建议在目标表里加一个 PROCESS_FLAG,刚开始都置为“PENDING”,下游系统消费完成以后更新为“PROCESSED”,这样即使程序重跑,也不会把已经送出的数据再送一遍。

4.2 场景二:工艺路线数据周期取数复制

工艺路线取数是 EBS 数据抽取里的高频需求,尤其做成本核算或生产排程的系统,基本都要把制造工程模块的工艺路线、工序、工作中心等信息定期抽取到外部数据库或报表库。

这个场景和固定资产复制最大的不同在于版本管理。一条物料可以有多个工艺路线版本,每个版本又有多个工序,工序之间还有顺序关系。复制的时候如果只取到物料编号和工序,下游根本排不了产。所以我通常会把工艺路线主表、工序表、物料关联表、工作中心表做一次宽表复制,打上批次号,并在映射文档里写清楚主键是物料+替代工艺路线+工序号+生效日期。

增量取数时,要特别小心 LAST_UPDATE_DATE 是时间戳,部分 EBS 表更新时未提交会阻塞读取。程序里尽量加上“只取已提交事务”的天然限制:在 WHERE 条件中,排除“状态为未生效”的数据,同时在目标表通过唯一索引保证重复提交不产生重复记录。

我实际做过的一个化工企业项目,工艺路线数量不算大,但每天改工艺的人很多。他们要求每天晚上 10 点把当天所有被改过的工艺路线复制到报表库。一开始我用全量复制,每天要跑 40 多分钟,而且占用了大量数据库连接。改成增量之后,只复制当天发生变化的记录,执行时间降到 4 分钟,源表压力也小了很多。

4.3 多组织数据范围对复制结果的影响

EBS 的数据访问权限和库存组织、资产账簿、业务实体绑定在一起。复制程序如果用错了职责或没写多组织过滤条件,最典型的症状就是“能提交但复制出来的数据莫名少了一部分”或“数据翻倍”。

我在开发这类程序时,一般会要求传入 P_ORG_ID 或 P_LEDGER_ID,并且在 PL/SQL 里灵活使用 MO_GLOBAL.GET_ACCESSIBLE_ORGS 之类的上下文函数,或者干脆在 SQL 条件里明确过滤组织字段。有些目标表没有组织字段,只保存了组织代码,看上去也没什么问题,但一旦多组织环境合并业务,同一字段会被多条组织数据覆盖。处理办法是目标表必须增加 ORG_ID 列,并纳入唯一索引,复制时按组织分开维护。

这个坑很难在单组织测试环境里暴露,所以上线前一定要拿生产的多组织数据量做联调,用两个组织的数据各跑一遍,检查是否有串号。

5. 常见问题与排障实录

5.1 问题速查表

整理了一份我在现场遇到频率最高的几个问题,直接按表格排查能省不少时间。

现象可能原因处理建议
请求状态显示 TerminatedPL/SQL 里抛出异常,retcode 返回非 0查看请求日志最后的错误堆栈
请求一直处于 Pending/Running并发管理器连接数不足,或者有阻塞锁查看并发管理器日志,查 v$locked_object
目标表出现主键重复重跑时没有做幂等处理将逻辑改成先更新后插入,或加批次字段
复制结果比源表少多组织权限没过滤,或源表状态条件过强检查职责的 data access set,检查过滤条件
程序跑完没有日志FND_FILE 写在 LOG 而非 OUTPUT区分请求日志和输出文件两个位置
全量复制把历史批次数据删了DELETE 条件没有带 batch_id 或组织修改删除逻辑,限制只清理同批次数据
增量复制漏数源表有删除操作,或 LAST_UPDATE_DATE 只更新到秒增加删除标识处理,或启用 CDC 方案
自动调度没执行请求集没有关联到职责,或调度时间设置错误检查请求集、职责、并发管理器日历

这张表不可能覆盖所有环境,但大多数复制程序的问题都离不开这几个大类。真遇到没见过的,第一步永远是看日志,第二步是打开 SQL Trace,第三步是查锁和会话状态。

5.2 日志分析和 SQL Trace 排查

我见过很多顾问排错,上来就改代码,这是最低效的。EBS 并发程序天然有日志体系,只要在代码里写了 FND_FILE.PUT_LINE,请求结束后系统会生成请求日志和输出文件。用户提交请求的界面可以直接查看日志,系统管理员也可通过“查看请求”去打开。

要看出真实执行的 SQL 和绑定变量,最直接的办法是开启 SQL Trace。在并发请求参数里加一个 P_DEBUG 参数,值为 Y 时执行 ALTER SESSION SET SQL_TRACE=TRUE。跑完后,去数据库服务器上的用户 dump 目录找 trace 文件,重点看执行计划里有没有全表扫描、索引有没有被用到。

有一次客户说复制程序白天跑很正常,晚上跑特别慢,看 Trace 发现晚上源表的成本分配表有几个大分区没有被压缩,导致全表扫描。当时我们没改代码,只是和 DBA 一起把分区重建了一下,晚上执行时间就恢复了。如果没有 Trace,这种问题很难定位。

5.3 我用过最有效的几个避坑技巧

最后说几个我反复在项目中用的技巧,不一定写在文档里,但很管用。

第一,复制程序一定要给自己留一个“清理接口”。很多程序跑完以后,如果下游发现数据不对,需要手工删除当前批次然后重跑。如果你在设计目标表时没有提供按 BATCH_ID 删除的过程,运维就只能去数据库删,非常危险。我一般会额外提供一个删除指定批次数据的存储过程,并开放成请求,这样业务人员可以自救。

第二,不要对目标表用 TRUNCATE 清理。虽然 TRUNCATE 很快,但带来的问题是:如果复制程序里有多个组织和多种业务类型,TRUNCATE 会把别人刚跑完的数据也清了。用带条件的 DELETE 更安全,代价是慢一点,但对核心财务数据,宁可慢也不能错。

第三,提交频率要结合实际环境调整。代码里默认 500,不代表所有环境都合适。如果数据库日志量很大,1000 提交也许更好;如果存在并发写,500 也会造成锁竞争。我会在参数里暴露 P_COMMIT_SIZE,上线后压测时调一下,找到最优值再固定下来。

第四,日志里要记录源数统计。程序开始前先输出 SELECT COUNT(*),结束时输出实际处理行数。核对两个数是否一致,是最快的正确性检查手段。只要源数和处理后数对不上,就说明有漏数据或者有重复数据,根本不用等财务发现。

第五,也是最关键的,复制逻辑要设计成“重新执行也不坏”。你没法保证每次调度都一切顺利,总有网络断了、数据库重启、请求超时的情况。只要处理逻辑是幂等的,无论业务人员手工重新提交多少次,结果都一样。这个原则从我第一次做 EBS 定期成批复制功能以来,一直放在设计第一位。

我在 EBS 里的定期成批复制做得越多,越发现决定项目成败的往往不是复制本身,而是重跑逻辑和日志是否完善。你花一天把代码写完,可能省不出太多时间;但如果你把参数、幂等、日志和清理方案想清楚了,上线之后就能少被财务叫醒好几次。接下来如果你的项目里也有固定资产成批增加复制、工艺路线取数同步这类需求,建议先按这个思路画一画数据流和异常处理图,再动手写代码。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询