脱讲稿
简介 : 设计并实现了 完整的 高安全性,高可用,高扩展的 登陆与注册 服务
自述 :
我给系统设计了一个支持 手机号码、微信登陆、邮箱注册 的登陆与注册服务。 使用 长短JWT + Session 控制用户的登陆登出。 处理了跨域和限流问题,并且对于手机登陆支持切换云服务商 支持 failover 策略。功能拥有完整的链路控制,并且使用 wrk 进行压测。
高安全性 :
- 做了限流, 手机验证, 设备验证
高可用 :
- 支持切换多个云服务商
高扩展的 :
- 基于面向接口编程. 并且采用 DDD 设计 支持 repo 层切换多个存储方案,支持 SMS 和 微信登陆切换各种服务
问题 1
- 什么是
Gin的middle-ware? 能解决什么问题 ?
Gin 框架提供的一种 AOP 编程机制, 支持实际业务逻辑前后进行插入额外处理 。 能够解决以下几类问题
Web治理 : 实现 http 层面的熔断、限流、降级
可观测性 : 可以聚合日志,metrics,tracing
身份的认证与鉴权
- 什么是跨域问题,怎么解决 ?
跨域的问题本质是 请求发送方和接收方 host 和 端口不匹配的问题 。 对于跨域问题,浏览器会预先发送一个 preflight option方法,询问允许访问的范围 。
Gin 框架支持使用 Cros 中间件进行处理跨域问题,设置对应的 allow-origins,allow-headers,allow-method即可
- 跨域问题需要设置哪些头部 ? 上面的已经回答
- 什么是 cookie,什么是 session ?
http 本身是无状态协议,为了保护用户的登陆状态,浏览器支持本地存储一个 kv 数据, 这个数据可以理解为 cookie
由于 cookie 的安全性问题,隐私信息一般不存放在 cookie中,因此引入 session 机制
session 机制允许 绑定一个 session_id 给前端,具体身份校验逻辑可以存放在后端避免隐私信息泄漏
- cookie 和 session 比起来有什么缺点 ?
cookie 不依赖后端存储,并且本身可以设置一些安全性配置 例如 domain,expires,secure
缺点的话就是 不允许存放敏感信息,容易丢失
–
- Session ID 可以放在哪里? 如果Cookie禁用
cookie, header, redis,本地缓存,sql 等各种地方
- 用户密码加密算法的选取
尽可能选择高安全系数的,不宜破解的 。 技术选型的时候考虑过 md5,pbkdf2,bcrypt
因为 md5 需要在数据库中额外存储盐值的原因最终选择 bcrypt
使用 wrk 压测的时候,发现 密码加密和不加密的情况下 性能相差10倍, 但是这是不可避免的
- 怎么做登陆校验 ?
正确性校验 :
- 在登陆的时候 获取对应的账号和密码,去数据库中查找,并且使用 bcrypt 进行比对 正确的话 则返回对应的 jwt 用于保护用户状体啊
状态维护:
- 每次获取对应的 短token进行 jwt解析,解析失败则登陆失败
安全性处理:
-
在 JWT 中维护一个 user-agent字段,每次比对该字段是否相同
-
使用 Redis 限流控制 每次的登陆
-
刷新 Session 过期时间的几种方案
-
快要过期的时候刷新 ; 这个可能会有 gap
-
每次访问都刷新 。会频繁的读redis
-
固定时间刷新 , 增加 updateAt字段
-
怎么保护 session_id ?主要还是启用 https协议,设置 cookie 的 secure
-
怎么做到 在session_id 和 jwt-token泄漏之后保护着客户 ? 记录登陆的额外信息
-
如何保护 Web 服务 ? 针对 IP限流、整个集群限流
这几个问题主要在问题 8 会引申
问题 2
- 你用 Redis 解决过什么问题?
业务上 :
-
用来标记某些 handle 是否完成。 例如某些需要在全环境修复的任务
-
分布式锁 控制针对于 IP 限流的一些账户创建
-
Stream 实现的轻量化 mq 队列 用于实现异步的消费
-
存储一些实时性不高的 例如快照的信息
-
使用 Zset 进行热榜功能的排序
-
使用 Redis 单机 线程安全的特性 实现限流
- 你知道 Redis 支持哪些数据结构,用过哪些 ? 用来解决什么问题?
string,list,hash,set,zset . 问题 1 的扩展
- 各个数据结构的底层实现 ?
不知道 ,有对应的 memo 。 预计会单独开辟一个文章
- 当你更新数数据的时候 先更新数据库还是先更新缓存,有没有一致性问题
先更新数据库,然后删除缓存。 对于更新操作 采用 Cahce-Aside 的方式 会有一致性问题
我们只有在 Get 操作的时候才会重新写入缓存
(这里说实话,忘记了开发的时候的想法了,创建一个memo处理一下)
- 如何解决一致性问题
这里主要是 check-do thing 的模型吧 。 尽可能使用原子操作 ,例如 lua 或者是 atomic
(同样单独使用 memo 进行处理)
问题 3
- 什么是依赖注入,如何在 Go 实现 依赖注入
依赖注入,就是 A 要调用 B 上面的方法, A 在构造的时候要求传入构造好的 B 而不是自己初始化一个 B 。
通过引入 wire包进行依赖注入
- 什么是面向接口编程 ? 为什么要面向接口编程 ?
业务之间 组件和组件的通信必须依赖接口 。 即 如果 A 调用 B,那么 B 必须是一个接口 。
面向接口编程最主要的一点就是提升扩展性,避免代码堆积 。
- 什么是长短 token ? 为什么要用长短两个 token ?
长 token 用于控制 短 token 的刷新
短 token 用于进行登陆的校验
长短token的设计是为了安全性, 如果只有一个 token 有效期设置的很短 用户频繁登陆,很长的话会有安全问题
- 长短 token 的过期时间应该怎么设置 ?
根据业务需求和产品经理来判断,长 token 一般会很长 例如半年,一个月 。 短token 可以是 2小时 或者是 1天
- 怎么保证长token的安全性,万一泄漏了怎么办 ?
携带一些设备信息,例如 user-agent 手机设备号,在对于敏感操作的时候,增加手机号验证
- 使用 JWT token 怎么退出登陆,使用 长短 token 之后怎么退出登陆
增加一个 session_id 进行维护,在 Redis 中维护一个黑名单
扩展问题
Q : 对于系统支持 微信、手机号、邮箱注册,数据库表是怎么设计的
A : 对于这三个字段单独设计了唯一索引,我们认为任意一个渠道注册进来就是单独的一个账号
Q : 那么如果我后续想要实现 手机号和微信和邮箱 是一个账号怎么办
A :
看一下业务层面是否要迁移历史数据
如果不迁移历史数据的话 :
-
创建部分联合索引 加上 _create 限制,保证就新的数据需要遵守该索引
-
业务层面强制 这三类业务注册的时候 就一起绑定 手机号 或者是 邮箱
如果要迁移历史数据的话
-
优先保证内部数据迁移跑完
-
并且合并多个账户之间的数据。 这里可能复杂点 预计需要强制用户绑定手机号才可以进行功能操作 xxx
-
创建索引
-
改善业务层面的登陆与注册服务
Q : Gin 实现了哪些限流 降级 熔断
遗留
-
尝试把 SMS 服务单独在拆一个场景出来,增加异步的内容
可以设置一个 同步转异步的 容错机制 。 当第三方不可用的时候,将当前请求存储到数据库中,后续再单独启动一个 gouroutine 异步的发送出去
AI Judge
|
|
对照 webook 实现后,这份准备材料按 2 年经验后端面试 标准评分如下。
总分:62 / 100
| 维度 | 分数 | 说明 |
|---|---|---|
| 项目叙事与亮点提炼 | 14/20 | 有多通道登录、长短 Token、限流、failover,但「三高」略夸大 |
| 与代码一致性 | 10/15 | 大方向对,细节与代码不一致,易被追问穿帮 |
| 安全与鉴权深度 | 12/20 | 知道 bcrypt / JWT / ssid,但机制说不清、有概念混用 |
| Go 工程能力(DI/接口/分层) | 12/15 | 这块相对扎实,和代码匹配 |
| Redis / 中间件深度 | 7/15 | 用过什么能说,底层与一致性大量空白 |
| 表达与完整度 | 7/15 | 错别字多、多题空答、遗留太多 |
优点
- 项目选型对得上代码:邮箱密码、短信、微信;长短 JWT +
ssid黑名单;User-Agent校验;SMS failover;Wire + 接口;Redis 滑动窗口限流;CORS;wrk压测——这些在仓库里都能找到对应实现。 - 能讲清楚「为什么做」:长短 Token 的安全/体验权衡、接口便于换 SMS/Repo、bcrypt 有压测体感——比只会背概念强。
- 扩展题有业务意识:多渠道账号合并、历史数据迁移,这是面试官爱听的「产品 + 工程」题。
- 工程分层叙事清晰:依赖注入、面向接口,和
wire.go、UserCache注释里的约束一致。
缺点(面试里会扣分的点)
1. 概念混用,容易被打穿
- 实际是 JWT 放 Header(
x-jwt-token/x-refresh-token)+ Redisusers:ssid黑名单,不是经典 Cookie Session。 - Cookie / Session 答得偏教材,和项目实现脱节;面试官一问「你们项目 Session 存在哪」,容易答乱。
- 「Session ID 可放 redis/本地缓存/sql」把 服务端存储 和 客户端携带位置 混在一起了。
2. 与代码不一致
| 讲稿说法 | 代码现实 |
|---|---|
| 短 Token 2h / 1 天,长 Token 月/半年 | 短 30 分钟,长 7 天(redis_jwt.go) |
| 设备验证 | 仅比对 User-Agent,不是设备指纹 |
| 完整高可用 SMS | 有 failover,但 SendV1 有逻辑问题;真实多厂商未必接好 |
| DDD | 更接近分层 + 接口,不是完整 DDD |
短信登录流程本身也不完整:LoginSMS 校验成功后没发 Token,Token 却在 SendLoginSMSCode 里设置——若被问「短信登录时序」,会很难圆。
3. 关键答案有硬伤
- bcrypt vs md5:选 bcrypt 主要因为自适应哈希/抗暴力破解,不是「md5 要额外存盐」。bcrypt 自带盐,理由说偏了。
- 跨域:不只是 host/port,还有 scheme;还缺
Credentials、ExposeHeaders(你们代码里刚好有ExposeHeaders暴露 JWT Header)。 - 缓存一致性:写了「忘了」——2 年经验被问 Cache-Aside / 双写,这是硬伤。
- Gin 限流/降级/熔断:空题;项目有限流,没有完整熔断降级,却标题写了「高可用」。
4. 准备完整度不够
Redis 底层、一致性、Gin 治理、遗留 SMS 异步都标了 memo/空答。面试不是写博客进度,空题 = 扣分。
5. 自我包装过满
「完整的高安全性、高可用、高扩展」对学习项目偏重。2 年经验更稳妥的说法是:
练手项目,覆盖多登录方式、长短 Token、限流与 SMS 容错,重点验证接口化扩展。
建议(按优先级)
立刻改讲稿
- 用代码里的真实数字讲:AT 30min、RT 7 天;退出时
ssid写入 Redis,TTL 与 RT 对齐。 - 统一术语:说「JWT + 会话标识(ssid)黑名单」,少说「Session 存在 Cookie」。
- 删掉或降级「设备验证」:改成「绑定 User-Agent,防简单 Token 盗用,不能防 UA 伪造」。
- 补齐空题,至少能答 2 分钟:
- Cache-Aside:先写 DB 再删缓存;并发下的短暂不一致
- Redis String/Hash/ZSet 各举一个本项目例子(验证码、用户信息、热榜)
- 限流:滑动窗口;熔断降级诚实说「项目未做,可讲思路」
对着代码能画一张时序图
- 邮箱登录 → 发 AT/RT → 中间件校验 AT + UA + ssid
- Refresh:校验 RT → 查黑名单 → 发新 AT
- Logout:ssid 入黑名单
面试官最爱顺着这条链路追问。
主动暴露已知坑(加分)
- 短信登录时序问题
- Failover 主备不均 /
SendV1错误处理 FindOrCreate主从延迟注释
「我知道问题在哪、怎么改」比「系统很完美」更像有 2 年经验。
表达
错别字(Cros、状体啊、Cahce)和口语化「不知道有 memo」直接改掉;面试可以诚实说「这块我回去补过」,但讲稿里不该留白。
面试官视角的一句话
项目素材够用(约 70 分的料),讲解与掌握大约 55–60 分;合在一起 62。
能过初筛讲项目,但深挖安全、缓存一致性、短信登录细节时风险大。把术语对齐代码、补空题、主动讲缺陷,冲到 75+ 比较现实。