旧Excel台账里,“行政部”和“行政”是同一个部门;资产编码00128被保存成了128;一行“办公电脑20台”,原值写的是整批金额。把文件上传成功,不代表这份台账已经正确进入资产系统。
历史数据导入真正要解决的是:旧表里的记录,怎样转换成系统可以识别、关联和追溯的资产事实。上传、解析只是开始,后面还要明确字段含义、确认业务粒度、处理错误行,并核对最终数量与金额。
本文用一份100条记录的旧台账作教学案例,给出暂存数据结构、字段校验、批次处理和验收方法。技术示例面向Java 17、Spring Boot与MySQL 8.x,字段和数量均为演示,不代表真实项目实测。本文重点是首次迁移的完整流程;同一文件反复提交、跨批次重复建卡等问题将在重复导入专题单独展开。
目录
- 先确定一行数据代表什么
- 模板不是换列名,而是约定字段含义
- Excel读出来的值,未必是用户看到的值
- 先导入暂存区,错误行不要污染正式台账
- 预检通过以后,还需要一次提交确认
- 导入结果应能回答“哪一行,为什么没进来”
- 验收不能只看“成功85条”
- 需要撤销时,先确认资产有没有被使用
一、先确定一行数据代表什么
设计模板前,先看原表的记录粒度。一行是单台设备、采购批次,还是某类资产的汇总?如果这个问题没有解决,再严格的格式校验也可能导入错误。
| 旧台账内容 | 不能直接做的处理 | 应确认的业务事实 |
|---|---|---|
| 办公电脑20台,原值100000元 | 生成20张卡,每张原值100000元 | 金额是总额还是单价,是否需要逐件管理 |
| 资产编码00128 | 自动转成数值128 | 编码是否保留前导零 |
| 使用人张伟 | 随便匹配第一个同名员工 | 员工号、所属组织及离职状态 |
| 状态“正常” | 一律转换为在用 | “正常”是技术状况还是使用状态 |
| 原值为空 | 自动填0 | 数据缺失还是确实为零 |
本例限定为“一行一件实物、一张资产卡”,数量必须为1,金额统一人民币元。批量汇总行先进入人工拆分流程,不在本次导入中自动展开。拆分会影响数量和金额分摊,应保留原记录与拆分后的对应关系。
还要约定迁移截止时间。系统迁移的是某一时点的资产状态;若导出后旧表继续发生借用、归还和调拨,需要冻结窗口或补录增量清单,不能导完快照就宣布账实同步。
图1:旧文件先经过粒度确认、映射和预检,再进入正式入库与对账,上传成功只是流程起点。
二、模板不是换列名,而是约定字段含义
模板应带版本号,例如asset-import-v1。导入任务保存模板版本及映射版本,避免下个月修改字段解释后,无法重现旧批次的处理规则。
| Excel字段 | 目标字段 | 类型与规则 | 不满足时 |
|---|---|---|---|
| 旧资产编码 | legacy_code | 文本,按约定保留前导零 | 空值进入确认,不猜编码 |
| 资产名称 | asset_name | 非空文本,长度有上限 | 行错误 |
| 所属公司编码 | company_id | 在当前授权组织范围内映射 | 拒绝越界 |
| 部门编码 | department_id | 属于目标公司且允许使用 | 行错误 |
| 使用人工号 | custodian_id | 可选,非空则需唯一匹配 | 同名不能自动认领 |
| 启用日期 | service_date | yyyy-MM-dd,使用LocalDate | 格式或日历错误 |
| 原值(元) | original_cost | 非负Decimal,本例最多两位小数 | 不自动舍入 |
| 使用状态 | usage_status | 按已确认字典转换 | 未知值待处理 |
是否允许停用部门承接历史记录、离职员工如何迁移,要由业务规则决定。例如系统可以保留历史保管人信息,同时把当前责任人设为待确认;不能为了通过关联校验,直接把全部旧记录挂到管理员名下。
图2:原始列先标准化,再转换为系统标识;名称映射存在歧义时应拦截,而不是选一个看起来相近的值。
公司范围必须来自服务端授权上下文。即使Excel有公司编码,也只能从当前用户可操作范围内选择,不能让上传者通过改一列数据跨公司写入。
三、Excel读出来的值,未必是用户看到的值
1. 编码列应明确要求文本
Excel数值单元格可能只存128,显示格式却是00000。Apache POI的DataFormatter可按单元格格式取得显示文本,但如果原文件已经丢失前导零和格式,就无法凭读取工具恢复00128。
因此,应在模板中把编码设为文本,并在接收时检查单元格类型;超过Excel数值精度的长编号,也不能先读成浮点数再转字符串。
2. 金额不通过double中转
本例不接受货币符号、千位逗号、科学计数法及超过两位的小数,要求先把待导入值整理成规范十进制文本。下面是Java 17中可独立使用的字段解析示例,金额上限需按实际系统调整:
importjava.math.BigDecimal;importjava.math.RoundingMode;importjava.util.regex.Pattern;publicfinalclassImportAmountParser{privatestaticfinalPatternAMOUNT=Pattern.compile("(?:0|[1-9][0-9]{0,13})(?:\\.[0-9]{1,2})?");publicstaticBigDecimalparse(Stringraw){if(raw==null||!AMOUNT.matcher(raw.trim()).matches()){thrownewIllegalArgumentException("AMOUNT_FORMAT_INVALID");}returnnewBigDecimal(raw.trim()).setScale(2,RoundingMode.UNNECESSARY);}}这是单字段规则,不负责Excel读取,也不代表所有企业都只能保留两位小数。关键是按明确契约拒绝或转换,而不是悄悄四舍五入。
| 输入 | 本例预期 |
|---|---|
| 1250 | 1250.00 |
| 1250.50 | 1250.50 |
| 1,250.50 | 拒绝,要求规范格式 |
| 空字符串 | 拒绝,不当作0 |
| 12.345 | 拒绝,不隐式舍入 |
| -1 | 拒绝,不符合本例原值约定 |
3. 日期和公式分开处理
启用日期优先要求ISO格式文本,严格解析为LocalDate。若兼容Excel日期数值,应由读取库识别日期格式和工作簿日期系统,不自己硬编码起点去换算。
金额等关键字段,本例拒绝公式单元格,要求在迁移副本里固化并复核数值,同时保留原文件。公式缓存可能未更新;支持公式计算的系统还需要明确计算范围、外部引用与失败处理,不能直接假定“显示出来就是最终值”。
上传端同时限制文件大小、工作表数、行列数和解压资源,校验真实格式,不仅检查扩展名。大文件采用适合的流式读取方案;不能取消压缩安全限制来解决所有读取异常。
四、先导入暂存区,错误行不要污染正式台账
建议分成三类记录:批次、来源行和正式资产。正式资产模型继续使用项目现有设计,不把导入状态混进资产使用状态。
| 记录 | 最低字段 |
|---|---|
| import_batch | id、company_id、file_hash、template_version、mapping_version、snapshot_time、status |
| import_row | batch_id、source_sheet、source_row_no、raw_payload、normalized_payload、status、error_code |
| asset_card | 资产字段、source_batch_id、source_row_id、created_by、created_at |
raw_payload保留来源值,normalized_payload保留映射后的目标值,两者不能混用;其中人员等敏感数据应限制访问并设置保留期限。文件摘要用于识别来源与追溯,不单独承担所有业务防重规则。
以下为MySQL 8.x隔离测试库中的暂存表示例,假定批次表已由项目管理;它不含完整外键、审计和租户安全设计,不能直接替代生产表:
CREATETABLEasset_import_row_demo(idBIGINTPRIMARYKEYAUTO_INCREMENT,batch_idBIGINTNOTNULL,source_sheetVARCHAR(64)NOTNULL,source_row_noINTNOTNULL,raw_payload JSONNOTNULL,normalized_payload JSONNULL,statusVARCHAR(24)NOTNULL,error_codeVARCHAR(64)NULL,error_messageVARCHAR(500)NULL,target_asset_idBIGINTNULL,UNIQUEKEYuk_source_row(batch_id,source_sheet,source_row_no),KEYidx_batch_status(batch_id,status))ENGINE=InnoDB;唯一键只保证同一批次内来源行不会重复落暂存,不表示不同文件中的同一资产已经自动防重。正式写入仍须保留业务唯一约束,不能把导入变成绕过约束的特殊通道。
图3:文件格式、字段值、业务关联和授权范围依次校验,任何一层失败都应落到具体来源行和错误原因。
本例100条来源记录中,90条通过,6条部门映射不明确,4条金额格式不符,预检结果应清楚显示“90条可提交、10条待处理”,而不是“成功90%”就直接结束。
如果一行有多个错误,可用独立错误明细表记录字段、错误码与提示;不要只留下最后一个错误覆盖此前结果。
五、预检通过以后,还需要一次提交确认
预检与正式提交之间,部门可能停用,权限可能调整,资产编码也可能被其他请求占用。因此,预检结果是候选结果,不是无限期有效的写入许可。
确认页面至少展示:目标公司、截止时点、模板版本、可导入行数、待处理行数,以及有效记录的金额小计。若金额有解析失败,不能把该小计称为“原文件总金额”。
批次状态可以采用:UPLOADED → VALIDATING → READY → IMPORTING → COMPLETED或PARTIAL;批次级不可恢复错误进入FAILED。行状态单独采用READY、INVALID、IMPORTED、WRITE_FAILED,两个层级分别表达流程与结果。
图4:分批提交可能得到部分成功,批次状态与每行结果共同决定如何恢复,不能用一条“导入失败”覆盖已提交事实。
本例选择“允许明确确认后部分导入”:90条有效记录先提交,10条待纠正。若业务要求整份文件同时生效,则应采用另一个明确方案,不能边写边承诺全有或全无。
提交事务的流程伪代码如下:
接收提交指令: 服务端验证操作者对批次与目标公司的权限 原子地将READY批次认领为IMPORTING 逐个受控大小的数据块: 开启数据库事务 锁定或认领本块尚未处理的来源行 重新校验关联、权限、业务约束及映射版本 写入正式资产,并记录来源行标识 在同一事务内把来源行更新为IMPORTED 提交事务 失败时: 回滚当前块 在独立、可靠的状态记录中登记失败原因 汇总已提交行与未提交行,进入可恢复状态如果一个块中数据库约束冲突,简单实现可以回滚整块并标记待重试;若要求其余行继续成功,就要单独设计行级事务或失败隔离,不能在同一失败事务里catch后继续假装提交成功。
Spring事务还需验证代理调用、回滚规则与多数据源边界。不要在导入事务内临时执行建表、改表或TRUNCATE;MySQL多类DDL会隐式提交,不能靠普通ROLLBACK保证撤销。
进程在某块提交后崩溃,恢复任务应以已持久化的来源行状态为准,不能重新从文件第一行盲写。防重复点击、重试认领与跨批次幂等的完整实现留到下一篇,但这不意味着第一版可以取消唯一约束。
六、导入结果应能回答“哪一行,为什么没进来”
错误回执至少包含工作表、Excel原行号、来源编码、字段、错误码及修正提示。保留原行号,不把有效记录过滤以后重新编号,避免用户对错位置。
| 原行号 | 字段 | 错误码 | 提示示例 |
|---|---|---|---|
| 18 | 部门编码 | DEPARTMENT_NOT_FOUND | 当前公司下未找到该部门,请确认映射 |
| 42 | 原值 | AMOUNT_FORMAT_INVALID | 请提供不含货币符号与千位分隔符的金额 |
| 67 | 使用人工号 | CUSTODIAN_AMBIGUOUS | 人员标识不能唯一确定,请补充工号 |
回执不直接写SQL异常或数据库路径。导出Excel时把用户原始文本作为文本单元格写入,不将其解释成公式;CSV回执还需针对以等号等字符开头的内容设置明确的公式注入防护规则。
本例如果90条预检有效记录中85条写入,5条因提交时关联变化未写入,汇总应满足:100条来源=85条已导入+10条预检无效+5条提交失败或待处理。状态集合需要互斥且穷尽;仍有未处理状态时必须列出,不能漏计。
可以用只读SQL核对暂存状态分布:
SELECTbatch_id,status,COUNT(*)ASrow_countFROMasset_import_row_demoWHEREbatch_id=1001GROUPBYbatch_id,status;代码中的1001仅作教学批次号。生产查询还需要公司范围与访问权限过滤,知道批次ID不代表拥有查看权限。
七、验收不能只看“成功85条”
数量相等也可能导错部门,金额相等也可能互相错配。核对应该覆盖来源、字段、关联和业务可用性。
图5:导入完成后核对的是资产事实和追溯关系,而不仅是成功数量;未处理记录必须有明确去向。
| 验收用例 | 预期 | 留存证据 |
|---|---|---|
| 文本编码00128 | 前导零按规则保留 | 来源与卡片对照 |
| 汇总行“电脑20台” | 被阻止或进入已批准拆分流程 | 处理记录 |
| 同名部门与人员 | 不随机匹配 | 字典映射结果 |
| 金额空值、三位小数 | 按契约拒绝 | 错误回执 |
| 跨公司修改模板 | 服务端拒绝 | 权限测试记录 |
| 文件含公式 | 按既定政策处理 | 单元格类型与错误码 |
| 块提交后进程中断 | 已成功数据可辨认,未成功部分可恢复 | 批次与行状态 |
| 正式资产抽样 | 可以正常查询、借用或进入对应业务 | 业务操作记录 |
金额对账应使用相同币种和范围:将“已导入来源行的规范金额之和”与“该批次生成正式资产的金额之和”比较,不把整份文件总额与部分成功总额直接相比。本例暂存金额位于JSON中,正式实现宜增加类型化校验列或审查后的对账查询,避免临时强制转换掩盖非法值。
抽样还应覆盖每个部门、状态类型和异常边界,不能只抽前十条正常电脑。导入完成后发生了新的借用或调拨,不宜用当前状态直接与迁移快照硬对,应以迁移时点及后续流水解释差异。
八、需要撤销时,先确认资产有没有被使用
整批导错目标公司,不应立即按source_batch_id删除所有资产。部分资产可能已经被借用、调拨或引用,直接删除会破坏后续记录。
在确认尚无后续业务、依赖完整可控的情况下,可评估有审计记录的撤销流程;已经被业务使用的记录,应采用经过审批的更正、停用或其他补偿方案。文件来源与迁移历史仍应保留,不能靠删除日志“回到没发生过”。
上线前确定谁负责最终核对、错误行什么时候补齐,以及旧台账何时停止继续维护,迁移才算真正交接完成。
固定资产导入不是把Excel搬进数据库,而是把一份有歧义的历史资料,转成有规则、有来源、能继续流转的数据。让AI帮忙开发时,可以把模板、映射表和上述验收用例一起提供,要求它逐项实现并解释异常路径,而不是只生成一个上传按钮。
参考资料
- Apache POI电子表格读取、格式与日期处理
- MySQL会引发隐式提交的语句
- Spring声明式事务回滚规则