v6: 完整图书平台 --- 全栈集成
一、版本概述
v6 在 v5 微服务基础上新增 user-service (用户认证)、Bleve 全文搜索 和前端界面,形成完整的前后端分离平台。
相比 v5 的核心变化:
- 用户服务:user-service(:8083),注册/登录/JWT 认证
- JWT 认证:golang-jwt 生成和验证 Token,中间件保护路由
- 全文搜索:Bleve 搜索引擎,支持中文分词(CJK)
- 前端界面:HTML + CSS + JS,图书管理/搜索/借阅/登录
- 完善 gRPC:borrow-service 真正调用 book-service 的 gRPC 接口
- bcrypt 密码:密码哈希存储,不存明文
项目结构:
v6/
├── backend/
│ ├── book-service/
│ │ ├── main.go
│ │ ├── model/book.go # GORM Book
│ │ ├── repository/
│ │ │ ├── db.go # MySQL
│ │ │ └── book_repo.go # CRUD + 库存操作
│ │ ├── cache/redis.go # Redis 缓存 + 分布式锁
│ │ ├── search/bleve.go # Bleve 全文搜索(新增)
│ │ ├── service/book_service.go
│ │ ├── handler/
│ │ │ ├── http_handler.go # HTTP API + 搜索路由
│ │ │ └── grpc_handler.go # gRPC 服务端
│ │ └── Dockerfile
│ ├── borrow-service/
│ │ ├── main.go # gRPC Client 注入
│ │ ├── model/borrow_record.go
│ │ ├── repository/
│ │ │ ├── db.go
│ │ │ └── borrow_repo.go
│ │ ├── handler/
│ │ │ ├── grpc_client.go # gRPC 客户端(完善实现)
│ │ │ └── http_handler.go
│ │ ├── service/borrow_service.go # BookStockClient 接口 + 补偿事务
│ │ └── Dockerfile
│ └── user-service/ # 新增服务
│ ├── main.go
│ ├── model/user.go # GORM User
│ ├── repository/
│ │ ├── db.go
│ │ └── user_repo.go
│ ├── service/user_service.go # JWT + bcrypt
│ ├── handler/http_handler.go
│ ├── middleware/auth.go # JWT 认证中间件
│ └── Dockerfile
├── frontend/
│ ├── index.html
│ ├── css/style.css
│ └── js/
│ ├── api.js # API 封装
│ └── main.js # 交互逻辑
├── proto/
│ ├── book.proto
│ └── book/
│ ├── book.pb.go
│ └── book_grpc.pb.go
└── docker-compose.yml
二、核心代码解读
user-service/service/user_service.go --- JWT 认证
go
func (s *userService) Login(username, password string) (*LoginResult, error) {
user, err := s.repo.GetByUsername(username)
if err != nil {
return nil, ErrUserNotFound
}
// bcrypt 校验密码
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
return nil, ErrInvalidPassword
}
// 生成 JWT Token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user_id": user.ID,
"username": user.Username,
"exp": time.Now().Add(24 * time.Hour).Unix(),
})
tokenString, _ := token.SignedString(jwtSecret)
return &LoginResult{Token: tokenString, UserID: user.ID}, nil
}
设计要点:
- bcrypt:自适应哈希,同一个密码每次生成的哈希值不同,防止彩虹表攻击
- JWT Claims:包含 user_id、username、exp(过期时间),客户端携带 Token 访问受保护资源
- HS256:对称签名,适合单服务端场景;生产环境可考虑 RS256(非对称)
user-service/middleware/auth.go --- 认证中间件
go
func AuthMiddleware(userSvc service.UserService) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
parts := strings.Split(authHeader, " ")
if len(parts) != 2 || parts[0] != "Bearer" {
http.Error(w, "Token格式错误", http.StatusUnauthorized)
return
}
userID, err := userSvc.VerifyToken(parts[1])
if err != nil {
http.Error(w, "无效的Token", http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), UserIDKey, userID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
设计要点:
- Bearer Token :标准 Authorization 头格式
Bearer <token> - Context 传递 :解析出的 userID 存入 context,后续 handler 可通过
GetUserID(ctx)获取 - 中间件模式:与 v3 的 Logger/Recover 一致,可灵活组合
book-service/search/bleve.go --- 全文搜索
go
func InitSearch() error {
indexPath := os.Getenv("SEARCH_INDEX_PATH")
if indexPath == "" {
execPath, _ := os.Getwd()
indexPath = filepath.Join(execPath, "search_index")
}
// 创建或打开索引
mapping := bleve.NewIndexMapping()
mapping.AddDocumentMapping("book", createBookMapping())
idx, _ := bleve.New(indexPath, mapping)
index = idx
}
func SearchBooks(keyword string) ([]model.Book, error) {
// 标题搜索(CJK分词) + 作者搜索(标准分词),取并集
titleQuery := bleve.NewMatchQuery(keyword)
titleQuery.SetField("title")
authorQuery := bleve.NewMatchQuery(keyword)
authorQuery.SetField("author")
disjunction := bleve.NewDisjunctionQuery(titleQuery, authorQuery)
// ...
}
设计要点:
- CJK 分词:中文日文韩文分词器,支持中文搜索
- DisjunctionQuery:标题或作者匹配任一即返回(OR 逻辑)
- 索引路径:支持环境变量配置,默认当前目录
borrow-service/handler/grpc_client.go --- 真正的 gRPC 调用
go
func (c *GRPCClient) DecreaseStock(ctx context.Context, bookID int64) (int, error) {
conn, err := grpc.NewClient(c.bookServiceAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return 0, err
}
defer conn.Close()
client := pb.NewBookServiceClient(conn)
resp, err := client.DecreaseStock(ctx, &pb.DecreaseStockRequest{BookId: bookID})
if err != nil {
return 0, err
}
return int(resp.Available), nil
}
对比 v5 :v5 的 gRPC 客户端在 main.go 启动时建立连接并复用;v6 使用 grpc.NewClient(替代已废弃的 grpc.Dial),每次请求创建连接(简化实现,生产环境应复用连接)
borrow-service/service/borrow_service.go --- 接口化 gRPC 客户端
go
type BookStockClient interface {
DecreaseStock(ctx context.Context, bookID int64) (int, error)
IncreaseStock(ctx context.Context, bookID int64) (int, error)
}
type borrowService struct {
repo borrowRepository // 接口而非具体类型
stockClient BookStockClient // 可 mock 的接口
}
设计要点:
- 接口抽象:BookStockClient 接口使 borrowService 不依赖具体 gRPC 实现,便于测试
- borrowRepository 接口:同样抽象化,测试时可 mock 整个数据层
frontend/js/api.js --- 前端 API 封装
javascript
const API_BASE = {
user: 'http://localhost:8083',
book: 'http://localhost:8081',
borrow: 'http://localhost:8082'
};
async function apiCall(service, method, path, body = null) {
const url = `${API_BASE[service]}${path}`;
const options = { method, headers: { 'Content-Type': 'application/json' } };
if (authToken) options.headers['Authorization'] = `Bearer ${authToken}`;
if (body) options.body = JSON.stringify(body);
const response = await fetch(url, options);
return await response.json();
}
设计要点:
- 统一 API 封装 :
apiCall函数处理认证头、请求体、错误处理 - 函数命名隔离 :
apiRegister、apiLogin等加api前缀,避免与 main.js 的事件处理函数重名
三、与 v5 的对比
| 特性 | v5 | v6 |
|---|---|---|
| 服务数量 | 2 (book+borrow) | 3 (+user) |
| 认证 | 无 | JWT + bcrypt |
| 搜索 | 无 | Bleve 全文搜索 |
| 前端 | 无 | HTML/CSS/JS |
| gRPC 客户端 | 功能完整 | 完善实现+接口化 |
| 密码存储 | 无 | bcrypt 哈希 |
| 接口可测试性 | 具体类型 | 接口化(mock友好) |
四、设计思想
| 思想 | 体现 |
|---|---|
| 认证授权 | JWT 无状态认证,服务端不保存 session |
| 接口隔离 | BookStockClient/borrowRepository 接口,依赖倒置 |
| 前后端分离 | 前端 SPA + 后端 RESTful API |
| 全文搜索 | 独立搜索引擎,与数据库解耦 |
| 渐进增强 | 每个版本只在前一版本基础上增加必要功能 |
五、版本演进总结
v1 单文件录入 → v2 分层CRUD → v3 HTTP服务 → v4 并发借阅 → v5 微服务 → v6 全栈平台
文件I/O JSON持久化 Service层 Mutex+Context MySQL+Redis JWT认证
纯函数测试 RWMutex 中间件 优雅关闭 gRPC通信 全文搜索
RESTful 补偿事务 分布式锁 前端界面
Docker部署 bcrypt密码
核心演进路线:
- v1→v2:从"能跑"到"可维护"(结构化+分层)
- v2→v3:从"本地工具"到"网络服务"(HTTP+Service层)
- v3→v4:从"单用户"到"多用户并发"(Mutex+Context+补偿事务)
- v4→v5:从"单体"到"分布式"(微服务+MySQL+Redis+gRPC)
- v5→v6:从"后端"到"全栈"(认证+搜索+前端+接口化)
每个版本都是在前一个版本的基础上最小增量地引入新概念,让学习者能清晰地看到每个架构决策的来龙去脉。