换系统时最容易出事的环节不是业务流程配置,而是基础资料和历史数据的导入。手工导 Excel 几百条还行,上万条就必须走接口。这篇把星空 WebAPI 做批量导入的工程细节讲清楚:怎么保证幂等、怎么映射字段、常见报错怎么处理。
一、接口选择与基本调用形态
星空侧用 WebAPI,通过 DynamicFormService 的 BatchSave 提交;星辰侧走 OpenAPI,形态类似,都是"登录拿会话 → 提交业务对象 → 解析返回体"。
登录接口:
POST /Kingdee.BOS.WebApi.ServicesStub.AuthService.ValidateUser.common.kdsvc
提交接口:
POST /Kingdee.BOS.WebApi.ServicesStub.DynamicFormService.BatchSave.common.kdsvc
返回体统一是 Result.ResponseStatus 结构,成功与否看 IsSuccess,逐条错误在 Errors 数组里。不要只看 HTTP 200 就认为成功,业务错误也是 200。
二、幂等:迁移必须能重跑
迁移脚本一定会重跑——试迁一次、修数据一次、正式迁一次。如果脚本不幂等,第二次跑就会生成重复基础资料。
做法是"先查后写",以业务编码作为幂等键:
// 幂等导入:以编码为业务键,存在则跳过或更新,不存在才新增
public async Task ImportAsync(List rows, int batchSize = 200)
{
var result = new ImportResult();
// 1. 一次性把已存在的编码捞回来,避免逐条查询
var existing = await QueryAllNumbersAsync(“BD_MATERIAL”);
result.Skipped = rows.Where(r => existing.Contains(r.Number)).ToList();
// 2. 只提交不存在的 var toCreate = rows.Where(r => !existing.Contains(r.Number)).ToList(); foreach (var chunk in Chunk(toCreate, batchSize)) { var payload = BuildBatchSave("BD_MATERIAL", chunk); var resp = await PostAsync( "/Kingdee.BOS.WebApi.ServicesStub.DynamicFormService.BatchSave.common.kdsvc", payload); var jo = JObject.Parse(resp); var status = jo["Result"]?["ResponseStatus"]; if (status?["IsSuccess"]?.Value<bool>() != true) { // 逐条记录失败原因,不整体回滚,保留已成功部分 foreach (var err in status?["Errors"] ?? new JArray()) result.Failed.Add(new FailedRow( err["FieldName"]?.Value<string>() ?? "", err["Message"]?.Value<string>() ?? "unknown")); } result.Succeeded += chunk.Count; await Task.Delay(200); // 控制提交频率,避免触发限流 } return result;}
关键点三个:批量前先全量查一次已有编码(比逐条查快一个数量级)、失败不整体回滚(保留已成功的部分,失败清单人工补)、批次之间留间隔(防止限流)。
三、字段映射表
基础资料里最麻烦的是物料。老系统字段到星空字段的映射建议单独维护成一张配置表,不要硬编码在代码里:
老系统字段
星空字段
转换规则
存货编码
FNumber
去空格、统一大写;重码必须先合并再导
存货名称
FName(多语言实体)
直接映射,注意实体层级
规格型号
FSpecification
空值补 -,避免必填校验失败
计量单位
FBaseUnitId.FNumber
按单位编码映射,单位必须先导入
税率
FTaxRateId.FNumber
缺省取 13
启用状态
FForbidStatus
老系统"停用" → C,启用 → A
计价方法
FCostMethod
老系统叫法各异,需单独做映射字典
有两个细节容易被忽略:多语言字段要按实体结构写(FName 下面还有语言项),基础资料有依赖顺序(计量单位 → 物料 → BOM),顺序错了会一路报"基础资料不存在"。
四、报错处理表
报错现象
原因
处理方式
LoginResultType = 0
appid / appsecret 不匹配,或用户被锁
核对第三方系统登录授权,检查用户状态
会话失效 / 401
会话默认有超时(约 30 分钟)
捕获后自动重登,重放当前批次
单据编号重复
FNumber 已存在
走"存在则更新"分支,或跳过并记入清单
必填字段为空
计量单位、税率未映射到
记入失败清单人工补,不做默认值兜底
基础资料不存在
依赖对象未先导入
按依赖顺序导:单位 → 物料 → BOM
请求体过大 400
单批条数超限
分批提交,实测 200 条/批比较稳
提交后状态不对
保存与提交审核没拆开
IsAutoSubmitAndAudit = false,保存成功后再单独调提交审核
中文乱码
编码未指定 UTF-8
请求体统一 UTF-8,不要用默认编码
其中"必填字段为空"这一条最重要:千万不要给缺失字段编默认值。用默认值蒙混过去,数据看起来迁完了,实际问题被推迟到几个月后才发现,追溯成本极高。宁可失败、宁可人工补。
五、几个踩过的坑
坑一:只看 HTTP 状态码。 业务失败也返回 200,必须解析 ResponseStatus.IsSuccess。
坑二:把期初余额当普通单据导。 期初是独立业务对象,有专门的导入方式和校验规则,不能走普通保存。而且期初余额的三个科目方向(银行、往来、库存)必须与财务人工核对后的数字一致,接口只负责搬运,不负责判断数字对不对。
坑三:不记录迁移批次。 建议每次导入都落一条批次记录(时间、条数、成功数、失败数、操作人)。出问题时能定位到具体是哪一批带进来的。
坑四:忽略性能。 上万条基础资料逐条提交,可能要几个小时。改成批量提交 + 并发控制后,通常能压到十几分钟。但并发不要开太高,服务端有限流。
小结
接口批导的技术难点不在调通,而在三件事:幂等(能安全重跑)、失败可见(失败清单要能导出、能人工补)、依赖有序(基础资料先后顺序)。这三点做到了,迁移就是可控的工程动作;做不到,就会变成反复补数据的消耗战。