请写一篇关于【技术探索与实践实践总结】的技术文章
去年十月,成都的秋天湿漉漉的。我坐在家里那张吱呀作响的旧电脑椅上,盯着屏幕右下角的时间——18:47。窗外是玉林路熟悉的烟火气,楼下烧烤摊已经开始支摊了,而我刚刚收到了一封邮件:「感谢您过去三年的付出……」。
是的,我被裁了。
当时月薪15k,在成都这个生活成本不高但工资也“不高”的城市里,勉强能覆盖我和老婆每月3500的房租、2000的日常开销,再加上她刚怀孕三个月的产检费用。裁员补偿N+1,一共拿了不到5万块。说实话,那天晚上我翻来覆去睡不着,脑子里全是房贷、奶粉钱、医保断缴这些词。老婆看我脸色不好,轻声说:“要不……先接点外包试试?”
我苦笑:“我都三年没碰新项目了,Springboot 还停留在 2.3.x,现在都 3.x 了,连 GraalVM 原生镜像都上生产了,我能行吗?”
但现实没有给我犹豫的余地。第二天一早,我就在电报群、V2EX、甚至老家亲戚的朋友圈里发消息:“全栈开发接单,Java/Springboot/Vue,价格好谈。”
第一个外包项目:从“能跑就行”到“必须稳如老狗”
第一个活儿是上周五晚上接到的——一个本地做社区团购的小老板,微信上加我:“兄弟,听说你搞 Java?有个后台系统,急用,两周上线,预算 8000。”
我差点脱口而出“太少了”,但想到银行卡余额,硬生生咽了回去:“行,我看看需求。”
需求很简单:用户下单 → 商家接单 → 配送员派单 → 财务对账。典型的 CRUD + 流程控制。按我以前在大厂的做法,可能直接套个若依(RuoYi)框架改改就交差了。但这次不行——这是我的“招牌工程”,万一崩了,以后谁还敢找我?
于是,我决定:哪怕只赚 8000,也要按生产级标准来干。
教程式复盘:我是怎么把 Springboot 用“活”的
1. 别再用 application.properties 了,请拥抱 profile + 配置中心思维
以前在公司,配置都是运维管的。自己搭环境才发现,本地、测试、生产三套配置混在一起有多痛苦。这次我直接用 application-dev.yml、application-prod.yml 拆开,敏感信息(比如数据库密码、短信 API key)全部用环境变量注入。部署时一行命令搞定:
java -jar -Dspring.profiles.active=prod my-app.jar
虽然小项目用不到 Nacos 或 Apollo,但提前养成配置隔离的习惯,以后接大单才不会手忙脚乱。
2. 统一响应格式 + 全局异常处理,省下 50% 的沟通成本
客户最烦什么?接口返回五花八门,一会儿 {code:200},一会儿直接抛 JSON 字符串。我直接封装了一个 Result<T> 工具类:
public class Result<T> {
private int code;
private String msg;
private T data;
// 省略 getter/setter
}
配合 @ControllerAdvice 做全局异常捕获:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result<?> handleBusiness(BusinessException e) {
return Result.fail(e.getCode(), e.getMessage());
}
}
前端同事(其实是客户自己找了个兼职学生)第一次联调就说:“你这接口真清爽!”——那一刻,我觉得值了。
3. 事务别乱加,但该加的地方一个都不能少
社区团购有个关键场景:用户支付成功后,要扣库存、生成订单、发通知。这三个操作必须原子性。我一开始图快,只在 Service 层方法上加了 @Transactional,结果测试时发现:如果发短信超时,整个事务回滚,用户钱退了但页面显示“支付失败”,体验极差。
后来改成:核心数据操作(扣库存、建订单)在一个事务里,非核心(发通知、日志)用异步解耦。用 Spring 的 @Async + 自定义线程池搞定:
@Service
public class OrderService {
@Transactional
public void createOrder(OrderDTO dto) {
// 扣库存
// 保存订单
notificationService.sendAsync(dto.getUserId(), "订单创建成功");
}
}
@Service
public class NotificationService {
@Async("taskExecutor")
public void sendAsync(Long userId, String msg) {
// 调短信/微信模板
}
}
教训:事务不是银弹,过度使用反而拖垮性能。分清“必须一致”和“最终一致”很重要。
4. 日志别只打 info,关键路径必须 traceId
客户半夜打电话:“怎么用户付了钱没生成订单?” 我一脸懵,只能翻日志。但日志全是“order created”、“stock deducted”,根本没法串联。
痛定思痛,我引入了 MDC(Mapped Diagnostic Context) + 自定义拦截器,为每个请求生成唯一 traceId:
@Component
public class TraceIdInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String traceId = UUID.randomUUID().toString().replace("-", "");
MDC.put("traceId", traceId);
return true;
}
}
然后在 logback-spring.xml 里配置:
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level [%X{traceId}] %logger{36} - %msg%n</pattern>
从此,查问题只需搜一个 traceId,整条链路清晰可见。客户再也没半夜 call 我。
从“外包仔”到“技术顾问”:心态的转变
做完第一个项目,客户很满意,又介绍了两个朋友。其中一个是个做宠物殡葬的创业者,需求更复杂:预约管理、纪念册生成、线上追思会直播集成。预算 2.5w,周期一个月。
这次我没急着写代码,而是约他在镋钯街的咖啡馆聊了整整一下午。我问他:“你最怕系统出什么问题?” 他说:“怕家属看到一半网页崩了,那是对逝者的不尊重。”
这句话戳中了我。技术不是冷冰冰的 CRUD,它承载的是人的情感和信任。
于是我在架构上做了几件事:
- 前端用 Vue + Nuxt 做 SSR,确保首屏加载快、SEO 友好
- 后端用 Redis 缓存纪念册数据,避免频繁查库
- 关键页面(如追思会入口)加上熔断降级逻辑,即使服务挂了也展示静态悼念页
交付那天,他眼眶有点红:“谢谢你把它当回事。”
那一刻,我突然意识到:我不是在“接外包”,我是在用技术帮人解决问题、传递价值。工资低不可怕,可怕的是把自己当成“码农工具人”。
真实数据:自由职业后的收入变化
被裁前:月薪 15k,税后约 12k
自由职业 6 个月后:
- 外包收入:平均每月 22k(最高单月 35k)
- 时间自由:每天工作 6-7 小时,陪产检、接送老婆下班
- 技术成长:逼自己学了 Docker、Redis 高级用法、甚至摸了摸 Rust 写 CLI 工具
当然也有焦虑的时候。比如上个月连续两周没单子,银行卡只剩 1.2w,吓得我连夜更新了 GitHub 和掘金主页。但很快就有老客户介绍新活——口碑,才是自由开发者最硬的通货。
给同样在路上的朋友几点建议
- 别怕从小单做起。8000 块的项目也能打磨出生产级代码,关键是态度。
- Springboot 是你的瑞士军刀,但别只会开罐头。深入理解自动配置、Starter 原理、Actuator 监控,才能应对各种“奇葩”需求。
- 建立自己的“最小可行产品库”。我整理了一套 Springboot 脚手架,包含统一响应、JWT 认证、多数据源等模块,新项目直接 clone,省下 3 天时间。
- 技术之外,学会沟通。客户不懂“幂等性”,但懂“别让我重复扣钱”。用他听得懂的话解释技术方案,信任感就来了。
- 保持学习,但别盲目追新。GraalVM 很酷,但小项目用原生镜像可能得不偿失。合适,比先进更重要。
最后一点思考
很多人说成都“卷不动”,工资低、机会少。但换个角度看:生活成本低,意味着试错成本也低。我可以在玉林小酒馆喝 20 块的精酿,也可以在家写代码到凌晨三点。自由职业不是逃避,而是一种主动选择的生活方式。
技术探索的本质,不是为了炫技,而是为了更高效、更可靠、更有温度地解决问题。每一次深夜调试,每一次客户说“谢谢”,都在告诉我:这条路,走对了。
如果你也在迷茫,不妨从小项目开始,写一篇属于自己的“最佳实践”。不用完美,但求真实。因为真实,才有力量。
共勉。
—— 一个在成都靠 Springboot 养家糊口的前大厂程序员
2024 年 6 月于家中书桌

评论 0