项目技术学习手册
实际业务场景、模块效果与 Go 技术点解析
基于当前 Go + Kratos + protobuf + Vue 3 代码整理
|
从“用户找柜”到“支付开门、异常换柜”的完整代码阅读路线 |
整理日期:2026 年 7 月 31 日
项目目录:helloworld
阅读说明
|
文档目标:帮助你把每个业务效果和真实代码位置对应起来。阅读时不只看语法,还要观察请求如何穿过 service、biz、data 三层,以及接口、并发和数据一致性为什么这样设计。 |
1. 项目的实际业务场景
这个项目模拟的是一套面向旅客、居民、快递员、柜机终端和运营人员的智能快寄柜系统。它不只是“查询柜子”,而是把定位、选城市、搜索网点、选择柜格、估算费用、下单、支付、开门、异常换柜、优惠券和运营配置串成一条完整链路。
|
核心价值:用户在车站、景点、商圈等场景中,可以快速找到附近有空闲柜格的寄存点,完成费用确认和支付后获得开门凭证;系统则在高并发下保护柜格不被重复占用,并保留可追溯的日志和异常记录。 |
1.1 主要参与角色
|
角色 |
实际操作 |
系统关注点 |
|
普通用户 |
定位、切换城市、搜索寄存点、选柜、用券、支付、取件 |
体验流畅、金额正确、个人数据隔离 |
|
快递员/工作人员 |
存件、操作柜门、查看终端状态 |
柜格状态准确、凭证安全、操作可审计 |
|
柜机终端 |
展示屏幕状态、开门、确认存取件 |
指令可信、状态同步、故障可恢复 |
|
运营人员 |
配置城市、指南、须知、收费规则和优惠券 |
配置实时生效、权限隔离、变更留痕 |
|
运维/管理员 |
查看告警、监控接口、处理异常 |
耗时、错误率、并发量和安全日志可观测 |
1.2 项目覆盖的现实问题
- 定位不稳定:GPS 权限关闭或超时时,自动尝试 IP 定位,再降级到默认城市。
- 城市逐步开放:通过稳定灰度分桶,让不同用户看到不同的已开通城市池。
- 附近寄存点查询:结合城市、关键词、坐标、距离排序和分页返回最合适的点位。
- 柜格并发抢占:同一柜格在多人同时下单时只能被一个订单锁定。
- 费用复杂:小时计费、日封顶、押金和优惠券要分项计算并可追溯。
- 支付和开门异常:失败时释放资源,支持在同网点换柜并归档旧订单。
- 运营内容变化频繁:指南、须知、收费规则和城市文案通过数据配置更新。
- 安全和审计:统一鉴权、限流、脱敏日志、支付校验和操作审计。
2. 用户完整业务旅程
从用户打开首页到完成寄存,代码大体执行以下十个阶段。这个顺序也可以作为理解整个项目的主线。
|
阶段 |
用户动作 |
后台处理 |
|
1 |
定位 |
浏览器 GPS -> 后端解析;失败则 IP -> 默认城市 |
|
2 |
选城市 |
展示灰度范围内城市,支持中文/拼音检索和手动切换 |
|
3 |
找网点 |
按城市、坐标和关键词搜索,计算距离并分页排序 |
|
4 |
看柜格 |
查询小/中/大柜格、空闲状态、地址和营业信息 |
|
5 |
算费用 |
按时长和柜型计租金,叠加押金,校验并抵扣优惠券 |
|
6 |
确认须知 |
读取后台配置须知,记录查看和同意行为 |
|
7 |
锁柜下单 |
在锁定窗口内占用柜格,生成唯一待支付订单 |
|
8 |
支付 |
创建支付单,校验回调,更新订单与柜格状态 |
|
9 |
开门寄存 |
生成柜门号、密码/二维码等开门凭证并通知用户 |
|
10 |
异常恢复 |
超时/失败释放柜格;故障时重新分配并记录换柜审计 |
3. 系统架构与目录分层
|
主调用方向:Client -> HTTP/gRPC Server -> Service(DTO 转换) -> Biz(业务规则) -> Repo 接口 -> Data/Plugin 实现 -> 数据库、缓存或第三方服务。 |
项目遵循 Kratos 的分层方式。分层的关键不是文件夹好看,而是防止 HTTP、protobuf、SQL 和第三方 SDK 混进核心业务规则,降低后续替换存储或支付供应商的成本。
|
目录层 |
职责 |
主要示例 |
|
api/<domain>/v1 |
定义 protobuf DTO、HTTP 路由、gRPC 契约和错误原因 |
api/locker/v1/locker.proto |
|
internal/service |
校验传输参数,完成 DTO 与领域对象转换,调用用例 |
internal/service/locker.go |
|
internal/biz |
领域模型、业务规则、Usecase、Repo 接口和业务错误 |
internal/biz/locker.go |
|
internal/data |
SQL、缓存、内存仓库、事务以及 PO/DO 转换 |
internal/data/locker.go |
|
internal/plugin |
定位、城市灰度、支付或其他可替换第三方适配器 |
internal/plugin/location/ |
|
internal/support |
鉴权、限流、日志、计费、状态机、地理计算等公共能力 |
internal/support/ |
|
internal/server |
创建 HTTP/gRPC 服务、挂载中间件、注册生成路由 |
internal/server/http.go |
|
cmd/helloworld |
程序入口和 Wire 依赖装配 |
cmd/helloworld/bootstrap/wire.go |
|
migrations |
MySQL 表、索引、唯一约束和初始化结构 |
001 到 011 SQL 脚本 |
3.1 三种数据形态
DTO 是接口传输对象,由 protobuf 生成;DO 是 biz 层拥有的领域对象;PO 是 data 层的持久化形态。Service 负责 DTO <-> DO,Data 负责 DO <-> PO。这样 biz 层不需要知道 HTTP 字段名或数据库列名。
Client DTO -> service -> Domain Object -> biz -> Repo interface -> data -> Persistent Object -> storage
4. 已实现的功能模块
4.1 公共支撑模块
公共支撑模块被 HTTP 与 gRPC 入口统一复用,给定位、城市、寄存点、订单和支付等业务提供一致的安全、缓存、日志和监控能力。
|
能力 |
实现效果 |
代码位置 |
|
鉴权与角色 |
解析 Bearer Token,生成可信 Principal,按 operation 校验角色 |
internal/support/security/security.go |
|
令牌桶限流 |
按 Token 指纹 + operation 分桶,控制突发和持续调用频率 |
internal/support/security/security.go |
|
多级缓存 |
L1 内存 + 可插拔 L2,统一 TTL、Key、失效和 read-through |
internal/data/cache/cache.go |
|
日志埋点 |
记录 request_id、耗时、错误、脱敏入参/出参和用户行为 |
internal/support/observability/observability.go |
|
监控告警 |
统计并发、错误率和 P95;定位/搜索超阈值时生成告警 |
internal/support/observability/observability.go |
|
版本兼容 |
解析 X-API-Version 并校验最低兼容版本 |
internal/support/versioning/versioning.go |
|
通用工具 |
地理距离、分页、i18n、灰度下发、统一响应、CRUD |
internal/support/ + internal/data/crud/ |
4.2 用户定位模块
定位业务优先使用 GPS 经纬度,失败后尝试 IP,最后返回系统默认城市。结果标准化为城市名、城市编码、省份、经纬度、来源和是否降级,并写入缓存与数据库。
- 业务规则:internal/biz/location.go,负责 GPS > IP > 默认城市、刷新和手选城市逻辑。
- 传输入口:internal/service/location.go,负责请求字段与客户端 IP 处理。
- 持久化:internal/data/location.go,通过 database/sql 保存 user_locations,并结合缓存恢复。
- 第三方边界:internal/plugin/location/,定位解析器以后可以替换为真实地图供应商。
- 数据库结构:migrations/002_user_locations.sql。
4.3 城市选择管理模块
系统返回已开通且对当前用户灰度可见的城市,支持中文、全拼、拼音首字母和城市编码检索。用户切换城市后,选择记录与定位记录会在事务中更新,前端据此重新查询寄存点。
- 城市业务:internal/biz/city.go,负责列表、搜索、分组、切换和行为埋点。
- 城市存储:internal/data/city.go,使用 SQL 查询、缓存和数据库事务保存切换结果。
- 灰度算法:internal/plugin/city/rollout.go,使用稳定哈希避免用户反复进出灰度组。
- 动态文案:未开通提示和年度拓展文案存储在数据库,运营更新后实时生效。
- 数据库结构:migrations/003_city_management.sql。
4.4 寄存点地图搜索与寄存指南
寄存点搜索按城市过滤,可结合坐标和关键词查询。业务层使用地理距离计算并按距离排序,返回名称、地址、距离、空闲柜数、地图坐标和分页信息;5 公里内无结果时返回空状态并记录埋点。
寄存指南支持规则、步骤、注意事项和 FAQ 分层返回,并按语言、版本兼容策略选择内容;后台更新后无需重新发布服务。核心接口统一定义在 api/locker/v1/locker.proto,业务入口位于 internal/biz/locker.go。
4.5 柜格、收费规则与状态管理
网点柜格接口返回柜格编号、小/中/大规格、空闲/锁定/占用/故障状态以及动态收费规则。不可用柜格在后端被拒绝,前端也会置灰;柜格状态变化会生成使用流水。
|
环节 |
关键规则 |
主要代码 |
|
查询柜格 |
按网点返回柜格详情和规格汇总 |
LockerUsecase + LockerRepo |
|
收费规则 |
小时单价、日封顶、押金、营业时间可动态配置 |
internal/biz/locker.go |
|
状态变更 |
空闲/锁定/占用/故障受业务校验保护 |
internal/data/locker.go |
|
使用流水 |
记录柜格占用、释放和异常操作 |
internal/data/locker.go |
4.6 费用预估与优惠券
费用计算以整数“分”为金额单位,避免浮点数误差。基础租金根据时长、柜型、小时单价和日封顶计算;押金单独展示且不参与优惠;优惠券会校验有效期、城市、网点、柜型、抵扣上限和核销状态。
最终应付 = max(基础租金 - 优惠抵扣, 0) + 押金
通用计费逻辑位于 internal/support/pricing/pricing.go;LockerUsecase 组装业务上下文后调用 pricing.Estimate;优惠券查询和核销仓储逻辑位于 internal/data/locker.go。
4.7 柜格锁定、订单与寄存须知
用户确认寄存时,仓储层在互斥锁保护下检查柜格是否仍为空闲,写入带过期时间的 doorLocks,并把柜格标记为锁定。随后生成唯一订单号、待支付状态、费用快照、押金、优惠券和锁定截止时间。
寄存须知由后台配置。创建订单前必须有用户确认记录,业务层同时记录查看和确认行为,避免前端绕过勾选直接支付。
|
并发语义:当前 doorLocks 是单进程内存锁模型,用来演示高并发下的唯一占用逻辑。多实例生产部署时应替换为 Redis/数据库条件更新,并增加唯一约束或行锁。 |
4.8 支付流程与开门凭证
支付流程先根据订单创建支付单,再处理第三方回调。回调会核对订单、支付流水、金额和状态;成功后把订单改为已支付、柜格改为已占用,生成柜门编号、开箱密码/二维码等开门凭证,并触发消息通知。
支付接口和 DTO 位于 api/locker/v1/locker.proto;支付业务编排位于 internal/biz/locker.go;原子状态更新与流水保存位于 internal/data/locker.go。
4.9 支付异常、柜门故障与换柜
支付超时、支付失败或开门故障时,系统释放锁定柜格、取消待支付单据并回滚订单状态。用户选择继续操作后,系统在同网点寻找相同规格的空闲柜格,生成新订单和新支付单,旧订单标记作废,并保存原柜号、新柜号、原因和时间。
异常提示会区分支付超时、柜门故障和网点停业,并提供客服入口。主要实现是 LockerUsecase 的异常处理/重试方法和 lockerRepo 的 ReleaseStorageOrder、RetryStorageOrder、SwitchStorageOrderDoor。
4.10 优惠券配置管理
运营端可以配置面额、有效期、城市、网点、柜型、叠加规则和抵扣上限;用户查询时会同时返回可用、过期和已核销状态,前端据此置灰。订单抵扣后会生成核销流水并关联订单。
接口定义位于 locker.proto;业务校验位于 internal/biz/locker.go;配置、列表过滤和核销记录位于 internal/data/locker.go;建表脚本位于 migrations/011_storage_coupon_config_redemption.sql。
5. 项目使用的 Go 技术点
5.1 struct、方法和领域建模
项目使用 struct 表达 Locker、Door、StorageOrder、PaymentRecord、Coupon、LocationResult 等领域对象。方法接收者把行为放在数据附近,例如 Pricing.PriceFor、Locker.AvailableDoorCount 和 Locker.FindDoor。
- 知识点:结构体、字段组合、指针接收者和值接收者、自定义类型。
- 代码:internal/biz/locker.go 中的领域类型和方法。
- 价值:让“柜格能否使用、订单当前状态”等规则有明确归属,而不是散落在接口处理器。
5.2 自定义类型、常量与状态枚举
DoorSize、DoorStatus、StorageOrderStatus、PaymentStatus 和 StorageExceptionScene 使用自定义字符串类型与 const 常量表达有限状态。protobuf 层也定义对应 enum,service 层负责转换。
|
学习重点:Go 的 const 不等于数据库约束。生产系统仍应在数据库中增加 CHECK、唯一索引或条件更新,避免绕过应用代码后写入非法状态。 |
5.3 interface 与依赖倒置
biz 层声明 LockerRepo、LocationRepo、CityRepo 等接口,Usecase 只依赖接口;data 层实现这些接口。这样业务测试可以传入 fake repo,后续也可以把内存仓库替换为 MySQL/Redis,而不改用例规则。
biz defines interface -> data implements interface -> Wire injects implementation into Usecase
关键位置:internal/biz/locker.go 的 LockerRepo;internal/data/locker.go 的 lockerRepo;cmd/helloworld/bootstrap/wire.go 的依赖装配。
5.4 context.Context
几乎所有 service、usecase 和 repo 方法都把 context.Context 作为第一个参数。它承载请求取消、超时、链路信息和认证 Principal,使一次请求的上下文能够贯穿多层。
- security.WithPrincipal 把认证主体写入 context。
- service 通过 PrincipalFromContext 获取可信用户,而不是直接相信请求中的 user_id。
- database/sql 的 QueryContext、ExecContext 和 BeginTx 能响应请求取消。
5.5 map、slice 与防御性复制
内存仓库使用 map 按 ID 保存柜机、订单、支付记录、优惠券和锁信息;列表接口使用 slice 返回结果。clone 系列函数在读写边界复制对象,避免调用方拿到仓库内部指针后绕过锁修改数据。
关键位置:internal/data/locker.go 顶部的 map 字段,以及文件后半部分的 cloneStorageOrder、cloneLocker 等复制函数。
5.6 sync.RWMutex 与并发安全
lockerRepo 和 MemoryCache 使用 sync.RWMutex。只读查询获取 RLock,写订单、锁柜、支付回调和状态变更获取 Lock。多个读请求可以并发,写请求会独占关键区。
|
原子性示例:CreateStorageOrder 在同一把写锁内完成“检查门状态 -> 清理过期锁 -> 创建锁 -> 修改柜格状态 -> 保存订单”,防止两个请求同时占用同一个柜格。 |
5.7 goroutine、channel 与 ticker
公共组件启动后台 goroutine,使用 time.Ticker 周期清理长时间未访问的限流桶;通过 channel 接收停止信号,并在应用退出时释放资源。这展示了 Go 的轻量并发任务和生命周期管理。
关键位置:internal/support/support.go,以及 security.TokenBucketLimiter 的清理方法。
5.8 database/sql、事务与 SQL 占位符
Data 持有长期复用的 *sql.DB。定位和城市仓储使用 QueryContext、QueryRowContext、ExecContext 与事务。城市切换需要同时更新 user_city_selections 和 user_locations,因此使用事务保证要么全部成功,要么回滚。
- internal/data/data.go:创建数据库连接并初始化 SQLite 开发结构。
- internal/data/location.go:定位记录查询、upsert、清理和缓存协同。
- internal/data/city.go:城市列表查询、选择事务和动态文案。
5.9 Go 泛型
缓存和通用 CRUD 使用类型参数减少重复代码,例如 GetOrLoad[T] 可以缓存任意可 JSON 序列化的数据,Store[T, ID] 可以为不同实体提供一致的 CRUD 结构。
关键位置:internal/data/cache/cache.go 的 GetOrLoad[T];internal/data/crud/store.go 的 Store[T, ID] 和 MemoryStore。
5.10 errors、错误包装与 Kratos 语义错误
biz 层定义 NotFound、BadRequest、Conflict、Unauthorized 等语义错误;data 层把底层 SQL/仓储错误转换为业务错误;service 直接返回给 Kratos,由框架映射为 HTTP/gRPC 状态。底层错误使用 fmt.Errorf("...: %w", err) 保留错误链。
这比返回普通字符串更可靠:前端可根据稳定 reason 展示不同提示,日志也能区分柜格冲突、支付校验失败和资源不存在。
5.11 protobuf、gRPC 与 HTTP 双协议
locker.proto 同时定义 RPC、message、enum 和 HTTP annotation。生成代码让一套业务服务同时提供 HTTP 与 gRPC 入口,避免手写两套 DTO 和路由。不要手动修改 *.pb.go、*_grpc.pb.go 或 *_http.pb.go。
关键位置:api/locker/v1/locker.proto;internal/server/http.go 和 internal/server/grpc.go 注册同一个 LockerService。
5.12 中间件与函数式组合
Kratos Middleware 本质上是接收 next Handler 并返回新 Handler 的高阶函数。项目按 Recovery、Observability、Security、Versioning、Validate 的顺序包装请求,实现统一异常恢复、日志、鉴权、限流、版本校验和参数校验。
request -> recovery -> observability -> security -> versioning -> validate -> service
5.13 密码学与敏感数据处理
项目使用 crypto/rand 生成不可预测凭证,使用 SHA-256 保存 Token 指纹和取件码摘要,使用 crypto/subtle 的常量时间比较降低时序侧信道风险。日志会递归识别 token、password、pickup_code 等敏感字段并替换为脱敏值。
|
金额安全:金额以 int64 的“分”为单位保存和计算,不使用 float64,避免 0.1 + 0.2 一类浮点误差影响支付校验。 |
5.14 算法:排序、分页和地理距离
附近寄存点查询使用 Haversine 公式计算两点球面距离,使用 sort.Slice 按距离排序,再按 page/page_size 截取 slice。监控 P95 也会复制耗时样本后排序并取第 95 百分位。
- 地理工具:internal/support/geo/distance.go。
- 寄存点排序:internal/biz/locker.go 的搜索逻辑。
- 分页工具:internal/support/pagination/pagination.go。
5.15 状态机
订单不是任意修改状态,而是由 OrderMachine 维护允许的流转,例如 pending_payment -> paid/cancelled/expired。非法跳转返回错误,避免把已取消订单直接改成已支付。
关键位置:internal/support/statemachine/order.go,并在订单、支付和异常处理过程中调用。
5.16 依赖注入与 Wire
ProviderSet 把 repo、usecase、service、server 和公共组件构造函数交给 Google Wire。编译期生成 wire_gen.go,应用启动时得到清晰、类型安全的依赖图;生成文件不能手改。
5.17 测试方法
项目测试覆盖鉴权限流、缓存生命周期、位置持久化、城市事务、计费、状态机、地理距离和 Locker 业务流程。测试通常使用 table-driven tests、t.Run、临时数据库和可替换时钟,体现 Go 中常见的可测试设计。
5.18 Go 技术点与代码位置总表
|
Go 技术点 |
典型代码位置 |
在业务中的作用 |
|
struct / 方法 |
internal/biz/locker.go |
建模柜机、柜格、订单、支付、优惠券及其行为 |
|
interface |
LockerRepo / LocationRepo / CityRepo |
隔离业务规则和具体存储实现 |
|
context.Context |
service -> biz -> data 全链路 |
传递取消、超时、认证主体和链路信息 |
|
sync.RWMutex |
internal/data/locker.go |
保护内存仓库、锁柜和支付状态原子更新 |
|
goroutine/channel |
internal/support/support.go |
后台定时清理限流桶并支持优雅停止 |
|
database/sql |
internal/data/location.go / city.go |
真实保存定位、选城和运营文案 |
|
SQL transaction |
internal/data/city.go |
保证选城与定位同步更新 |
|
泛型 |
cache.GetOrLoad[T] / crud.Store[T, ID] |
复用缓存回源和 CRUD 基础逻辑 |
|
map/slice |
internal/data/locker.go |
保存内存数据、过滤列表、排序和分页 |
|
sort.Slice |
internal/biz/locker.go |
按距离排序寄存点、按样本计算 P95 |
|
crypto |
security.go / locker.go |
Token 指纹、凭证生成、哈希校验和常量时间比较 |
|
Kratos errors |
internal/biz/*.go |
输出稳定的 HTTP/gRPC 语义错误 |
|
protobuf |
api/*/v1/*.proto |
定义接口契约并生成 HTTP/gRPC 代码 |
|
middleware |
internal/server/http.go / grpc.go |
统一鉴权、限流、日志、版本和校验 |
|
状态机 |
internal/support/statemachine/order.go |
约束订单状态流转 |
|
Wire |
cmd/helloworld/bootstrap/ |
编译期依赖注入和应用装配 |
6. 典型调用链
6.1 获取当前定位
- HTTP/gRPC 请求进入 LocationService。
- Service 转换 DTO,并把用户、GPS 坐标、客户端 IP 交给 LocationUsecase。
- Usecase 先查询有效缓存/数据库;需要刷新时执行 GPS -> IP -> 默认城市策略。
- 解析器通过 plugin/location 边界工作,业务层不依赖具体地图供应商。
- LocationRepo 先写数据库,再更新缓存;Observability 记录定位埋点和耗时。
api/location/v1 -> service/location.go -> biz/location.go -> plugin/location + data/location.go
6.2 搜索附近寄存点
- Security 中间件验证 Token、角色和调用频率。
- LockerService 校验城市、坐标、关键词和分页参数。
- LockerUsecase 按城市/关键词过滤,调用 geo.DistanceMeters 计算距离。
- 使用 sort.Slice 从近到远排序,再截取当前页。
- 无结果时生成空状态并记录 storage_points_searched 行为。
server middleware -> service/locker.go -> biz/locker.go -> LockerRepo -> response DTO
6.3 费用预估、锁柜和创建订单
- 查询柜格与动态收费规则,确认柜格可选。
- 校验时长、营业截止时间和优惠券适用范围。
- 调用 pricing.Estimate 计算租金、抵扣、押金和最终应付。
- 用户确认寄存须知后,Usecase 组装 CreateStorageOrderCommand。
- lockerRepo 在写锁内锁柜、创建订单、保存费用快照和优惠券占用信息。
6.4 支付回调
- 接收支付渠道回调并校验签名/身份上下文。
- 按支付流水号查找支付单,核对订单号、金额、押金和当前状态。
- 状态机确认待支付可以流转为已支付。
- 在原子写操作内保存流水、更新订单、将柜格改为占用。
- 使用安全随机数生成开门凭证,保存摘要并向用户发送通知。
6.5 异常释放与换柜
- 根据支付超时、柜门故障或网点停业识别异常场景。
- 释放旧柜格锁,取消或作废旧订单和待支付单。
- 在同网点寻找同规格的空闲柜格。
- 创建新锁、新订单和新支付单,并返回继续操作信息。
- 写入换柜审计日志,记录旧柜、新柜、原因、操作者和时间。
7. 数据持久化现状与实现边界
|
已经真实落库:用户定位、用户选城、城市列表/文案等模块通过 database/sql 访问 SQLite/MySQL。开发环境可自动初始化 SQLite;MySQL 使用 migrations 中的脚本。 |
|
目前主要为内存实现:柜格、寄存订单、支付流水、支付回调、异常换柜和优惠券核心仓储主要由 lockerRepo 的 map + sync.RWMutex 模拟。程序重启后这些演示数据会重置。 |
migrations/005 到 011 已提供订单、支付、柜格规则、锁、须知、异常重试和优惠券等表结构与索引,说明数据库模型已经设计,但 data/locker.go 还需要替换为正式 SQL/Redis 实现,才能满足多实例和持久化生产要求。
7.1 为什么内存实现仍有学习价值
- 可以清楚观察互斥锁如何保护一个完整业务事务。
- 不依赖外部基础设施即可演示完整订单和支付流程。
- Repo 接口保持不变,后续替换数据库实现时 biz 和 service 层无需重写。
- clone 函数展示了引用类型在并发程序中的数据所有权问题。
7.2 生产化建议
- 将 doorLocks 替换为 Redis SET NX + TTL,或数据库条件更新/行锁,并保留唯一约束。
- 把订单、支付、优惠券核销和换柜审计写入 MySQL 事务,使用支付流水唯一索引保证幂等。
- 用 JWT/OAuth2 或 API Gateway 替换静态 Token 校验器;密钥放入密钥管理服务。
- 为支付回调增加真实供应商签名验证、时间戳和重放保护。
- 把内存指标导出到 Prometheus/OpenTelemetry,把 JSONL 日志接入集中日志系统。
- 把进程内 L1 缓存与 Redis L2 组合,配置热点 Key 失效和防缓存击穿策略。
- 增加集成测试、并发压测、支付幂等测试和数据库故障回滚测试。
8. 推荐代码阅读顺序
|
顺序 |
文件 |
重点 |
|
1 |
api/locker/v1/locker.proto |
先理解系统对外提供了哪些能力和数据结构。 |
|
2 |
internal/server/http.go |
观察一条请求先经过哪些中间件。 |
|
3 |
internal/service/locker.go |
学习 DTO 校验和 DTO/DO 转换。 |
|
4 |
internal/biz/locker.go |
阅读核心领域对象、Repo 接口和业务流程。 |
|
5 |
internal/support/pricing/pricing.go |
单独理解费用算法和金额边界。 |
|
6 |
internal/support/statemachine/order.go |
理解订单状态为什么不能随意修改。 |
|
7 |
internal/data/locker.go |
理解内存仓库、并发锁、原子更新和复制。 |
|
8 |
internal/biz/location.go + city.go |
对比定位策略和城市灰度业务。 |
|
9 |
internal/data/location.go + city.go |
学习 database/sql、缓存和事务。 |
|
10 |
internal/support/security/security.go |
学习接口鉴权、角色和令牌桶限流。 |
|
11 |
internal/support/observability/observability.go |
学习中间件日志、脱敏、P95 和告警。 |
|
12 |
migrations/001-011 |
把业务查询路径与表、索引、唯一约束对应起来。 |
9. 关键文件索引
|
主题 |
核心文件 |
|
Locker 协议 |
api/locker/v1/locker.proto |
|
Locker 传输层 |
internal/service/locker.go |
|
Locker 业务层 |
internal/biz/locker.go |
|
Locker 仓储层 |
internal/data/locker.go |
|
定位 |
internal/biz/location.go、internal/data/location.go、internal/plugin/location/ |
|
城市 |
internal/biz/city.go、internal/data/city.go、internal/plugin/city/ |
|
鉴权限流 |
internal/support/security/security.go |
|
日志监控 |
internal/support/observability/observability.go |
|
缓存 |
internal/data/cache/cache.go |
|
计费 |
internal/support/pricing/pricing.go |
|
状态机 |
internal/support/statemachine/order.go |
|
地理距离 |
internal/support/geo/distance.go |
|
服务注册 |
internal/server/http.go、internal/server/grpc.go |
|
依赖注入 |
cmd/helloworld/bootstrap/wire.go、wire_gen.go |
|
数据库结构 |
migrations/001_support_schema.sql 到 011_storage_coupon_config_redemption.sql |
10. 术语简表
|
术语 |
含义 |
|
DTO |
接口传输对象,主要由 protobuf message 生成。 |
|
DO |
领域对象,由 biz 层拥有,不带数据库或传输层标签。 |
|
PO |
持久化对象,由 data 层拥有,对应数据库字段或存储结构。 |
|
Usecase |
业务用例,编排规则和仓储接口完成一个用户目标。 |
|
Repo |
仓储接口/实现,负责领域对象的查询与保存。 |
|
Middleware |
在业务处理器外统一执行鉴权、日志、限流等横切逻辑。 |
|
P95 |
95% 请求耗时不超过的值,用于观察尾部延迟。 |
|
幂等 |
同一个请求或回调重复执行,最终结果仍保持一致。 |
|
分布式锁 |
在多个服务实例之间协调同一资源的唯一占用。 |
|
防御性复制 |
返回对象副本,避免外部通过共享指针修改内部数据。 |
结语
这个项目最值得学习的地方,是同一条业务链路中同时出现了接口契约、分层架构、并发控制、金额计算、状态机、缓存、数据库事务、安全和可观测性。建议先沿着“搜索寄存点 -> 费用预估 -> 锁柜下单 -> 支付回调 -> 异常换柜”阅读一次,再分别深入 security、cache、pricing 和 observability。这样 Go 语法会与真实业务问题建立对应关系,更容易理解和记忆。

345

被折叠的 条评论
为什么被折叠?



