简介:这份资源是一套面向Java初中级开发者与企业信息化实施人员的固定资产管理项目源码,基于若依(RuoYi)框架与layui前端构建,可用于学习后台管理系统开发或作为企业内部资产管理二次开发的基础。系统覆盖资产登记、领用借用、归还、维修、调拨转移、报废处理与统计分析等业务流程,并配有组织结构维护与角色权限分配,较完整地呈现了权限控制与业务流转的实现方式。压缩包共1883个文件、约8.88MB,以317个Java源文件与180个HTML页面为核心,辅以90个JS脚本、40个CSS样式、38个XML配置及若干图片与字体资源,另含SQL脚本、yml配置与版本控制文件,便于直接导入运行并梳理工程结构。目前已有563人学习下载,适合通过阅读源码理解若依脚手架分层设计、权限模型和前端表单交互的读者参考借鉴。
1. 固定资产管理为什么不能拿进销存改:一套 Java 源码要解决的问题
接手固定资产管理需求的人,十有八九第一反应是找个进销存系统改一改。真动手就会发现两边的账根本不是一本:进销存关心的是货进来、货出去、库存数量对得上;固定资产关心的是这台设备从采购入库那天起,挂在哪个部门、谁在用、每年折旧多少、什么时候该报废,中间还可能被借走、调拨、送修。数量对得上没用,账实相符才有用。
这套 Java 固定资产管理源码基于若依框架搭建,做的就是这件事:把资产台账、领用、调拨、借用、维修、盘点、报废这一整条生命周期串起来,配上部门数据权限和按月折旧计提。适合正在做毕设、课程设计、需要一套能跑起来的完整业务闭环的人,也适合小单位内部直接部署一套用起来。
2. 需求与选型:资产全生命周期怎么拆成可交付的模块
2.1 先把台账字段定死:一张表撑起后续所有单据
固定资产管理最容易返工的地方不是代码,是字段。台账字段定不下来,后面的领用单、调拨单、盘点表全要跟着改,改到最后只能删库重建。我一般先问三个问题:这台设备归谁管(部门 + 使用人)、值多少钱(原值 + 残值率 + 使用月数)、现在在哪(存放地点 + 状态)。这三个问题回答完,字段基本就定了。
下面是台账主表的关键字段,可以直接当作表结构评审的底稿:
| 字段 | 类型 | 说明 |
|---|---|---|
| asset_code | varchar(32) | 资产编号,全局唯一,也是贴标签的那个号 |
| category_id | bigint | 分类,树形结构,决定编码前缀和默认折旧年限 |
| price | decimal(12,2) | 购置原值,折旧计算的基数 |
| salvage_rate | decimal(5,2) | 残值率百分比,存 5.00 而不是 0.05 |
| service_month | int | 预计使用月数,比“使用年限”好用,折旧直接除 |
| purchase_date | date | 购置日期,折旧起算点 |
| dept_id / keeper_id | bigint | 使用部门 / 使用人,数据权限过滤就靠它 |
| location_id | bigint | 存放地点,盘点的物理依据 |
| asset_status | char(2) | 0 在库 1 在用 2 维修 3 借出 4 待报废 5 已报废 |
| invoice_no / warranty_end | varchar / date | 发票号、保修截止,报修和审计都要查 |
先说残值率为什么存百分比。存 0.05 的话,前端输入框要额外做一次除以 100 的转换,导出报表又要乘回来,来回转换是数字对不上的经典来源。存 5.00,前端页面上写“5%”,后端计算时统一除以 100,只有一处转换,出错概率最低。
再说 asset_status 为什么用 char(2) 而不是 tinyint。项目里状态值经常被拿去做字符串拼接和字典匹配,用定长字符在 MyBatis 映射和前端字典回显上都少一层类型转换,而且 0~5 这几个值一眼能看出业务含义。状态一定不要用 int,因为一旦有人往里面塞了 99,你排查起来连个搜索范围都没有。
最后是 dept_id 必须在主表冗余一份。有人会说部门信息不是能从流转流水表推出来吗?能推,但每次列表查询都要 join 一次流水表取最新一条,几千条资产就开始明显卡。数据权限是按部门过滤的,把 dept_id 放在主表、加一个联合索引,性能是另一个量级。
2.2 为什么选若依框架而不是从零搭 Spring Boot
从零搭一套 Spring Boot,第一周大概率全花在登录、权限、菜单、字典、分页、统一返回、全局异常上,真正写资产相关代码的时间被压缩得所剩无几。若依把这些都给你了:用户/角色/菜单/部门/岗位五张权限表、字典管理、操作日志、登录日志、定时任务、代码生成器,还有@PreAuthorize和@DataScope两个注解直接解决按钮权限和部门数据隔离。
固定资产这个场景里,“部门数据隔离”几乎是刚需——A 部门的管理员只能看到自己部门的资产。这个功能自己写,要处理“本部门”“本部门及以下”“仅本人”“自定义部门”四种数据范围,还要在 XML 里拼 SQL。若依的切面已经把这段逻辑写好了,你要做的只是两件事:实体继承BaseEntity,XML 里留一个${params.dataScope}占位符。
代价也有,得接受它的分层约定。controller只管参数校验和返回、service承担业务和事务、mapper只做数据访问,业务逻辑一律不许下沉到 Controller 或者塞进 Mapper XML 里。这条线守不住,后面加盘点、加折旧的时候,同样的状态校验会散落在三四个文件里,改一次逻辑要找半天。
还有一个细节:资产状态、单据类型、报废原因这类枚举值,放字典表sys_dict_data还是写常量类?我的做法是前端下拉框读字典,后端校验用常量类。字典表是可以在后台被人随手改的,如果状态机校验依赖字典值,某天有人在后台把“已报废”的字典值从 5 改成 6,你的状态机就静默失效了——这种问题查起来相当费劲。
2.3 六个业务模块的边界与依赖关系
模块划分不用太细,六个足够覆盖一个完整毕设或内部系统的交付范围:
基础数据模块:资产分类(树形)、存放地点、供应商、计量单位。所有下游模块都依赖它,所以必须最先做,而且要先把分类树的层级规则定死(建议两级,多了做选择器很痛苦)。
资产台账模块:主表增删改查、批量导入导出、二维码或条码标签打印。导入导出建议直接用若依自带的 Excel 工具类,省掉大半工作量。
流转模块:领用、调拨、借用、归还、送修、维修完成、报废申请、报废审批。这七八个动作不需要八张表,共用一张asset_flow流水表,用flow_type区分即可。
盘点模块:盘点任务(按部门或地点圈定范围)+ 盘点明细,核心产出是盘盈、盘亏、状态不符三类差异。
折旧模块:按月计提,写折旧明细表,累计折旧不允许超过应折旧总额。
报表模块:部门资产汇总、分类金额占比、即将到期报废提醒、保修到期提醒。
依赖方向是单向的:基础数据 → 台账 → 流转/盘点/折旧 → 报表。台账模块不允许反过来调用报表模块的方法,这条约束听着像废话,但真有人为了图省事在台账 Service 里直接查折旧表算净值,最后模块之间咬死,想单独拆出来做接口都拆不动。
3. 总体架构设计:分层结构、数据表与权限落在哪一层
3.1 目录结构与分层职责
若依的模块结构是ruoyi-admin(启动和配置)、ruoyi-framework(安全、数据源、切面)、ruoyi-system(系统管理)、ruoyi-common(工具类),业务代码通常新建一个ruoyi-asset模块,被 admin 依赖。包结构按若依惯例分:
com.xxx.asset ├── controller // 只做参数接收、权限注解、返回统一结果 ├── service // 业务规则、事务边界、状态流转、折旧计算 │ └── impl ├── mapper // 接口 + XML,只写 SQL ├── domain // 实体,继承 BaseEntity 才能拿到数据权限参数 └── constant // 状态、单据类型、盘点差异类型的常量domain层继承BaseEntity这件事千万别漏。BaseEntity里有params这个 Map,@DataScope切面拼好的数据权限 SQL 片段就是塞在params.dataScope里的。实体不继承,切面找不到落点,权限会静默失效——页面照常打开,数据全都能看到,而且不报错。
constant包单独拎出来,是为了避免常量散落在各个 Service 里。资产状态、单据类型、审核状态、盘点差异类型,四组常量放在四个类里,谁引用谁 import,比到处写魔法值强太多。
3.2 数据表设计:五张主表加三张流水表
主表:asset_info(台账)、asset_category(分类)、asset_location(地点)、asset_supplier(供应商)、asset_check(盘点任务)。
流水表:asset_flow(流转单据)、asset_check_detail(盘点明细)、asset_depreciation(折旧明细)。
台账主表的建表语句,重点看索引和注释:
CREATE TABLE asset_info ( asset_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '资产主键', asset_code VARCHAR(32) NOT NULL COMMENT '资产编号,全局唯一', asset_name VARCHAR(128) NOT NULL COMMENT '资产名称', category_id BIGINT NOT NULL COMMENT '分类ID', spec_model VARCHAR(128) DEFAULT NULL COMMENT '规格型号', price DECIMAL(12,2) DEFAULT 0.00 COMMENT '购置原值', salvage_rate DECIMAL(5,2) DEFAULT 5.00 COMMENT '残值率%', service_month INT DEFAULT NULL COMMENT '预计使用月数', purchase_date DATE DEFAULT NULL COMMENT '购置日期', dept_id BIGINT DEFAULT NULL COMMENT '使用部门', keeper_id BIGINT DEFAULT NULL COMMENT '使用人', location_id BIGINT DEFAULT NULL COMMENT '存放地点', asset_status CHAR(2) DEFAULT '0' COMMENT '0在库1在用2维修3借出4待报废5已报废', del_flag CHAR(1) DEFAULT '0' COMMENT '0存在2删除', create_by VARCHAR(64) DEFAULT NULL, create_time DATETIME DEFAULT NULL, update_time DATETIME DEFAULT NULL, PRIMARY KEY (asset_id), UNIQUE KEY uk_asset_code (asset_code), KEY idx_dept_status (dept_id, asset_status) ) ENGINE=InnoDB COMMENT='资产台账主表';uk_asset_code是唯一索引,不是普通索引。资产编号重复是这类项目最典型的翻车点,靠代码里“查一下有没有再插入”挡不住并发,只有数据库唯一约束是最后一道防线。
idx_dept_status是联合索引,顺序不能反。数据权限过滤条件永远是dept_id IN (...),列表页最常带的筛选是状态,组合起来刚好走这个索引。
del_flag用逻辑删除,因为资产报废之后历史单据还要能查到关联信息,物理删除会让流水表变成孤儿数据。
流转流水表的设计要点是“一张表管多种单据”:
CREATE TABLE asset_flow ( flow_id BIGINT NOT NULL AUTO_INCREMENT, flow_no VARCHAR(32) NOT NULL COMMENT '单据号', flow_type CHAR(2) NOT NULL COMMENT '1领用2调拨3借用4归还5送修6报废申请7报废审批', asset_id BIGINT NOT NULL COMMENT '关联资产', from_dept BIGINT COMMENT '原部门', to_dept BIGINT COMMENT '新部门', from_keeper BIGINT COMMENT '原使用人', to_keeper BIGINT COMMENT '新使用人', audit_status CHAR(1) DEFAULT '0' COMMENT '0待审1通过2驳回', audit_by BIGINT COMMENT '审核人', audit_time DATETIME COMMENT '审核时间', flow_remark VARCHAR(255) COMMENT '备注/报废原因', PRIMARY KEY (flow_id), UNIQUE KEY uk_flow_no (flow_no), KEY idx_asset_type (asset_id, flow_type) ) ENGINE=InnoDB COMMENT='资产流转单据表';from_*和to_*两组字段是必须的。调拨要记录从哪个部门到哪个部门,借用要记录从谁到谁,如果只存一个dept_id,历史轨迹就断了,出了问题回溯不了。
3.3 数据权限:把部门隔离压在 Service 层
数据权限的落点很明确:@DataScope注解加在 Service 实现类的查询方法上,Mapper XML 里留占位符。注解参数是表别名,XML 里的占位符必须用${}不能用#{}:
<select id="selectAssetList" parameterType="AssetInfo" resultMap="AssetInfoResult"> SELECT a.* FROM asset_info a WHERE a.del_flag = '0' <if test="assetCode != null and assetCode != ''"> AND a.asset_code LIKE CONCAT('%', #{assetCode}, '%') </if> <if test="assetStatus != null and assetStatus != ''"> AND a.asset_status = #{assetStatus} </if> <!-- 数据权限片段由 @DataScope 切面注入,必须用 $ 取值,用 # 会被当成普通字符串 --> ${params.dataScope} ORDER BY a.create_time DESC </select>#{}是预编译占位符,会把整段 SQL 当字符串值传进去;${}才是文本替换,切面拼好的AND (a.dept_id IN (1,2,3))才能生效。写成#{}的后果是 SQL 语法直接报错,这时反而容易发现;真正难查的是实体没继承BaseEntity——那样切面找不到params,什么都不注入,不报错,权限形同虚设。
提示:验收数据权限不要用管理员账号测,管理员默认跳过数据范围过滤。用一个只勾了“本部门”角色的普通账号登录,能看到别的部门资产就是没生效。
4. 核心模块落地:从入库到报废的状态流转怎么写成代码
4.1 资产编码生成与唯一性校验
资产编号建议规则是ZC + 分类码 + 年月 + 四位流水,比如ZC010120250001。好处是肉眼可读,贴在设备上一眼能看出分类和购置月份,扫码录入时也能做一次格式校验。
@Autowired private AssetCategoryMapper categoryMapper; /** * 生成资产编号:ZC + 分类码(4位) + 购置年月 + 4位流水 * 依赖数据库唯一索引 uk_asset_code 做最终兜底 */ public String generateAssetCode(Long categoryId, Date purchaseDate) { String catCode = categoryMapper.selectCodeById(categoryId); if (StringUtils.isEmpty(catCode)) { throw new ServiceException("资产分类未配置编码前缀"); } String prefix = "ZC" + catCode + DateUtils.parseDateToStr("yyyyMM", purchaseDate); // 取该前缀下最大编号,注意这条 SQL 要加 FOR UPDATE 或走 Redis 自增 String maxCode = assetInfoMapper.selectMaxCodeByPrefix(prefix); int seq = StringUtils.isEmpty(maxCode) ? 1 : Integer.parseInt(maxCode.substring(prefix.length())) + 1; return prefix + String.format("%04d", seq); }selectMaxCodeByPrefix这条查询在并发下是有问题的:两个请求同时读到同样的最大值,生成同样的编号。真正的解决办法有三层,从轻到重依次上:数据库唯一索引兜底(必须做)、SELECT ... FOR UPDATE加行锁(简单有效,量小够用)、Redis 自增(并发高时用)。只做第一层也不丢人,捕获DuplicateKeyException重试一次即可,比不加锁又不加唯一索引强一万倍。
4.2 状态机:把非法流转挡在 Service 层
资产流转的核心规则是:不是所有状态都能发起所有单据。在库的资产不能“归还”,已报废的资产不能“领用”,待报废的资产不能“调拨”。这些规则如果只在前端把按钮置灰,接口层面就是裸奔——随便一个测试工具都能把已报废资产领出去。
我的做法是用一张静态 Map 兜住全部合法流转,越简单越好维护:
@Service public class AssetFlowServiceImpl implements IAssetFlowService { /** key = 当前状态#单据类型,value = 流转后的状态 */ private static final Map<String, String> TRANSFER = new HashMap<>(); static { TRANSFER.put("0#1", "1"); // 在库 → 领用 → 在用 TRANSFER.put("0#3", "3"); // 在库 → 借出 → 借出 TRANSFER.put("1#2", "1"); // 在用 → 调拨 → 仍在用(换部门/换人) TRANSFER.put("1#3", "3"); // 在用 → 借出 → 借出 TRANSFER.put("1#5", "2"); // 在用 → 送修 → 维修中 TRANSFER.put("1#6", "4"); // 在用 → 报废申请 → 待报废 TRANSFER.put("2#5", "1"); // 维修中 → 维修完成 → 在用 TRANSFER.put("3#4", "1"); // 借出 → 归还 → 在用 TRANSFER.put("4#7", "5"); // 待报废 → 报废审批通过 → 已报废 } @Override @Transactional(rollbackFor = Exception.class) public int submitFlow(AssetFlow flow) { AssetInfo asset = assetInfoMapper.selectAssetById(flow.getAssetId()); if (asset == null) { throw new ServiceException("资产不存在"); } String nextStatus = TRANSFER.get(asset.getAssetStatus() + "#" + flow.getFlowType()); if (nextStatus == null) { throw new ServiceException("当前资产状态不允许发起该单据"); } flow.setAuditStatus("0"); flow.setFlowNo(generateFlowNo(flow.getFlowType())); return assetFlowMapper.insertFlow(flow); } }注意几个设计取舍。第一,状态流转的合法性和状态更新要分两步:提交单据时只写流水,审核通过时才回写asset_info.asset_status,这样驳回的单据不会污染台账。第二,TRANSFER里1#2的下一状态还是1,因为调拨只换部门和人不换状态,如果把调拨做成“在库→在用”会凭空多出一次虚假流转。第三,所有异常都抛ServiceException,不要返回AjaxResult.error之后继续往下走——事务不会回滚,后面会写出一堆半截数据。
4.3 盘点任务与盘盈盘亏的差异回写
盘点的业务模型是:先建任务,圈定范围(某部门或某地点),系统把范围内的资产全部快照进明细表,账面状态写进book_status;然后人去现场扫码或手工填check_status;最后系统比对差异。
差异查询用一条 SQL 就能覆盖三类情况:
-- 盘点差异:盘亏、状态不符;盘盈走单独的新增逻辑 SELECT d.detail_id, a.asset_code, a.asset_name, d.book_status AS bookStatus, d.check_status AS checkStatus, CASE WHEN d.check_status IS NULL THEN '盘亏' WHEN d.book_status <> d.check_status THEN '状态不符' END AS diffType FROM asset_check_detail d LEFT JOIN asset_info a ON a.asset_id = d.asset_id WHERE d.check_id = #{checkId} AND (d.check_status IS NULL OR d.book_status <> d.check_status)盘亏的资产不能直接逻辑删除,要走报废或“待核实”流程;状态不符的,要人工确认后手工修正台账。真正容易出问题的是盘盈——现场盘出来一台台账里没有的设备,正确的做法是生成一条新的asset_info,状态置为“在库”,来源标记为“盘盈”,而不是往流水表里塞一条假的领用记录。流水表一旦被伪造,后面所有报表统计都是错的。
4.4 折旧计提:平均年限法的定时任务实现
平均年限法的月折旧额计算很简单:(原值 - 原值 × 残值率) / 预计使用月数。真正需要设计的是幂等——“本月已经计提过”必须挡住,否则定时任务重跑一次,报表数字直接翻倍。
/** 月折旧额 = 原值 × (1 - 残值率/100) / 预计使用月数 */ BigDecimal residual = asset.getPrice() .multiply(asset.getSalvageRate().divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP)); BigDecimal monthDep = asset.getPrice().subtract(residual) .divide(new BigDecimal(asset.getServiceMonth()), 2, RoundingMode.HALF_UP); // 累计折旧不能超过应折旧总额,最后一期取差额 BigDecimal remain = asset.getPrice().subtract(residual).subtract(accumulated); if (monthDep.compareTo(remain) > 0) { monthDep = remain; }divide的第二个参数是小数位数,第三个是舍入模式,两个都必须显式写。默认的除法遇到除不尽会抛ArithmeticException,而service_month是 36、60 这种值,看似能整除,一旦残值率是 5% 就除不尽了。
幂等靠一张唯一索引兜底:UNIQUE KEY uk_asset_period (asset_id, period),其中period是202501这种年月格式。定时任务里先按period查一次,存在就跳过,不存在才插入。捕获唯一键冲突当“已计提”处理,是最省事的做法。
注意:报废当月的折旧要不要计提,取决于单位的财务口径。做毕设或演示系统,建议统一“报废当月不计提”,规则写进注释,答辩时被问到能说清楚就行。
5. 避坑与排查:五个让账实对不上的高频问题
5.1 三个数据层面的经典问题
资产编号偶发重复。现象是批量导入或者多人同时录入时,偶尔抛Duplicate entry for key 'uk_asset_code',重试一次又能成功。原因是selectMaxCode + 1这个模式在并发下没有互斥,两个请求读到同一个最大值。短期处理是捕获DuplicateKeyException后重试一次(流水号位数够,重试基本必成);中期处理是把取号方法加上FOR UPDATE,让同前缀的取号串行;长期处理是改成 Redis 的INCR,按“前缀 + 年月”做 key,天然原子。三层里唯一不能省的是数据库唯一索引,它是所有方案的最后一道保障。
单据审核通过了,台账状态没变。现象是领用单显示“已通过”,资产列表里那台设备还是“在库”。原因通常有两个:一是审核方法没有加@Transactional,流水表更新成功了,回写台账时抛异常,事务没覆盖到;二是回写逻辑写在了 Controller 里,前端某次没传assetId,跳过更新也不报错。解决办法是把“审核 + 回写状态”收进同一个 Service 方法的同一个事务里,并且状态值只能由后端根据flow_type推导,禁止前端传asset_status上来。
同一个月被计提了两次折旧。现象是折旧报表出现两倍金额,且两条记录时间戳只差几秒。原因是定时任务重跑、或者手动触发了补跑,而代码里只有“查询是否已计提”,没有数据库约束。查询挡不住并发,两个线程同时查到“没有”,然后都插进去了。解决方式是给(asset_id, period)加唯一索引,并且在插入时捕获冲突异常当作“已处理”。
5.2 两个权限与统计数据问题
普通账号能看到全公司资产。现象是部门管理员登录后,列表里出现了别的部门的设备,而且系统不报任何错。原因是台账实体没有继承BaseEntity,@DataScope切面拿不到params,直接跳过了注入;或者注解加在了 Controller 上(注解只对 Service 方法生效)。排查顺序是:先看实体继承,再看注解位置,最后看 XML 里是不是把${params.dataScope}写成了#{}(这个会直接报 SQL 语法错误,反而好发现)。
盘点做完,账面数据没变。现象是差异列表都出来了,但资产状态、部门、使用人还是盘点前的值,只有导出的差异表能看。原因是只做了“比对”没做“回写”。正确的收尾流程是:盘亏的资产转“待核实”或发起报废、状态不符的按实盘结果修正台账字段、盘盈的新建台账记录。三步都要有明确的按钮和二次确认,不能做成“点一下自动全改”——盘点差异必须留人工判断的口子。
5.3 上线前的一张自检清单
- 台账表有没有
uk_asset_code唯一索引,导入模板里有没有做编号重复校验 - 所有状态回写方法有没有
@Transactional(rollbackFor = Exception.class) - 折旧表有没有
(asset_id, period)唯一约束,定时任务能不能重跑 - 实体是否继承
BaseEntity,XML 里数据权限占位符是否用${} - 已报废、待报废资产是否在 Service 层被状态机拦住,而不是只靠前端置灰按钮
- 逻辑删除的资产是否在所有关联查询里都被
del_flag = '0'过滤掉
前五条是踩过的坑,第六条是踩完才发现的问题——报废资产在盘点明细里还能被扫出来,现场的人会以为系统坏了。
6. 验收与答辩:用一张流转表和一组合规数据证明系统跑通
功能做完不等于能交付。答辩或者验收现场,最容易被问倒的不是代码写得多漂亮,而是“你怎么证明这套账是对的”。我的做法是准备一组合规数据,把一条资产从采购到报废的完整轨迹跑一遍,然后把每一步的状态和单据列出来,现场演示比讲十页架构图管用。
验收表格可以照这个结构准备:
| 验收项 | 验证方法 | 通过标准 |
|---|---|---|
| 台账建档 | 新增一条资产,填写原值 12000、残值率 5%、使用 60 个月 | 编号按规则生成,列表可见,重复编号被拦 |
| 状态流转 | 依次发起领用、调拨、送修、维修完成、报废申请、报废审批 | 每次流转后台账状态与单据一致,非法流转报错 |
| 数据权限 | 用只勾“本部门”的普通账号登录 | 只能看到本部门资产,管理员不受限 |
| 盘点差异 | 建任务后手工把一条明细的实盘状态改成与账面不同 | 差异列表出现该条,回写后台账同步更新 |
| 折旧计提 | 手动触发两次月度计提 | 第二次被跳过,累计折旧不超过应折旧总额 |
| 逻辑删除 | 删除一条资产后查流转记录和盘点明细 | 关联数据仍在,列表不再展示 |
表格里每一行都要准备好能现场点出来的数据,尤其是“非法流转报错”和“折旧重复计提被跳过”这两条。这两项是最能体现设计思考的地方,也是导师和评审最爱问的地方——为什么这么设计、不这么设计会怎样,答得出来就说明真跑过。
答辩时如果被问到技术选型,别只答“因为若依方便”。更好的答法是:权限、字典、日志这些横切关注点占了开发量的很大一块,用成熟脚手架把这部分固化下来,把时间投在资产状态机和折旧规则上,而这两块恰好是这类系统真正的业务价值所在。这个回答体现的是取舍能力,不是偷懒。
我从第一个类似项目开始就养成了一个习惯:每加一个状态流转,先在纸上把状态图画出来,对着图把TRANSFER那张表填满,再写代码。听起来多此一举,但少了这一步,后面一定会有人绕过前端直接调接口,把一台已报废的服务器“借”出去,然后你要花半天从日志里翻出到底是谁在什么状态下干了什么。希望帮到你。
本文还有配套的精品资源,点击获取