技术文章
三十而“码”:一个转行者的折腾日常
去年冬天,我还在跑建材市场的档口,每天和水泥标号、瓷砖吸水率打交道。如今,却窝在自家书房里,一边远程接入公司 GitLab,一边给 Spring Boot 项目写动画过渡效果——是的,我就是那个传说中“三十岁裸辞转码农”的人。
说不焦虑是假的。求职那会儿,简历投出去石沉大海,HR 回一句“经验不符”就把我打回原形。但好在开源社区没嫌弃我这个半路出家的老菜鸟。靠着啃 Vue3 源码、复刻 Ant Design 动画交互、还硬着头皮给几个 GitHub 小项目提 PR,总算混进了一家做 SaaS 工具的远程团队。
今天想聊的,不是“如何三个月速成大神”,而是技术探索这事儿,到底该怎么“实践”才不白费力气。
被逼出来的 Spring Boot 项目改造
上个月,产品经理突然甩来一个需求:“后台管理页的加载动画太卡了,用户反馈像在等泡面煮开。”
我当时心里一万个问号:我们这明明是个纯 CRUD 的 Spring Boot 后台,前端用的是老旧的 jQuery + Bootstrap 3,哪来的“动画”?
结果一看代码,好家伙——前端为了“炫技”,在列表加载时加了个 CSS 动画,但数据接口一慢(平均 1.2s),整个页面就卡成 PPT。更离谱的是,后端接口居然每次查全表不分页!
运维同事在群里幽幽补刀:“上次压测,这接口直接干崩了 Redis。”
行吧,这锅我背了(虽然入职才两周)。领导拍板:“你不是对交互感兴趣吗?正好重构一下,前后端一起优化。”
于是,一场“小项目大折腾”的实战开始了。
技术选型:别为了新而新
很多人一听说“优化”,立马想到上微服务、换 React、搞 Server-Send Event。但咱得现实点——这是个只有三个开发的小团队,Deadline 是两周后,而且不能影响线上稳定。
我的思路很朴素:
- 后端不动大架构:Spring Boot 2.7 还挺稳,没必要升 3.x 冒兼容风险
- 前端渐进式替换:保留老页面,新功能用 Vue3 + Vite 单独挂载
- 动画必须可控:不能让 UI 阻塞主线程,优先考虑 CSS 硬件加速
最关键的一点:所有改动必须能回滚。毕竟我可不想因为一个 loading 动画,半夜被 PagerDuty 叫醒。
实战:从“卡成狗”到丝滑如德芙
第一步:接口瘦身
原接口返回字段巨多,连“创建人头像 base64”都一股脑塞进来。我做了三件事:
- 加分页(Pageable + JPA)
- 用
@JsonView控制返回字段 - 关键查询走 Redis 缓存(TTL 5 分钟)
// 精简后的 Controller
@GetMapping("/items")
@JsonView(ItemViews.Public.class) // 只暴露必要字段
public Page<Item> listItems(Pageable pageable) {
return itemService.findPublicItems(pageable);
}
缓存策略也很保守:只缓存 ID 列表,详情按需查 DB。避免缓存雪崩。
第二步:前端懒加载 + 动画解耦
我把新列表页拆成独立 Vue 组件,通过 Webpack Module Federation 动态挂载到老页面上(感谢公司允许我试新技术)。
重点来了:动画不能依赖数据加载完成才开始。
我用了“骨架屏 + 渐进渲染”方案:
- 数据未到时,显示带 shimmer 动画的骨架块
- 数据到达后,用
requestAnimationFrame分批插入 DOM - 每次插入不超过 20 条,避免长任务阻塞
<template>
<div v-if="loading" class="skeleton-list">
<div v-for="i in 10" :key="i" class="skeleton-item animate-pulse"></div>
</div>
<div v-else>
<ItemRow
v-for="item in visibleItems"
:key="item.id"
:item="item"
@appear="onItemAppear" />
</div>
</template>
<script setup>
// 使用 IntersectionObserver 触发动画
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('fade-in');
}
});
});
</script>
这套方案上线后,Lighthouse 性能分从 42 直接飙到 89。产品经理终于闭嘴了。
安全?别以为动画就不用管!
说到安全意识,很多人觉得“我又不做金融系统,怕啥”。但上周我就踩了个坑:
前端为了“个性化”,允许用户上传头像并生成 CSS 背景 URL。结果有同事测试时输入了 background: url(javascript:alert(1)) —— 虽然现代浏览器基本防住了,但万一遇到老 IE 呢?
立刻加了两道防线:
- 后端校验图片 MIME 类型(拒绝非 image/*)
- 前端用
DOMPurify.sanitize()处理所有用户输入
// 安全地设置背景图
const cleanUrl = DOMPurify.sanitize(userAvatarUrl, {
ALLOWED_TAGS: [],
ALLOWED_ATTR: []
});
if (isImageUrl(cleanUrl)) {
element.style.backgroundImage = `url(${cleanUrl})`;
}
记住:任何用户输入,都是潜在的 XSS 入口,哪怕只是个头像链接。
转行人的“笨办法”:读源码、造轮子、交朋友
作为新人,我没资格挑活儿干。所以但凡有点空闲,我就去翻公司项目的 Git 历史,看前辈们怎么处理异常、怎么写单元测试、怎么设计 API 响应结构。
最近迷上了研究开源项目的错误处理机制。比如 Spring Boot 的 @ControllerAdvice,我对比了三种写法:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 全局统一异常 | 代码集中,维护方便 | 灵活性差 | 内部系统 |
| 按模块自定义 | 可定制错误码 | 重复代码多 | 多租户 SaaS |
| 结合 AOP 切面 | 无侵入 | 调试困难 | 高安全要求 |
最后我们选了第二种——毕竟客户要的错误码五花八门,财务模块和日志模块根本不是一个逻辑。
我也开始尝试贡献小工具。比如写了个 spring-boot-starter-animate(名字随便起的),自动把后端耗时注入到响应头,前端据此决定是否显示 loading 动画。虽然没人用,但至少让我理解了 Spring Boot 自动配置的原理。
写在最后:技术不是秀肌肉,是解决问题
有时候看着 22 岁的同事手撕红黑树,我会怀疑自己是不是来得太晚。但慢慢发现,经验这东西,换个赛道也能迁移。
以前卖建材,我知道客户要的不是“304不锈钢”,而是“十年不锈的护栏”;现在写代码,我也明白产品经理要的不是“酷炫动画”,而是“用户不觉得卡”。
技术探索的意义,从来不在“用了多新的框架”,而在“是否真的让事情变得更好”。
下个月又要面试新人了。如果看到简历上写着“喜欢研究源码”、“做过个人项目”,我会多问一句:“你最近一次为了解决一个问题,主动去学了什么?”
因为我知道,真正的成长,永远发生在实践的泥泞里——而不是教程的幻灯片上。
(完)
作者:一个在家撸代码的前建材销售,现 Spring Boot 新手。GitHub 主页全是 fork,但 commits 记录真实。欢迎交流,但别问我 Vue 和 React 哪个好,我两个都爱(也都不熟)。

评论 0