Java后端开发者:从需求到上线 — 工作思路全纪实

2026-04-15 (Tuesday) · 老王 · 5年经验 Java 后端 · 用户中心组

这次我们聚焦"工作思路和习惯"——一个后端拿到需求后,脑子里在想什么、手在做什么、先做什么后做什么

Java后端 Spring Boot 工作思路 需求拆解 全流程纪实 暗规则

本篇记录一位5年经验Java后端开发者(老王)完整的一天。需求是:「用户中心增加手机号绑定功能,需要短信验证码验证,且一个手机号只能绑定一个账号」。你会看到他每一步的思考——不是"怎么做",而是"为什么这么做"。

需求到手 拆解分析 风险识别 DB设计
API设计 排期估时 编码实现 自测验证
代码评审 部署上线 复盘总结

09:00需求到手:先不写代码,先读三遍

需求文档来了。第一反应不是"用什么技术实现",而是"这个需求到底要解决什么业务问题"。如果业务问题没搞清楚就开始写代码,后面一定会返工。

老王打开JIRA,看到了PM提的需求单:

REQ-2026-0488:用户中心增加手机号绑定功能 优先级:P1

需求描述

  • 用户在个人中心可以绑定手机号
  • 绑定时需要发送短信验证码,用户输入验证码验证通过后完成绑定
  • 一个手机号只能绑定一个账号(全局唯一)
  • 用户可以解绑手机号,解绑后可以重新绑定新的手机号
  • 已有手机号的用户可以更换手机号(先验证旧手机号,再绑定新手机号)

验收标准

  • 验证码60秒内只能发送一次
  • 验证码5分钟内有效
  • 同一天同一手机号最多发5次
  • 绑定成功后发送站内通知
负责人:老王 期望上线:2026-04-18 涉及方:前端、后端、短信网关
第一遍粗读——了解大概是什么功能。
第二遍细读——找出边界条件和限制规则。
第三遍带着问题读——"什么场景下会出问题?"
⚡ 后端习惯 #1:读需求时不只看"要做什么",更要找"限制条件"和"异常场景"。验证码发送频率限制、手机号唯一性、更换手机号的双向验证——这些才是后端要处理的核心难点。如果只看到"发送验证码+验证"就开写,上线后一定会被刷短信接口、重复绑定等问题打脸。

09:15需求拆解:把一句话拆成8个接口

老王在笔记本上画出了功能拆解:

1. 发送验证码→ 调短信网关 + Redis存储验证码
2. 验证验证码→ 从Redis取验证码 + 比对
3. 绑定手机号→ 检查唯一性 + 写DB
4. 解绑手机号→ 清除绑定关系
5. 更换手机号→ 先验旧号 + 再绑新号(=2+3的组合)
6. 查询绑定状态→ 前端展示用
7. 站内通知→ 绑定成功后异步发送
8. 操作日志→ 记录谁什么时候绑定了什么手机号
PM写的是"绑定手机号",看起来是一个功能。但拆开来,至少8个子功能/接口。如果开始写代码前不做这一步拆解,写的时候会一会儿想到这个没做,一会儿想到那个漏了,代码结构会很混乱。
类比——装修房子:业主说"我要装个厨房",装修师傅不会直接买砖头开始砌。他要先拆成:水电改造→防水→贴砖→橱柜安装→台面→电器接线→验收。每一项都有依赖顺序。后端拆需求就像装修拆工序。
⚡ 后端习惯 #2:拆需求时画出"功能依赖关系图"。"更换手机号"依赖"验证旧号码"和"绑定新号码"两个功能,这决定了代码层面的复用关系。先想清楚依赖关系,再写代码,Service层会很干净。

09:40识别风险:那些上线后会爆炸的炸弹

老王在纸上列出了这个需求可能的风险点:

风险类型具体场景影响程度解决方案
短信轰炸恶意用户循环调用发验证码接口,消耗短信费用🔴 高(花钱)Redis限流:同IP/同手机号发送频率限制
并发绑定两个用户同时绑定同一个手机号🔴 高(数据不一致)数据库唯一索引 + 分布式锁
验证码暴力破解攻击者枚举4位验证码(10000种组合)🟡 中验证码6位 + 最多尝试5次 + 超限锁定
短信网关超时短信服务商接口响应慢🟡 中调用超时控制 + 降级(切备用通道)
解绑后数据残留解绑后旧绑定记录丢失,无法审计🟢 低绑定记录软删除 + 独立日志表
中间人攻击验证码传输过程中被截获🟡 中全链路HTTPS + 验证码一次性使用
这些风险点90%都不是PM写在需求里的。后端开发的一个重要职责就是"帮产品把异常场景都想全"。这个习惯来自经验——被生产事故打过的开发者,看到"发验证码"四个字就会条件反射地想到短信轰炸。
⚡ 后端习惯 #3:拿到需求后,花至少30分钟专门做"风险识别"。用红黄绿三色标注影响程度。这些风险点会直接变成代码里的防御逻辑——限流、锁、重试、降级。如果跳过这一步直接写码,上线后等着被安全团队和运维找上门。

🔴 真实事故案例(老王亲历)

曾经有个项目,手机号绑定功能没有做"验证码尝试次数限制"。上线第三天,安全扫描发现有人在暴力枚举验证码,试了8万次,猜中了3个账号的验证码并成功绑定。最终的结果是:紧急加限流 + 全量密码重置 + 写事故报告。代价是整个组加班到凌晨2点。从此以后,老王看到任何验证码功能,第一件事就是加尝试次数限制。

10:00数据库设计:先想数据,再想代码

很多新手后端拿到需求就打开IDE写Controller。但老手第一步永远是先设计数据库表结构。因为代码可以重构,表结构上线后就很难改(要改表结构=数据迁移=风险巨大)。所以先想清楚数据怎么存。

老王打开了DataGrip,开始设计表结构:

DataGrip — user_center@production-dev DDL Editor
-- 用户手机号绑定记录表 CREATE TABLE user_phone_bindig ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT '用户ID', phone VARCHAR(20) NOT NULL COMMENT '手机号(AES加密存储)', status TINYINT DEFAULT 1 COMMENT '1=绑定中 2=已绑定 3=已解绑', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 全局唯一索引:一个手机号只能绑定一个账号 UNIQUE KEY uk_phone_status (phone, status), -- 用户ID索引:按用户查询绑定记录 KEY idx_user_id (user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户手机号绑定记录';
注意这个唯一索引的设计——uk_phone_status(phone, status)。为什么phone和status组合唯一?因为如果用户解绑了(status=3),这个手机号应该可以被其他用户绑定。所以"一个手机号只能绑定一个账号"的准确含义是"一个手机号在同一时间只能有一个有效绑定"(status≠3的记录唯一)。这个设计如果不仔细想,写成UNIQUE KEY uk_phone(phone),用户解绑后手机号就永远不能被别人绑定了。
⚡ 后端习惯 #4:设计表结构时,要考虑"状态流转"。绑定→解绑→再绑定,数据生命周期不是只有"创建"和"删除"。软删除(status标记)比物理删除好——因为审计、回溯、数据分析都需要历史数据。但软删除的唯一索引设计要特别小心,否则软删除的记录会阻止新数据插入。

老王接着设计了验证码存储方案(用Redis,不用MySQL):

Redis CLI — redis://localhost:6379
# 验证码存储结构设计 Key: sms:code:{phone} # 例: sms:code:13800138000 Value: {code} # 6位随机数字 TTL: 300# 5分钟有效 # 发送频率限流 Key: sms:limit:{phone} # 发送间隔锁 TTL: 60# 60秒内不能重发 # 当日发送次数 Key: sms:count:{phone}:{date} # 例: sms:count:13800138000:20260415 TTL: 86400# 到当天24点过期 # 验证码尝试次数 Key: sms:retry:{phone} # 每验证错误+1,到5次锁定 TTL: 300# 和验证码同时过期
验证码存Redis而不存MySQL,原因有三:
1. 验证码是临时数据,5分钟后就没用了,不需要持久化
2. Redis自带TTL过期,不用自己写定时清理
3. 高频读写(发码、验证、限流),MySQL扛不住
类比——寄快递:验证码就像快递的取件码。你不会把取件码写在户口本上(MySQL永久存储),而是写在一张便利贴上(Redis临时存储),贴在快递柜上,5天后自动撕掉(TTL过期)。这就是为什么验证码用Redis而不是MySQL。

10:30API设计:后端的第一步产出是"接口文档"

API设计完之后,第一时间把接口文档发给前端。让前端先看接口定义,能并行开发——前端对着Mock数据写页面,后端写接口实现。这就是"前后端并行开发"的前提。如果接口定义不清晰,前端开发到一半发现接口缺字段,就得等你改完才能继续。

老王用YAPI(接口管理平台)设计了以下接口:

POST /api/v1/user/sms/send 发送短信验证码
Request Body (JSON)
{ "phone": "13800138000" }
Response 200
{ "code": 200, "message": "验证码已发送", "data": { "expireIn": 300 } }
Response 400 — 发送太频繁
{ "code": 4001, "message": "发送太频繁,请60秒后重试" }
POST /api/v1/user/phone/bind 绑定手机号
Request Body (JSON)
{ "phone": "13800138000", "code": "123456" }
Response 200
{ "code": 200, "message": "绑定成功", "data": { "phone": "138****8000", "boundAt": "2026-04-15T14:30:00" } }
Response 400 — 手机号已被绑定
{ "code": 4002, "message": "该手机号已被其他账号绑定" }
注意一个细节:响应里手机号是脱敏的(138****8000),不是完整号码。这是安全习惯——API返回的数据永远脱敏,完整号码只在后台管理系统中可见。这个细节90%的新手不会注意,但安全审计一定会查。
⚡ 后端习惯 #5:API设计的三原则:
1. URL是名词,方法是动词:/api/v1/user/phone/bind(POST),不是/api/v1/bindUserPhone
2. 错误码有层次:4001=发送太频繁、4002=手机号已绑定、4003=验证码错误。不要所有错误都返回code:400
3. 敏感数据脱敏:手机号、身份证号、银行卡号,输出时一律打码

11:00排期估时:为什么后端总说2天,实际要4天

老王在排期时,做了一张任务分解表:

子任务乐观估时实际估时为什么差这么多
建表 + Entity + Mapper0.5h2h要写AES加密/解密工具类,要对字段做校验注解
短信发送Service1h3h要对接短信网关SDK,要做限流逻辑,要做发送日志
绑定/解绑/更换Service2h4h要处理并发绑定(分布式锁),要做事务保证
Controller + 参数校验1h2h每个接口的参数校验注解、异常处理、Swagger文档
站内通知0.5h1.5h要用MQ异步发,要写消费者
自测(Postman逐个测)1h3h正常流程 + 异常流程 + 边界条件,至少20个场景
联调(和前端)1h3h前端问题、字段不匹配、数据格式调整,改来改去
代码评审修改0.5h2h评审意见修改、补充测试、优化代码结构
合计7.5h20.5h实际估时 = 乐观估时 × 2.7
新手后端容易犯的错误是:按"乐观估时"报排期。7.5小时 = "今天下午就能做完"。但实际干起来你会发现:AES加密工具类的密钥管理就花了半小时;短信网关SDK文档有坑;分布式锁的key设计要想清楚;前端联调时总有字段对不上。所以老手的经验法则是:乐观估时 × 2.5~3 = 实际估时
⚡ 后端习惯 #6:估时的时候,一定要把"自测时间"和"联调时间"单独列出。很多新人只估"写代码的时间",觉得自己半天就能写完。但写完代码只是30%的工作量——自测要花30%、联调20%、改评审意见20%。如果排期里不包含自测联调时间,你一定会加班。
类比——装修报价:装修师傅报价的时候不会只报"买材料的钱"。他会报:材料费+人工费+垃圾清运费+验收整改费+意外修补费。后端估时也一样——"写代码的时间"只是材料费,"自测+联调+改评审"就是垃圾清运和验收整改。

13:00开始写码:分层不是形式主义,是工程纪律

午休回来,老王打开IntelliJ IDEA,开始写代码。注意他写代码的顺序——不是从Controller往下写,而是从底层往上写:

① Entity → Mapper ② Service接口 ③ Service实现
④ Controller ⑤ 参数校验 ⑥ 异常处理
从底层(数据)写到上层(接口),不是反过来
为什么从底层往上写?因为底层最稳定——表结构你已经设计好了,Entity和Mapper几乎是机械翻译。写完底层,上层就有了"地基"。如果从Controller往下写,你写到Service的时候发现表结构要改,底层一改上层全要跟着改。这就像盖楼——你不会先搭屋顶再挖地基。
第一步:Entity层(数据实体)
UserPhoneBinding.java — user-center-service/src/main/java/.../entity/
UserPhoneBinding.java
UserPhoneBindingMapper.java
SmsService.java
1@Data
2@TableName("user_phone_binding")
3public class UserPhoneBinding {
4 @TableId(type = IdType.AUTO)
5 private Long id;
6
7 private Long userId;
8
9 // 手机号加密存储,不存明文!
10 private String phone; // AES加密后的值
11
12 /** 1=绑定中 2=已绑定 3=已解绑 */
13 private Integer status;
14
15 @TableField(fill = FieldFill.INSERT)
16 private LocalDateTime createdAt;
17
18 @TableField(fill = FieldFill.INSERT_UPDATE)
19 private LocalDateTime updatedAt;
20}
✓ No errors UTF-8 Java 17 Ln 10, Col 27
第二步:Service层 — 核心业务逻辑
@Service @Slf4j public class PhoneBindingServiceImpl implements PhoneBindingService { @Autowired private StringRedisTemplate redis; @Autowired private SmsGatewayClient smsClient; // 短信网关 @Autowired private UserPhoneBindingMapper bindingMapper; @Autowired private RedissonClient redisson; // 分布式锁 /** * 发送验证码 — 4层防护 * 1. 60秒内不能重发 * 2. 当日不超过5次 * 3. 手机号格式校验 * 4. 生成6位随机码 + 存Redis + 调网关 */ public SendCodeResult sendCode(String phone) { // 1. 手机号格式校验 if (!PhoneUtil.isValid(phone)) { throw new BizException("手机号格式不正确"); } // 2. 60秒发送间隔限制 String limitKey = "sms:limit:" + phone; Boolean notExpired = redis.hasKey(limitKey); if (BooleanUtil.isTrue(notExpired)) { throw new BizException("发送太频繁,请60秒后重试"); } // 3. 当日次数限制(最多5次) String countKey = "sms:count:" + phone + ":" + DateUtil.today(); Long count = redis.opsForValue().increment(countKey); if (count == 1) { redis.expire(countKey, Duration.ofDays(1)); } if (count > 5) { throw new BizException("今日发送次数已达上限"); } // 4. 生成6位验证码 String code = RandomUtil.randomNumbers(6); // 5. 调短信网关发送 try { smsClient.send(phone, "您的验证码是:" + code + ",5分钟内有效"); } catch (Exception e) { log.error("短信发送失败, phone={}", phone, e); throw new BizException("验证码发送失败,请稍后重试"); } // 6. 存Redis(5分钟过期)+ 设60秒间隔锁 redis.opsForValue().set( "sms:code:" + phone, code, Duration.ofSeconds(300)); redis.opsForValue().set(limitKey, "1", Duration.ofSeconds(60)); log.info("验证码发送成功, phone={}, code={}", phone, code); return new SendCodeResult(300); } /** * 绑定手机号 — 并发安全处理 */ @Transactional(rollbackFor = Exception.class) public BindResult bindPhone(Long userId, String phone, String code) { // 1. 验证码校验 String cacheCode = redis.opsForValue().get("sms:code:" + phone); if (cacheCode == null) { throw new BizException("验证码已过期,请重新获取"); } if (!cacheCode.equals(code)) { // 验证码错误,增加重试计数 Long retries = redis.opsForValue().increment("sms:retry:" + phone); if (retries >= 5) { redis.delete("sms:code:" + phone); // 锁定,删验证码 throw new BizException("验证码错误次数过多,请重新获取"); } throw new BizException("验证码错误,还可尝试" + (5 - retries) + "次"); } // 2. 分布式锁:防止同一用户并发绑定 RLock lock = redisson.getLock("phone:bind:lock:" + phone); try { boolean locked = lock.tryLock(3, TimeUnit.SECONDS); if (!locked) { throw new BizException("系统繁忙,请稍后重试"); } // 3. 检查手机号是否已被绑定(唯一性校验) boolean exists = bindingMapper.existsActiveBinding(phone); if (exists) { throw new BizException("该手机号已被其他账号绑定"); } // 4. 写入绑定记录(加密存储) UserPhoneBinding binding = new UserPhoneBinding(); binding.setUserId(userId); binding.setPhone(AESUtil.encrypt(phone)); binding.setStatus(2); // 已绑定 bindingMapper.insert(binding); // 5. 删除验证码(一次性使用) redis.delete("sms:code:" + phone); // 6. 异步发送站内通知 // (通过MQ发送,不阻塞主流程) log.info("手机号绑定成功, userId={}, phone={}", userId, phone); return new BindResult(PhoneUtil.mask(phone)); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } }
看这段代码,注意几个后端"本能反应":
1. 每个外部依赖调用都包try-catch:短信网关可能超时/挂掉
2. 分布式锁的finally块一定要unlock:否则锁泄漏
3. 验证码验证通过后立刻删除:防止重放攻击
4. 手机号加密存储:数据库被脱库也不泄露
5. 每一步操作都有日志:出问题能排查
⚡ 后端习惯 #7:看一个后端是否成熟,看他代码里的"防御性编程"程度。成熟的代码里充满了if (xxx != null)try-catchfinally。不是因为胆小,而是因为生产环境的每一行代码都会被极端情况考验。NPE(空指针异常)是Java后端最常见的线上事故。

15:00自测:你不测的,线上一定会出问题

代码写完了,老王不急着提PR,而是打开Postman开始自测。他的自测清单:

📋 自测清单(逐项打勾)

#测试场景预期结果状态
1正常发送验证码返回200,收到短信
260秒内重复发送返回4001"发送太频繁"
3当日发送第6次返回4001"次数已达上限"
4错误的手机号格式返回400"格式不正确"
5正确验证码绑定返回200"绑定成功"
6错误验证码返回4003"验证码错误"
7验证码连续错5次返回4003"次数过多"
8已过期验证码返回4003"已过期"
9同一手机号被两个用户绑定第二个返回4002"已被绑定"
10解绑后重新绑定同一号码返回200"绑定成功"
11验证成功后重放同一验证码返回4003"已过期"(已被删除)
12参数为空/类型错误返回400"参数校验失败"
很多新人觉得自测是"浪费时间"——"代码我写完了,不会有bug的"。但老手知道,你脑子里跑通的逻辑和实际跑的代码之间,永远有一条沟。比如第11条——验证成功后重放验证码,如果你忘了在绑定成功后删除验证码,用户可以用同一个验证码反复调用绑定接口。自测就是填这条沟的过程。
POST http://localhost:8080/api/v1/user/sms/send
Test #2: 60秒内重复发送
{ "phone": "13800138000" }
400 Bad Request 47ms
{ "code": 4001, "message": "发送太频繁,请60秒后重试", "timestamp": "2026-04-15T15:02:13.456" }
⚡ 后端习惯 #8:自测时不仅要测"正常流程",更要花70%时间测"异常流程"。正常流程你的代码当然能走通——你写代码时就是按正常流程写的。出问题的地方永远在边界条件:空值、超长字符串、并发请求、网络超时、数据库连接断开。测异常场景才是自测的核心价值。

16:00代码评审:你漏的,同事帮你找

自测通过后,老王提交了Git MR(Merge Request):

wang@macbook ~/user-center-service $ git checkout -b feature/phone-binding wang@macbook ~/user-center-service (feature/phone-binding) $ git add . wang@macbook ~/user-center-service (feature/phone-binding) $ git commit -m "feat: 用户手机号绑定功能(发送验证码+绑定+解绑+更换)" # 提交MR前的自查清单: [✓] 代码格式(IDEA格式化了) [✓] 单元测试(核心Service有测试) [✓] 日志(关键路径都有info/error日志) [✓] 异常处理(外部依赖都包了try-catch) [✓] 安全(手机号加密、验证码限流、分布式锁) [✓] 接口文档(YAPI已更新) wang@macbook ~/user-center-service (feature/phone-binding) $ git push origin feature/phone-binding Enumerating objects: 24, done. Counting objects: 100% (24/24), done. Writing objects: 100% (15/15), 4.2 KiB | 4.2 MiB/s, done. remote: To create a merge request, visit: remote: https://gitlab.com/.../merge_requests/new

组长(老李)15分钟后在GitLab上给出了评审意见:

!488 feat: 用户手机号绑定功能
老李(组长)16:15 评论
整体写得不错,几个点改一下:
1. PhoneBindingServiceImpl.java L52
验证码校验通过后删除验证码之前,重试次数也要删除。否则下次发送验证码后,重试计数还是之前的值。
2. PhoneBindingServiceImpl.java L87
分布式锁的key用手机号没问题,但如果用户快速点击两次"绑定"按钮(同一手机号),第二次会拿不到锁直接返回"系统繁忙"。建议把提示语改成"正在处理中,请勿重复操作"。
3. UserPhoneBindingMapper.java
existsActiveBinding 方法的SQL,记得用 phone 的加密值去查(AESUtil.encrypt(phone)),不要用明文查。
4. 建议补充 ✨
在 bindPhone 方法里加一个 @RateLender 注解,限制单个用户1分钟最多绑3次。防止恶意绑定/解绑操作。
评审意见里有3个修改、1个建议。第1条是个真实bug——重试计数没清理。第2条是用户体验优化。第3条是安全漏洞——如果用明文查数据库,那加密就没意义了。这些就是代码评审的价值:你写代码时注意力在"实现功能",评审时注意力在"找漏洞和隐患"。
⚡ 后端习惯 #9:评审代码时关注点排序:安全 > 数据一致性 > 性能 > 可读性 > 代码风格。安全问题和数据不一致是P0(必须改),性能是P1(建议改),代码风格是P2(看心情)。不要把时间花在争论"花括号要不要换行"上。

16:30部署上线:从不直接点"发布"

评审通过,MR合并到dev分支。老王开始部署流程:

dev环境 test环境 预发布环境 生产环境
每个环境都要验证,不是直接上生产
# 1. 自动化构建(CI/CD流水线) ci@gitlab-runner $ mvn clean package -DskipTests [INFO] BUILD SUCCESS ci@gitlab-runner $ docker build -t user-center:v2.3.1 . Successfully tagged user-center:v2.3.1 # 2. 部署到test环境 ci@gitlab-runner $ kubectl set image deployment/user-center \ user-center=user-center:v2.3.1 -n test deployment.apps/user-center image updated # 3. QA在test环境验收 QA验证通过 ✅ (2026-04-15 16:45) # 4. 部署到预发布(和生产同配置) ci@gitlab-runner $ kubectl set image deployment/user-center \ user-center=user-center:v2.3.1 -n staging 冒烟测试通过 ✅ (2026-04-15 17:00)
部署到生产前,老王做了3件准备工作:
1. 数据库迁移脚本先在staging跑一遍——确认DDL不会锁表
2. 准备回滚方案——记下当前版本号v2.3.0,出问题30秒内回滚
3. 通知相关方——在工作群发"准备发布v2.3.1,预计5分钟"
⚡ 后端习惯 #10:上线前的检查清单(老王的版本):
✅ 数据库迁移脚本在staging验证过
✅ 回滚方案准备好(上一版本镜像tag记下来了)
✅ 监控告警已配置(接口RT、错误率、短信发送量)
✅ 功能开关/灰度策略已设置
✅ 发布窗口选在工作日下午4-5点(不是周五!不是凌晨!)
✅ 通知群里发过上线公告

🚫 永远不要在周五下午发布!

这是后端的铁律。周五下午发布 = 如果出问题,你周末得加班修。如果必须周五发,至少留出2小时的窗口期,并且确保有人值班。老王见过太多"周五下午发布导致整个周末都在修bug"的事故了。

17:30复盘总结:今天的工作思路拆解

一天结束,老王在笔记本上做了简单的复盘——这是他的日常习惯:

📊 今日复盘

环节用时做的好的可改进的
需求分析1h拆出了8个子功能,识别了6个风险应找PM当面确认验证码尝试次数限制的逻辑
DB设计1h唯一索引设计考虑了状态流转-
API设计0.5h接口文档同步给了前端,并行开发响应体可以统一加requestId
编码3h分层清晰,防御性编程到位分布式锁可以抽出独立方法减少嵌套
自测2h测了12个场景,包括异常场景应补一个并发绑定的压测
评审1h评审意见有价值,改了3个问题应在提MR前自查第1条(重试计数)
部署0.5h分环境部署,有回滚方案-
复盘的目的不是"自我批评",而是"提取经验"。今天评审发现的3个问题,以后写代码时会主动检查。今天在需求分析阶段多花1小时,省下了后面至少3小时的返工。这就是"慢即是快"——前期准备越充分,后期返工越少。

后端工作思路核心总结

🎯 后端工作10条核心习惯

#习惯新人做法老手做法
1读需求只看"要做什么"找"限制条件"和"异常场景"
2拆需求直接开写先画出功能拆解和依赖关系
3识别风险不考虑异常花30分钟列风险矩阵(红黄绿)
4设计DB先写Controller先设计表结构,再写代码
5设计API边写边定接口先出接口文档,和前端并行
6估时按乐观估时乐观估时 × 3
7写码从上往下写从底层往上写(Entity→Service→Controller)
8自测只测正常流程70%时间测异常场景
9评审只看代码风格关注安全 > 数据一致性 > 性能
10部署直接上生产分环境部署,准备回滚方案
类比——后端开发像做手术:外科医生不是进了手术室才开始想怎么开刀。术前要读病历(需求分析)、做检查(风险评估)、制定手术方案(DB+API设计)、准备器材和血库(编码+测试方案)、安排手术间和麻醉(部署准备)、术后观察(监控+复盘)。后端开发也一样——准备时间占60%,实际"写代码"只占20%,剩下20%是测试和部署。新手以为后端就是"写代码",老手知道后端是"系统工程"。