准备把 AI 辅助完成的项目写进 Java 简历时,我最担心的,是面试官顺着一个接口继续追问:设备为什么能借出去?经办人是谁决定的?归还和逾期巡检撞在一起,会不会把状态改乱?

页面能打开,还回答不了这些问题。所以这次,我把「有借」资产借还项目重新检查了一遍,补了身份认证、操作留痕,也修掉了几个正常演示时不容易碰到的问题。下面就按我实际检查和修改的顺序,聊聊这套项目现在能写什么,还有什么不能写。

一、先说清楚,我做了什么项目

「有借」是一个本地交付练习,用来管理设备台账、借出归还和维保工单。我沿用了已有的设备与借还基础,没有从零重做整个系统。后端采用 Java 17、Spring Boot 3.4、Spring Data JPA 和 Flyway,前端是 Vue 3、TypeScript、Pinia 与 Element Plus。

我把后续工作分成了三组:维保与续借、账号身份认证、操作审计。它们分别对应数据库的 V2、V3、V4 迁移。这里的版本号用来说明功能如何逐步补齐,不代表项目已经在线上发布过四个版本。

飞算 JavaAI 用在 IDEA 里的需求梳理与开发辅助。为了说明这次使用的环境,我保留了插件版本页和开发工具版本信息。IDEA 自带的运行时与项目使用的 JDK 不是一回事,后端的这轮验证用的是 JDK 17。

飞算 JavaAI 插件的本机安装版本与启用状态

图 1:飞算 JavaAI 插件的本机安装版本与启用状态。

本次开发环境信息,区分 IDE 运行时与项目使用的 JDK

图 2:本次开发环境信息,区分 IDE 运行时与项目使用的 JDK。

接下来,我先把需求拆开。例如「增加维保功能」,至少要说明谁报修、记录哪些费用、什么情况下恢复可用,以及维修失败后怎么处置设备。续借也是一样,不能只有一个「延期」按钮,还得说明次数上限、时间约束和经办人怎么留痕。

因为原始插件对话没能完整找回,所以我重新演示了一次需求拆解。图 3 展示的是这次复演,不是原始生成记录,也不能据此断言后面的代码都由插件一次生成。

维保、续借与归还健康度需求的拆解复演,非原始开发记录

图 3:维保、续借与归还健康度需求的拆解复演,非原始开发记录。

二、把借还、续借和维保接成一个流程

我先检查业务规则,因为后面的认证、审计和页面操作都要落在这些规则上。

设备处于「空闲待借」时才能借出。正常归还后恢复可用;如果验收发现受损,就进入维保;如果确认遗失,就转为报废。维保工单记录故障、厂商和费用,完工验收再决定恢复可用,还是确认报废。

续借沿用原来的借还单,更新本次原因和预计归还时间,并累计次数,最多两次。目前原因字段保存的是最近一次续借的内容,还没有单独的续借明细表;次数和期限的变更则写入审计。

资产看板中的数量统计、分类分布与借还趋势

图 4:资产看板中的数量统计、分类分布与借还趋势。

设备台账中的资产信息、当前状态与操作入口

图 5:设备台账中的资产信息、当前状态与操作入口。

维保工单及处理状态,图中记录为演示数据

图 6:维保工单及处理状态,图中记录为演示数据。

初始数据包含 14 台设备、7 笔借还流水和 3 笔维保工单,人员、电话和厂商信息都是虚构的。因为巡检和借还操作会改变状态,所以页面上的逾期数量不能固定要求为一条。

前端我保留了浅色、深色两套主题。看板的分类进度条和趋势柱形图用 HTML/CSS 实现,并没有实际接入 ECharts。依赖里出现了某个库,不等于我就完成了对应功能,这一点在项目介绍里也得写清楚。

三、经办人不能由表单自己说了算

最初检查经办人字段时,我发现一个容易被忽略的问题:前端可以直接提交借出经办人、归还验收人和报修人。后来虽然加了登录,但服务端只在这些字段留空时回填登录姓名。只要客户端显式传值,仍然能把业务经办人写成别人。

那么,既然服务端已经知道当前是谁登录,为什么还要让表单决定经办身份?

我把这里改成了强制读取登录上下文。认证拦截器先校验 Bearer Token,再把当前用户放进请求线程的上下文;业务方法需要经办人时,直接调用下面这个方法:

public static String requireRealName() {
    User user = CURRENT_USER.get();
    if (user == null) {
        throw new BusinessException(ResultCode.UNAUTHORIZED);
    }
    return user.getRealName();
}

这里的 CURRENT_USER 是保存当前请求用户的 ThreadLocal<User>,请求结束后会清理。缺少身份时直接拒绝,不再用「系统经办人」替用户补一个名字。

借出、归还和报修都按这个规则处理,旧请求里的身份字段保留兼容,但不参与赋值。借用人则仍是业务输入:我可以替别人登记借用,不能把「借用人」和「办理这笔业务的人」混为一谈。 借出接口返回的单据与经办人信息,经办身份由服务端确定

图 7:借出接口返回的单据与经办人信息,经办身份由服务端确定。

续借还有一个细节。我之前让续借操作覆盖了原来的借出经办人,结果同一个字段前后代表了不同的人。现在原 handler 保留首次借出的经办人,当前续借账号写入审计记录。

审计与业务更新放在同一个事务里,业务失败时一并回滚;登录失败时还没有用户上下文,所以按尝试登录的账号另行记录。这能帮助我追查已记录的操作,但目前只是应用层追加日志,还谈不上防篡改存证。

四、真正返工的地方,在正常流程之外

编辑资料,也可能绕过状态规则

我最先给手动状态接口加了限制:只有空闲设备能手动转维保或报废。接着检查普通编辑接口,才发现它仍然可以接收一个新的状态,把维保甚至报废设备改回可用。

因为两个入口都能改同一个字段,所以只修其中一个入口不够。我让 PUT 编辑与 PATCH 状态处置共用校验方法,并在设备行锁内检查。PUT 如果只是改名称、位置、备注,就保留原状态;确实要改状态时,再走白名单。

下面是白名单判断的核心条件,异常提示和审计代码省略:java DeviceStatus currentStatus = device.getStatus(); boolean manualAllowed = currentStatus == DeviceStatus.AVAILABLE && (targetStatus == DeviceStatus.MAINTENANCE || targetStatus == DeviceStatus.SCRAPPED);

if (!manualAllowed) { throw new BusinessException(ResultCode.CONFLICT); }

device.setStatus(targetStatus); 我也补了维保入口:报废设备不能新建送修单,否则还可能借着「送修—验收」这条路恢复可用。前端编辑表单则改为保留设备原状态,不再固定提交 AVAILABLE。

有行锁,还得检查锁住后读到了什么

另一个问题出在逾期巡检。原来先查出一批借还实体,随后再更新;如果中间有人完成了归还或续借,巡检就可能拿着旧实体覆盖新状态。

我把它改成先查候选 ID,再逐笔加锁读取。只有拿到锁后,单据仍在借用中、预计归还时间也确实已过,才标为逾期。核心步骤如下,整段逻辑在同一事务中执行:

LocalDateTime cutoff = LocalDateTime.now();
List<Long> candidates =
        borrowRecordRepository.findExpiredRecordIds(
                BorrowStatus.BORROWING, cutoff);

for (Long recordId : candidates) {
    BorrowRecord record =
            borrowRecordRepository.findByIdWithLock(recordId).orElse(null);

    if (record == null || record.getStatus() != BorrowStatus.BORROWING
            || !record.getExpectedReturnTime().isBefore(cutoff)) {
        continue;
    }

    record.setStatus(BorrowStatus.OVERDUE);
    borrowRecordRepository.save(record);
    auditService.record("BORROW_OVERDUE", "BORROW_RECORD",
            record.getRecordNo(), "预计归还时间已过,系统自动将借出单流转为逾期");
}

这里先取 ID,是为了避免候选实体提前进入持久化上下文,后面加锁时仍然拿到旧对象。我们要保护的,是「读取、判断、更新」这整个过程,不能只看到一个锁注解就认为问题解决了。

### 列表能打开,导出却不一定能用

加上认证以后,我还发现两处 CSV 导出仍然用 `window.open()` 打开地址。这个请求不会经过 Axios 的认证拦截器,所以列表查询正常,导出却拿不到同样的 Bearer Token。

现在两处导出都复用带认证头的请求客户端,以 Blob 接收文件,再创建临时下载地址。触发下载后移除临时链接,延迟释放地址;代码没有监听浏览器最终是否保存成功。

这里还有一个容易漏掉的失败分支:服务端返回的可能是一段错误 JSON,而不是 CSV。因为请求指定了 Blob,所以错误响应也要解析,不能直接把它存成一个后缀为 `.csv` 的文件。当前实现会校验响应类型,401 时清理登录态,其他错误保留服务端提示。

![CSV 导出相关文件与内容;下载结果需要同时核对文件和数据](https://iili.io/nAB8aKG.jpg)

> 图 8:CSV 导出相关文件与内容;下载结果需要同时核对文件和数据。

这几个问题让我更愿意沿着一次实际操作检查代码:页面发了什么,服务端信了什么,数据库最后改了什么。只看接口有没有返回 200,很容易漏掉前后不一致的地方。

## 五、我验证了哪些,还有哪些没做好

2026 年 9 月 19 日,我在 JDK 17 下完成了一轮回归:后端 26 项测试通过,失败、错误和跳过均为零;前端 5 项下载逻辑测试通过,TypeScript 检查与 Vite 构建也通过。

![后端完整回归测试结果](https://iili.io/nABS11a.jpg)

> 图 9:后端完整回归测试结果。

![前端下载逻辑测试与构建结果](https://iili.io/nABU1Z7.jpg)

> 图 10:前端下载逻辑测试与构建结果。

后端新增用例覆盖了编辑状态旁路、报废送修拒绝、伪造经办人、续借保留原经办人、缺少身份时回滚,以及导出认证。逾期问题则做了两种指定交错:先选出候选单据,让归还或续借提交,再让巡检加锁检查,确认它不会覆盖已提交结果。

这些测试使用 H2,候选 ID 查询处由测试桩控制时序。它们没有验证所有并发情况,也不是 MySQL 压力测试。前端下载测试中的请求适配器、DOM 和本地存储同样用了测试替身,不能据此认定浏览器文件保存已经完整验收。页面冒烟只覆盖了登录、列表、编辑后状态保留和部分表单检查,完整端到端流程还没跑完。

我觉得 AI 辅助开发的价值,是让我能围绕一套具体需求继续讨论表结构、接口和页面,而不是每一步都从空白开始。不过,这次没有做同条件的耗时对照,我不会据此写「效率提升几倍」,也不会把修复后的测试通过说成「生成时一次成功」。

用起来不够省心的部分也很具体:状态限制要检查所有写入口,认证加上以后还要回头检查导出,生成的角色字段也不代表权限已经实现。我希望这类工具在需求确认时,就把「谁能操作」「失败怎么处理」「时间到了由谁改变状态」列出来。这样我们接手检查时,至少知道哪些地方还空着。这些是本项目暴露的问题,不能直接推断所有生成结果都一样。

目前我还留着几项没有完成的工作:

- **权限和会话。** 管理员与操作员仍可访问相同的受保护接口,没有真正的 RBAC。Token 会话保存在单机内存,重启会失效,也不能供多个实例共享;浏览器端使用 localStorage,仍需考虑 XSS 风险。

- **账号和部署配置。** 缺少账号管理与登录限流。当前跨域配置仍放行任意来源,本地开发还启用了 API 文档和数据库控制台,上线前需要单独收紧和验收。

- **导出和审计。** CSV 还缺统一的字段转义及公式注入防护。审计没有防篡改保证,登录用户名也缺少长度限制,超长输入可能让失败审计落库异常;这个风险还需要补边界测试。

- **验证范围。** MySQL 容器、数据库升级回滚、并发压测、备份恢复都没完成;前端构建仍有包体积警告,也还没有做完整的浏览器端到端验证。

所以我会把它定位为一个有回归记录的本地交付练习,不写成已经在企业生产环境稳定运行的系统。

## 六、回到简历,我会怎么写

那么,这个项目到底能不能写进 Java 简历?我的答案是可以,但我会把 AI 辅助的范围、自己接手的工作和验证边界放在一起说明。

项目描述可以收成这样:

> 基于已有设备借还功能,使用飞算 JavaAI 辅助梳理增量需求,补充维保、续借、身份认证和操作审计。修复设备状态编辑旁路、经办人伪造、CSV 导出认证及逾期巡检覆盖问题。完成 26 项后端回归、5 项前端下载测试与构建检查;验证环境为 JDK 17 和 H2,尚未完成 MySQL 验收与并发压测。

这段话没有写「从零独立开发」,也没有给项目加上未经验证的生产指标。如果面试官继续追问,我就可以从一次 PUT 编辑、一次续借或一次导出讲起,说明原来的问题、改动的理由,以及测试覆盖到哪里。

回到开头那份担心,我现在更清楚该准备什么了。用了 AI 这件事可以直接讲;哪些判断是我做的,哪些地方仍然没有把握,也应该讲清楚。对我来说,这才是把项目写进简历之前,值得花时间完成的复盘。

<div class="hexo-wechat-follow-card" style="margin:28px 0 0;padding:16px 18px;border:1px solid #dbe7f3;border-radius:14px;background:#f8fbff;"><a href="weixin://profile/gh_1ab72c968bef" style="font-weight:700;color:#0f5b9f;text-decoration:none;">点这里一键关注『计算机魔术师』</a><p style="margin:8px 0 0;font-size:13px;color:#6f8299;line-height:1.7;">如果浏览器无法直接唤起微信,可在微信内打开公众号主页:<a href="https://mp.weixin.qq.com/mp/profile_ext?action=home&amp;__biz=MzkwNjQyOTUwOA==#wechat_redirect" style="color:#0f5b9f;text-decoration:none;">计算机魔术师</a></p></div>
上一篇:
Claude Opus 5.5 登顶 Artificial Analysis 智能指数,得分 58
下一篇:
五角大楼调查:过度依赖 Maven AI 是美军误击伊朗米纳布学校的原因之一

分享到这些地方