news 2026/9/16 9:43:01

固定资产新增API实战:从表设计到接口联调全流程解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
固定资产新增API实战:从表设计到接口联调全流程解析

做固定资产管理(FA,Fixed Asset)的人应该都有同感:资产新增这个动作看着简单,真要做成接口和页面联动时,坑特别多。最近我整理了一套"FA 新增资产API Demo",把新增资产的完整链路从数据库表设计一直串到HTTP接口返回,正好可以分享出来给有同样需求的人参考。

这套Demo解决的是最典型的业务场景:用户在资产管理系统里录入一台新设备,系统需要生成资产编码、校验分类、计算原值、写入数据库,还要在失败时完整回滚。它适合正在做ERP、EAM、后勤管理系统,或者想学习如何把企业级"新增类"接口做规范的人。无论是刚接触API开发的新手,还是被资产重复、编码规则混乱折磨过的老手,这套Demo都能给你一个可以直接抄作业的骨架。

围绕这个Demo,我会把背后的设计思路、字段校验、幂等处理、事务边界、异常排查一次讲透,而且尽量用"我在现场怎么操作"的方式来写,不是教科书式的概念罗列。

1. 为什么需要"新增资产API":从业务痛点说起

1.1 FA(Fixed Asset)系统里的"新增资产"到底在做什么

很多非资产管理人员会把"新增资产"理解成"往表里insert一条记录",但如果实际做过企业级FA系统就知道,一个标准的新增资产动作,至少涉及五件事:资产生命周期的起点创建、资产编码的生成、财务原值与折旧参数的初始化、使用部门和位置的分配、资产标签打印所需基础数据的落库。

以最常见的设备类资产为例,一台笔记本电脑从采购入库到成为"固定资产",中间包含采购单关联、验收状态确认、存放地点登记、保管人确认等环节。FA新增资产API要处理的,就是把这些分散的数据一次性组织好,按正确的业务顺序写入核心资产表及相关子表。

这个过程中最容易犯的错是"只盯着主表"——结果资产主表有记录,但资产分类表、存放地点表、附件表全是空的,后面查询统计全部出问题。Demo的设计目标就是避免这种"半截数据"。

1.2 独立API的定位:给谁用、解决什么问题

为什么要把"新增资产"单独做成一个API,而不是继续沿用传统单体应用的内部Service?我这次做Demo的出发点有三个。

第一是给外部系统对接用。很多企业有独立的采购系统、OA审批流、财务系统,它们都需要在资产验收通过后,自动把数据推送给资产管理平台。没有API,就得靠人工二次录入,不仅慢,还容易录错。

第二是给前端页面复用。资产录入页面要支持"单条新增"和"批量导入",两种入口最终走的其实是同一套业务校验和落库逻辑。把新增能力收敛成一个API之后,页面和导入工具都调同一个接口,逻辑不会漂移。

第三是为了控制变更影响面。资产编码规则、分类校验这类逻辑,在传统代码里经常散落在各种工具类中。收敛成API后,所有新增操作都经过同一道关口,改规则只改一处,排错也只查一处。

1.3 与现有页面的关系:API不是推翻页面,而是补充

一个比较常见的误解是:做了API,原来的页面新增功能就要重写。其实我的做法是两者并存。页面提交时调用同一个新增API,只是页面额外做一些交互提示,比如资产编码实时预览、分类联动查询、必填项高亮。

这样做最大的好处是:页面只是一个"壳",核心业务规则全部下沉到API层。后续如果要把录入入口从Web端换成微信企业号、钉钉小程序,后端一个接口都不用改,前端重新适配就行。Demo里我也特意把Controller做得非常薄,基本不做业务判断,所有逻辑都在Service层,目的就是让API的可复用性最大化。

2. 接口设计:先把字段和流程理清楚

2.1 RESTful资源建模:POST /api/fixed-assets

接口设计第一步是确定资源路径。我推荐用RESTful风格,把"资产"定义为资源,新增操作映射为:

POST /api/fixed-assets Content-Type: application/json

这个路径看起来简单,但背后有一个容易纠结的点:到底是叫"fixed-assets"还是"assets"。如果系统里只有固定资产,叫assets就可以;如果以后还可能有无形资产、低值易耗品,建议从一开始就用fixed-assets这样明确的路径,避免后续扩展时路径冲突。我这次Demo采用的是fixed-assets,防止后期改名。

还有一个细节是版本管理。内部系统可以不用,但如果是开放给第三方对接,建议在路径里加上版本号,例如:

POST /api/v1/fixed-assets

版本号的意义在于:当你对接口做了不兼容升级时,老调用方还可以继续打v1,新调用方用v2,不会出现"别人调你的接口调得好好的,你升级后对方全挂了"的情况。

2.2 字段定义与必填项:资产编码、分类、原值、地点

"新增资产"请求体的字段设计,要贴合实际业务。我整理了一套适合大多数FA系统的标准请求体结构,供参考:

{ "assetCode": "ZC-2025-0001", "assetName": "ThinkPad X1 Carbon", "categoryId": "CAT-00012", "specification": "i7-1365U/16GB/512GB", "originalValue": 12999.00, "purchaseDate": "2025-05-10", "inUseDate": "2025-05-12", "locationId": "LOC-003", "departmentId": "DEPT-008", "custodianId": "USER-1024", "supplierId": "SUP-56", "lifecycleStatus": "ACTIVE", "remarks": "研发部测试用笔记本" }

字段命名我统一采用小驼峰(assetCode, originalValue),在JSON传输层很常见;如果团队用Python较多,可以考虑snake_case(asset_code, original_value),核心是团队内部统一,不要混用。

必填项怎么界定,是业务问题。我的经验是"服务于后续流程的字段必须必填",比如资产分类、原值、使用部门、存放地点,这四个字段如果缺失,资产后续提折旧、做盘点、出统计报表都会出问题。而像供应商、备注、规格型号这类信息,可以作为选填项,不影响主流程。

2.3 校验规则:400 Bad Request是怎么来的

热词清单里有一个高频报错:api error: 400 invalid schema for function 'artifact'。这类错误在开发新接口时太常见了,本质就是"请求体不符合接口定义"。前端传过来的JSON少了字段、类型不对、枚举值不合法,后端都会响应400。

在FA新增资产场景下,常见的400触发点有几个:

  • assetCode缺了,或者格式不满足规则,例如要求ZC-开头,实际传了ABC-开头
  • originalValue传成了字符串,例如"12999.00"而不是12999.00
  • purchaseDate格式不对,要求yyyy-MM-dd,传成了2025/05/10
  • categoryId不在资产分类字典表里,这属于"业务校验失败",不过也应该在400或422里返回具体原因

Demo里我采用了"参数校验 + 异常处理器"双保险:先用Spring的@Validated做基础类型和格式校验,再在Service层做业务规则校验。这样既能快速拦截低级错误,又不会漏掉需要查数据库才能判断的业务问题。返回的报错信息统一为code + message格式,方便调用方直接提示给用户。

提示:不建议在400响应里只返回"参数错误"这四个字。要把具体的字段名和期望格式带出来,例如"field=originalValue, reason=must be a number"。调用方看到提示后自己就能定位,能省掉大量沟通成本。

2.4 幂等性设计:防止重复提交

资产新增最怕的就是"重复"。用户手抖点了两次提交,或者对接方网络超时后重试,同一台设备就可能被插入两条资产记录。解决这个问题,我推荐两个思路。

第一个思路:利用资产编码的唯一约束。如果前端生成资产编码后提交,后端在资产表上建一个唯一索引,重复插入时数据库会直接报DuplicateKeyException,被全局异常处理器转换成一个友好的业务提示。

第二个思路:引入请求幂等键(Idempotency-Key)。调用方在Header里传一个唯一标识,例如Idempotency-Key: uuid-xxx,后端在处理前先查一张幂等记录表,如果这个Key已经处理成功过,就直接返回上次的结果,不再执行新增逻辑。

Demo里我采用的是双保险:资产编码有唯一索引 + 对提交请求做了幂等键检查。这样即使并发环境下两次请求同时到达,也能保证数据最多成功提交一次。

3. Demo实现:从零到一跑通一个最小可用接口

3.1 技术栈选择:Spring Boot + MyBatis Plus

企业级FA系统后端,我见得最多的是Java技术栈,这套Demo我选择了Spring Boot 2.7 + MyBatis Plus 3.5,原因是它上手快、事务管理成熟,而且企业里招人容易。如果你更习惯Python,用FastAPI + SQLAlchemy也能复现这套逻辑,核心思想一致,只是语法有差异。

我建议建一个独立的Maven模块,包名结构如下:

com.example.fa ├── controller │ └── FixedAssetController.java ├── service │ ├── FixedAssetService.java │ └── impl │ └── FixedAssetServiceImpl.java ├── mapper │ ├── FixedAssetMapper.java │ └── AssetIdempotentMapper.java ├── model │ ├── entity │ │ └── FixedAsset.java │ ├── dto │ │ ├── FixedAssetCreateRequest.java │ │ └── FixedAssetCreateResponse.java │ └── vo │ └── ResultVO.java └── exception ├── BizException.java └── GlobalExceptionHandler.java

这样分层的价值在于:Controller只接收请求和返回结果,Service主导业务规则和事务,Mapper只做数据读写。出现问题的时候,按"Controller -> Service -> Mapper"一层一查,很快就能定位。

3.2 数据表设计与事务控制

资产主表我建得相对精简,重点字段如下:

CREATE TABLE fixed_asset ( id BIGINT PRIMARY KEY AUTO_INCREMENT, asset_code VARCHAR(64) NOT NULL, asset_name VARCHAR(128) NOT NULL, category_id VARCHAR(32) NOT NULL, specification VARCHAR(255), original_value DECIMAL(12, 2) NOT NULL, purchase_date DATE NOT NULL, in_use_date DATE, location_id VARCHAR(32), department_id VARCHAR(32), custodian_id VARCHAR(32), supplier_id VARCHAR(32), lifecycle_status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE', remarks VARCHAR(500), idempotent_key VARCHAR(64), created_by VARCHAR(64), created_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_asset_code (asset_code), UNIQUE KEY uk_idempotent_key (idempotent_key) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

这里有两个设计点值得说。

一是uk_idempotent_key唯一索引,可以兜底幂等键的并发冲突;没有这个索引,单纯靠应用层判断很容易出现两个请求同时查到"无记录"然后一起插入的情况。

二是DECIMAL(12, 2)而不是FLOAT。资产原值要精确到分,用FLOAT/DOUBLE会产生精度漂移。别小看这一分钱,财务对账的时候能让你查到怀疑人生。

事务控制方面,我直接在Service实现类上加@Transactional(rollbackFor = Exception.class)。默认情况下Spring只在遇到RuntimeException时回滚,但如果资产插入过程中抛了检查型异常,不加rollbackFor就不会回滚,所以这里明确指定了Exception.class,保证任何异常都触发回滚。

3.3 核心代码实现:Controller、Service、Mapper

Controller层只做三件事:接收JSON、调用Service、包装响应。

@RestController @RequestMapping("/api/v1/fixed-assets") public class FixedAssetController { @Resource private FixedAssetService fixedAssetService; @PostMapping public ResultVO<FixedAssetCreateResponse> create(@Validated @RequestBody FixedAssetCreateRequest request, @RequestHeader(value = "Idempotency-Key", required = false) String idempotencyKey) { FixedAssetCreateResponse response = fixedAssetService.createAsset(request, idempotencyKey); return ResultVO.success(response); } }

Service层是业务核心,主要逻辑是:幂等检查 -> 业务校验 -> 生成编码 -> 保存资产 -> 保存关联数据。

@Service public class FixedAssetServiceImpl implements FixedAssetService { @Resource private FixedAssetMapper fixedAssetMapper; @Resource private AssetIdempotentMapper idempotentMapper; @Override @Transactional(rollbackFor = Exception.class) public FixedAssetCreateResponse createAsset(FixedAssetCreateRequest request, String idempotencyKey) { if (StringUtils.hasText(idempotencyKey)) { FixedAsset existAsset = fixedAssetMapper.selectByIdempotentKey(idempotencyKey); if (existAsset != null) { return FixedAssetCreateResponse.from(existAsset); } } // 1. 资产编码生成,默认ZC-yyyyMMdd-四位流水 String assetCode = generateAssetCode(request.getCategoryId()); request.setAssetCode(assetCode); // 2. 业务规则校验 validateAsset(request); // 3. 插入资产 FixedAsset asset = new FixedAsset(); BeanUtils.copyProperties(request, asset); asset.setIdempotentKey(idempotencyKey); fixedAssetMapper.insert(asset); // 4. 如果有扩展子表,在这里同步写入 // assetLifecycleMapper.insert(...); return FixedAssetCreateResponse.from(asset); } }

有一个容易踩的坑:BeanUtils.copyProperties拷贝时,如果request里的字段名和entity不一致,比如request是assetCode,entity也是assetCode,拷贝没问题;如果两边命名规范有差异,比如custodianId拷贝到了userId,就会静默丢数据。所以用BeanUtils后,建议打印一条debug日志把关键字段打出来核对一遍。

3.4 联调与测试:用curl和Postman模拟调用

接口写完后,最直接的方式就是用curl做冒烟测试。我通常会把一个标准的"成功新增"和"重复提交"各打一遍。

先测成功新增:

curl -X POST "http://localhost:8080/api/v1/fixed-assets" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: uuid-0001" \ -d '{ "assetName": "ThinkPad X1 Carbon", "categoryId": "CAT-00012", "originalValue": 12999.00, "purchaseDate": "2025-05-10", "departmentId": "DEPT-008" }'

预期返回:

{ "code": 0, "message": "success", "data": { "id": 1, "assetCode": "ZC-20250510-0001" } }

再测一次幂等,把同样的Idempotency-Key再发一遍:

curl -X POST "http://localhost:8080/api/v1/fixed-assets" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: uuid-0001" \ -d '{ ... 同上 ... }'

这时返回的应该还是上一次的assetCode,而不是新生成一个编码,数据库中资产记录也不会新增第二条。

注意:测试时别忽略"并发场景"的验证,虽然用手工curl不好模拟并发,但可以使用Postman Collection Runner或JMeter同时发5个相同请求,确认最终数据库里资产记录只有1条。这个测试能暴露很多并发下的事务问题。

3.5 返回结果设计:成功与失败语义

API返回格式我习惯用统一的ResultVO,结构固定为code、message、data三个字段。

{ "code": 0, "message": "success", "data": {} }

code为0时表示成功,非0表示失败。不要用HTTP状态码本身表达业务错误,因为HTTP状态码只有几十个,不够用来区分"校验失败""分类不存在""编码重复""幂等键重复"这类不同语义。标准做法是:HTTP状态码只区分大类型,200表示请求已处理,400表示参数类错误,500表示服务端异常;具体的业务错误码放在响应体code里。

Demo里我定义了几类常见的业务错误码,便于接入方对账:

code含义
10001资产编码生成失败
10002资产分类不存在
10003资产编码已存在
10004幂等键重复,返回历史结果
10005原值超出允许范围

这样设计的目的,是让对接方可以通过code做自动化处理,而不是靠解析字符串来判断错误内容。

4. 常见问题与排查技巧实录

4.1 400 invalid schema:请求体与接口定义对不上

这是我做完Demo后在联调阶段遇到最多的一类问题,热词里反复出现的api error: 400 invalid schema就是这个类型。现象是调用方明明"感觉"自己传对了,后端却直接拒绝。

排查思路就三步:

第一,把请求体原样打印出来,对照接口文档逐字段检查。重点看字段名是下划线还是驼峰,例如asset_codeassetCode在后端默认配置下不是同一个字段。

第二,检查类型是否匹配。JSON里originalValue: 12999.00没问题,但originalValue: "12999.00"在开启严格类型校验时会报错。如果要兼容字符串数字,可以在DTO上做自定义反序列化。

第三,看枚举值是否合法。如果lifecycleStatus只允许ACTIVEDISPOSEDSCRAPPED这几个值,传IN_USE就会校验失败。建议在枚举字段上使用@JsonCreator提供容错解析逻辑。

提示:调试这类400错误,不要盯着浏览器或者Postman的"Prettify"看,直接把后端打印的请求体日志和错误堆栈拿过来,通常一眼就能定位。

4.2 500服务端错误:事务回滚与日志定位

服务端日志出现大段Exception,并且数据库中出现了"主表有记录、子表没有"或者"主表没有、子表有"的诡异情况,大概率是事务边界没有控制好。

有一次我在Demo里测试批量导入时,发现fixed_asset表插入了10条数据,但是到第7条时因为资产分类编码非法抛了异常,最终数据库只回滚了部分数据。排查后确认问题出在"批量新增"和"单条新增"共用了一个Service方法,而这个方法内部用了try-catch吞掉了异常,导致Spring事务感知不到错误,无法触发回滚。

解决方式很简单:不要轻易在事务方法内部catch异常后吞掉。如果确实需要捕获异常做补偿处理,至少要用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()显式标记回滚,或者把可能失败的子逻辑拆到一个独立的事务方法里通过REQUIRES_NEW处理。

500类错误还有一个常用排查技巧:在全局异常处理器里,把e.printStackTrace()替换成log.error("...", e),并带上请求路径和参数。这样日志里能直接看到"是哪条资产数据触发的问题",而不是只有一段通用的NullPointerException堆栈。

4.3 幂等键失效:重试场景下的重复资产

幂等键失效是我在实际对接中最头疼的问题,表现是:调用方重试后,资产记录出现了两条,且这两条记录的asset_code不同,或者idempotent_key都为NULL。

出现这个情况通常是两个原因:

第一,调用方重试时没有带上同一个幂等键,或者第一次根本没传。这需要对接方在客户端统一维护幂等键,一般用UUID,一次业务操作一个Key,不要每次请求都重新生成。

第二,应用层先查幂等记录,查不到再插入,但并发窗口期两个请求同时进入,都查不到,于是都执行插入。正是因为我在资产表上建了uk_idempotent_key唯一索引,才能保证这种情况下只有一个请求插入成功,另一个会抛DuplicateKeyException。所以幂等键保存的字段一定要建唯一索引,只靠应用层判断是不可靠的。

如果出现了重复资产,处理方式不是直接delete,而是先把资产状态改成"暂存",再做数据订正。直接删除涉及资产编号连续性、财务凭证关联等问题,风险太大。

4.4 排查速查表

我根据自己的实操经验,整理了一张速查表,适合资产类API联调时快速定位问题:

现象可能原因处理建议
请求返回400JSON字段名或类型不符打印请求体,逐字段对照接口文档
返回字段全部为nullBeanUtils.copyProperties字段名不匹配检查DTO和Entity的字段名映射
主表成功但子表无数据子表写入逻辑未执行或事务边界不对检查事务注解和子表Mapper调用
资产编码重复插入成功缺少唯一索引或生成规则冲突建唯一索引,编码规则加分布式锁
同一请求产生两条记录幂等键未传或并发窗口客户端统一幂等键,DB层加唯一索引
响应超时关联查询过多或数据库锁等待打印慢SQL日志,检查事务持有锁的时长

这几种问题几乎覆盖了资产类API从开发到联调的大部分"疑难杂症"。遇到问题时,按表格里的方向去查,基本不会白跑弯路。

5. 一些实操心得与小技巧

这套Demo做完之后,我最大的感受是:资产新增接口并不难写,难的是把所有边界条件都考虑到。下面分享几个我实际开发中沉淀下来的习惯,希望能帮你少踩坑。

第一个习惯是:所有涉及金额的字段,后端一律使用BigDecimal,数据库用DECIMAL,JSON序列化时配置保留两位小数。资产原值、残值率、月折旧额这些字段,一旦因为精度问题出现一分钱差异,财务对账就会非常痛苦。

第二个习惯是:新增接口里不要只返回"成功"两个字,最好把生成的资产编码、资产ID一并返回。这样前端拿到成功结果后,可以直接跳到资产详情页或者打印标签,省去一次"根据资产名称查询"的额外请求。

第三个习惯是:给关键接口增加一个"dryRun"模式,也就是试算模式。调用方传?dryRun=true时,后端只做校验、生成编码,但不落库。这个功能对接第三方系统时特别有用,对方可以先用dryRun验证自己的数据合法性,确认无误再正式调用。Demo里我预留了这个参数位,做起来也不复杂,无非是Service层加个分支判断。

第四个习惯是:写一份简短的接口对接文档,只要一页A4纸,包含请求示例、必填字段、错误码表。不要写几十页的复杂文档,对接方的开发通常只需要知道"传什么、回什么、出错怎么办",这份文档能省掉大量反复沟通的时间。

最后说一个小细节:如果你们的FA系统有多套环境(测试、UAT、生产),建议在API返回结构里加一个traceId字段。调用方出问题时,把traceId发给你,你在日志里一搜就能定位到那一整条调用链。没有traceId的时候,全靠时间和IP去猜,排查效率低很多。我在Demo的全局异常处理器里已经加了这个字段,你可以根据自己的日志框架再调整一下格式。

这套Demo的完整代码其实就是一两天的工作量,但设计思路和踩坑经验是多年项目里攒出来的。如果你正准备做资产模块的接口,或者正在被"重复资产""数据不一致"这类问题折磨,希望这套从接口设计到实现排错的完整链路,能帮你少写几版返工代码。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 9:42:49

新闻资讯微信小程序毕设开发全攻略:从选题到答辩避坑指南

每年到毕设季&#xff0c;都会有大量同学来问同一个问题&#xff1a;做什么题目既好过、又能写进简历&#xff0c;最好还能拿出来演示不丢人。我做了这么多年开发&#xff0c;也带过不少实习生&#xff0c;实话实说&#xff0c;新闻资讯微信小程序这类题目始终是稳妥之选。它不…

作者头像 李华
网站建设 2026/9/16 9:42:00

转向器壳体三维夹具设计:提升加工精度与效率

1. 项目概述&#xff1a;转向器壳体夹具的三维设计在汽车零部件制造领域&#xff0c;转向器壳体作为转向系统的核心承载部件&#xff0c;其加工精度直接影响整车的操控性能和安全系数。传统二维夹具设计存在定位误差大、调试周期长的问题&#xff0c;而三维数字化设计能实现加工…

作者头像 李华
网站建设 2026/9/16 9:40:29

企业级Agent平台如何让“超级个体”进化为“超级团队”?

1. 单打独斗的 Agent 撑不起企业场景&#xff1a;WorkBuddy Enterprise 到底在补什么课过去一年半&#xff0c;圈子里最热的概念就是“超级个体”。一个人挂三五个 Agent&#xff0c;写代码、做数据分析、画图、写周报全包&#xff0c;感觉一个人就是一支队伍。这个说法确实有吸…

作者头像 李华
网站建设 2026/9/16 9:40:07

51单片机车载酒精检测系统设计与工程实践

简介&#xff1a;本资源是一套完整的基于STC系列51单片机的车载防酒驾智能控制系统毕业设计解决方案&#xff0c;面向电子类、自动化及嵌入式方向本科生与初学者&#xff0c;解决酒精浓度实时检测、声光报警与点火联锁控制等核心工程问题。压缩包共44个文件&#xff0c;约998KB…

作者头像 李华
网站建设 2026/9/16 9:39:52

ETL全面解析:从抽取转换加载到数仓实战避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 9:39:13

Axure 9.0 动态面板创建方式

&#xff08;1&#xff09; 直接拖拽新建&#xff08;空白面板&#xff09;左侧基础元件库找到「动态面板」&#xff0c;直接拖拽到画布&#xff0c;默认生成空白面板&#xff0c;可自定义尺寸&#xff0c;后续双击进入编辑状态、添加内容。&#xff08;2&#xff09; 选中内容…

作者头像 李华