go语言项目--实例化(图书管理)--006

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 函数处理认证头、请求体、错误处理
  • 函数命名隔离apiRegisterapiLogin 等加 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密码

核心演进路线

  1. v1→v2:从"能跑"到"可维护"(结构化+分层)
  2. v2→v3:从"本地工具"到"网络服务"(HTTP+Service层)
  3. v3→v4:从"单用户"到"多用户并发"(Mutex+Context+补偿事务)
  4. v4→v5:从"单体"到"分布式"(微服务+MySQL+Redis+gRPC)
  5. v5→v6:从"后端"到"全栈"(认证+搜索+前端+接口化)

每个版本都是在前一个版本的基础上最小增量地引入新概念,让学习者能清晰地看到每个架构决策的来龙去脉。

相关推荐
Rotion_深1 小时前
C# 值类型与引用类型 详解
开发语言·jvm·c#
kuro-shiro1 小时前
SpringBoot 启动流程
java·spring boot·后端
偏爱自由 !1 小时前
8. 泛型程序设计
java·开发语言·windows
冰暮流星1 小时前
python之flask框架讲解-准备
开发语言·python·flask
ch.ju1 小时前
Java Programming Chapter 4——Class loading
java·开发语言
Huangjin007_1 小时前
【C++11篇(二)】右值引用、移动语义保姆级讲解!
开发语言·c++
孟浩浩3 小时前
JAVA SpringAI+阿里云百炼应用开发
java·开发语言·阿里云
碧蓝的水壶4 小时前
数据转换过程
java·开发语言·windows
2501_947575809 小时前
计算机毕业设计之jsp开山车行二手车交易系统
java·开发语言·hadoop·python·信息可视化·django·课程设计