综合实战 - 企业级项目
第十六章:综合实战 - 企业级项目
16.1 项目概述
构建一个企业级用户权限+订单管理系统,综合运用前面所有知识点:
- 用户权限模块:RBAC 权限控制
- 订单模块:复杂业务逻辑、事务处理
- 技术栈:GORM + GORM Gen 混用
16.2 系统架构
enterprise-example/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── domain/ # 领域模型
│ │ ├── user.go
│ │ ├── role.go
│ │ ├── permission.go
│ │ └── order.go
│ ├── repository/ # 数据访问层
│ │ ├── user_repo.go
│ │ └── order_repo.go
│ ├── service/ # 业务逻辑层
│ │ ├── user_service.go
│ │ └── order_service.go
│ ├── api/ # 接口层
│ │ ├── handler/
│ │ └── middleware/
│ └── infrastructure/ # 基础设施
│ ├── database/
│ ├── cache/
│ └── mq/
├── pkg/ # 公共库
│ ├── errors/
│ ├── utils/
│ └── logger/
├── generate/ # 代码生成
│ └── gen.go
├── dao/ # GORM Gen 生成
│ ├── model/
│ └── query/
└── configs/ # 配置文件
└── config.yaml
16.3 领域模型设计
用户领域
package domain
// User 用户领域模型
type User struct {
ID uint64
Username string
Password string
Email string
Phone string
Status int8 // 1-正常 2-禁用
Roles []Role // 角色
}
func (u *User) HasPermission(permCode string) bool {
for _, role := range u.Roles {
for _, perm := range role.Permissions {
if perm.Code == permCode {
return true
}
}
}
return false
}
// Role 角色
type Role struct {
ID uint64
Name string
Code string
Permissions []Permission
}
// Permission 权限
type Permission struct {
ID uint64
Name string
Code string
Type int8 // 1-菜单 2-按钮 3-接口
ParentID uint64
}
订单领域
package domain
// Order 订单领域模型
type Order struct {
ID uint64
OrderNo string // 订单号
UserID uint64
Status int8 // 1-待支付 2-已支付 3-已发货 4-已完成 5-已取消
TotalAmount float64 // 总金额
Items []OrderItem
Address Address
CreatedAt time.Time
PaidAt *time.Time
}
func (o *Order) CanCancel() bool {
return o.Status == 1 // 只有待支付可以取消
}
func (o *Order) Pay() error {
if o.Status != 1 {
return errors.New("订单状态不允许支付")
}
now := time.Now()
o.Status = 2
o.PaidAt = &now
return nil
}
// OrderItem 订单项
type OrderItem struct {
ID uint64
OrderID uint64
ProductID uint64
ProductName string
Quantity int
UnitPrice float64
TotalPrice float64
}
// Address 地址
type Address struct {
Province string
City string
District string
Detail string
Contact string
Phone string
}
16.4 GORM Gen 生成配置
// generate/gen.go
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gen"
"gorm.io/gorm"
)
func main() {
g := gen.NewGenerator(gen.Config{
OutPath: "./dao/query",
ModelPkgPath: "./dao/model",
Mode: gen.WithDefaultQuery | gen.WithQueryInterface,
FieldNullable: true,
FieldCoverable: true,
FieldWithTypeTag: true,
})
dsn := "user:password@tcp(127.0.0.1:3306)/enterprise?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
g.UseDB(db)
// 生成所有表
g.ApplyBasic(g.GenerateAllTable()...)
// 为用户表添加自定义方法
user := g.GenerateModel("sys_user",
gen.FieldIgnore("password"),
)
g.ApplyInterface(func(UserMethod) {}, user)
g.Execute()
}
type UserMethod interface {
FindByUsername(username string) (*gen.T, error)
FindByPhone(phone string) (*gen.T, error)
UpdatePassword(userID uint64, password string) error
}
16.5 Repository 层实现
基础 Repository
package repository
import (
"context"
"gorm.io/gorm"
)
type BaseRepository struct {
db *gorm.DB
}
func NewBaseRepository(db *gorm.DB) *BaseRepository {
return &BaseRepository{db: db}
}
func (r *BaseRepository) DB(ctx context.Context) *gorm.DB {
return r.db.WithContext(ctx)
}
func (r *BaseRepository) Transaction(ctx context.Context, fn func(*gorm.DB) error) error {
return r.db.WithContext(ctx).Transaction(fn)
}
User Repository(使用 Gen)
package repository
import (
"context"
"myapp/dao/model"
"myapp/dao/query"
"myapp/internal/domain"
"gorm.io/gen"
)
type UserRepository struct {
*BaseRepository
q *query.Query
}
func NewUserRepository(db *gorm.DB) *UserRepository {
return &UserRepository{
BaseRepository: NewBaseRepository(db),
q: query.Use(db),
}
}
// Create 创建用户
func (r *UserRepository) Create(ctx context.Context, user *domain.User) error {
u := &model.SysUser{
Username: user.Username,
Password: user.Password,
Email: user.Email,
Phone: user.Phone,
Status: user.Status,
}
return r.q.SysUser.WithContext(ctx).Create(u)
}
// FindByID 根据ID查询
func (r *UserRepository) FindByID(ctx context.Context, id uint64) (*domain.User, error) {
u := r.q.SysUser
user, err := u.WithContext(ctx).Where(u.ID.Eq(id)).First()
if err != nil {
return nil, err
}
return r.toDomain(user), nil
}
// FindByUsername 根据用户名查询
func (r *UserRepository) FindByUsername(ctx context.Context, username string) (*domain.User, error) {
u := r.q.SysUser
user, err := u.WithContext(ctx).Where(u.Username.Eq(username)).First()
if err != nil {
return nil, err
}
return r.toDomain(user), nil
}
// GetUserWithRoles 获取用户及其角色
func (r *UserRepository) GetUserWithRoles(ctx context.Context, id uint64) (*domain.User, error) {
// 使用原生 GORM 进行复杂关联查询
var user domain.User
err := r.DB(ctx).Table("sys_user").
Select("sys_user.*").
Where("sys_user.id = ?", id).
First(&user).Error
if err != nil {
return nil, err
}
// 查询角色
var roles []domain.Role
err = r.DB(ctx).Table("sys_role r").
Select("r.*").
Joins("JOIN sys_user_role ur ON r.id = ur.role_id").
Where("ur.user_id = ?", id).
Find(&roles).Error
if err != nil {
return nil, err
}
user.Roles = roles
return &user, nil
}
// UpdateStatus 更新状态(Gen)
func (r *UserRepository) UpdateStatus(ctx context.Context, id uint64, status int8) error {
u := r.q.SysUser
_, err := u.WithContext(ctx).Where(u.ID.Eq(id)).Update(u.Status, status)
return err
}
// List 用户列表(Gen)
func (r *UserRepository) List(ctx context.Context, page, pageSize int) ([]*domain.User, int64, error) {
u := r.q.SysUser
// 条件构建
q := u.WithContext(ctx)
// 统计
total, err := q.Count()
if err != nil {
return nil, 0, err
}
// 查询
users, err := q.Order(u.ID.Desc()).FindByPage((page-1)*pageSize, pageSize)
if err != nil {
return nil, 0, err
}
// 转换
var result []*domain.User
for _, user := range users {
result = append(result, r.toDomain(user))
}
return result, total, nil
}
func (r *UserRepository) toDomain(u *model.SysUser) *domain.User {
return &domain.User{
ID: u.ID,
Username: u.Username,
Email: u.Email,
Phone: u.Phone,
Status: u.Status,
}
}
Order Repository(GORM + 原生 SQL 混用)
package repository
import (
"context"
"fmt"
"myapp/internal/domain"
"gorm.io/gorm"
)
type OrderRepository struct {
*BaseRepository
}
func NewOrderRepository(db *gorm.DB) *OrderRepository {
return &OrderRepository{BaseRepository: NewBaseRepository(db)}
}
// Create 创建订单(事务)
func (r *OrderRepository) Create(ctx context.Context, order *domain.Order) error {
return r.Transaction(ctx, func(tx *gorm.DB) error {
// 1. 插入订单主表
if err := tx.Table("order").Create(map[string]interface{}{
"order_no": order.OrderNo,
"user_id": order.UserID,
"status": order.Status,
"total_amount": order.TotalAmount,
"created_at": order.CreatedAt,
}).Error; err != nil {
return err
}
// 2. 插入订单项
for _, item := range order.Items {
if err := tx.Table("order_item").Create(map[string]interface{}{
"order_id": order.ID,
"product_id": item.ProductID,
"product_name": item.ProductName,
"quantity": item.Quantity,
"unit_price": item.UnitPrice,
"total_price": item.TotalPrice,
}).Error; err != nil {
return err
}
}
// 3. 插入地址
if err := tx.Table("order_address").Create(map[string]interface{}{
"order_id": order.ID,
"province": order.Address.Province,
"city": order.Address.City,
"district": order.Address.District,
"detail": order.Address.Detail,
"contact": order.Address.Contact,
"phone": order.Address.Phone,
}).Error; err != nil {
return err
}
return nil
})
}
// FindByOrderNo 根据订单号查询
func (r *OrderRepository) FindByOrderNo(ctx context.Context, orderNo string) (*domain.Order, error) {
var order domain.Order
err := r.DB(ctx).Table("order").
Where("order_no = ?", orderNo).
First(&order).Error
if err != nil {
return nil, err
}
// 查询订单项
err = r.DB(ctx).Table("order_item").
Where("order_id = ?", order.ID).
Find(&order.Items).Error
if err != nil {
return nil, err
}
return &order, nil
}
// GetOrderStatistics 订单统计(原生 SQL)
func (r *OrderRepository) GetOrderStatistics(ctx context.Context, startDate, endDate string) (*OrderStats, error) {
var stats OrderStats
err := r.DB(ctx).Raw(`
SELECT
COUNT(*) as total_orders,
COUNT(CASE WHEN status = 2 THEN 1 END) as paid_orders,
SUM(total_amount) as total_amount,
AVG(total_amount) as avg_amount
FROM order
WHERE created_at BETWEEN ? AND ?
`, startDate, endDate).Scan(&stats).Error
return &stats, err
}
type OrderStats struct {
TotalOrders int64 `json:"total_orders"`
PaidOrders int64 `json:"paid_orders"`
TotalAmount float64 `json:"total_amount"`
AvgAmount float64 `json:"avg_amount"`
}
// UpdateStatus 更新订单状态(使用乐观锁)
func (r *OrderRepository) UpdateStatus(ctx context.Context, orderID uint64, fromStatus, toStatus int8) error {
result := r.DB(ctx).Table("order").
Where("id = ? AND status = ?", orderID, fromStatus).
Update("status", toStatus)
if result.RowsAffected == 0 {
return fmt.Errorf("订单状态已变更或订单不存在")
}
return result.Error
}
16.6 Service 层
Order Service
package service
import (
"context"
"fmt"
"myapp/internal/domain"
"myapp/internal/repository"
"myapp/pkg/snowflake"
"time"
)
type OrderService struct {
orderRepo *repository.OrderRepository
userRepo *repository.UserRepository
cache Cache
}
func NewOrderService(orderRepo *repository.OrderRepository, userRepo *repository.UserRepository) *OrderService {
return &OrderService{
orderRepo: orderRepo,
userRepo: userRepo,
}
}
// CreateOrder 创建订单
func (s *OrderService) CreateOrder(ctx context.Context, req CreateOrderRequest) (*domain.Order, error) {
// 1. 验证用户
user, err := s.userRepo.FindByID(ctx, req.UserID)
if err != nil {
return nil, fmt.Errorf("用户不存在: %w", err)
}
if user.Status != 1 {
return nil, fmt.Errorf("用户状态异常")
}
// 2. 构建订单
order := &domain.Order{
OrderNo: snowflake.GenerateID().String(),
UserID: req.UserID,
Status: 1, // 待支付
TotalAmount: 0,
CreatedAt: time.Now(),
Address: req.Address,
}
// 3. 构建订单项并计算总价
for _, itemReq := range req.Items {
item := domain.OrderItem{
ProductID: itemReq.ProductID,
ProductName: itemReq.ProductName,
Quantity: itemReq.Quantity,
UnitPrice: itemReq.UnitPrice,
TotalPrice: float64(itemReq.Quantity) * itemReq.UnitPrice,
}
order.Items = append(order.Items, item)
order.TotalAmount += item.TotalPrice
}
// 4. 应用优惠
if req.CouponID > 0 {
discount, err := s.applyCoupon(ctx, req.CouponID, order.TotalAmount)
if err != nil {
return nil, err
}
order.TotalAmount -= discount
}
// 5. 保存订单
if err := s.orderRepo.Create(ctx, order); err != nil {
return nil, fmt.Errorf("创建订单失败: %w", err)
}
// 6. 发送延迟消息(15分钟后检查支付状态)
s.sendDelayCheck(order.OrderNo, 15*time.Minute)
return order, nil
}
// PayOrder 支付订单
func (s *OrderService) PayOrder(ctx context.Context, orderNo string, userID uint64) error {
// 1. 查询订单
order, err := s.orderRepo.FindByOrderNo(ctx, orderNo)
if err != nil {
return fmt.Errorf("订单不存在: %w", err)
}
// 2. 验证所有权
if order.UserID != userID {
return fmt.Errorf("无权操作此订单")
}
// 3. 验证状态
if !order.CanCancel() { // 实际上应该是 CanPay()
return fmt.Errorf("订单状态不允许支付")
}
// 4. 调用支付接口(略)
// ...
// 5. 更新订单状态(使用乐观锁)
if err := s.orderRepo.UpdateStatus(ctx, order.ID, 1, 2); err != nil {
return fmt.Errorf("更新订单状态失败: %w", err)
}
// 6. 发送支付成功通知
s.sendPaySuccessNotify(order)
return nil
}
// CancelOrder 取消订单
func (s *OrderService) CancelOrder(ctx context.Context, orderNo string, userID uint64) error {
order, err := s.orderRepo.FindByOrderNo(ctx, orderNo)
if err != nil {
return err
}
if order.UserID != userID {
return fmt.Errorf("无权操作")
}
if !order.CanCancel() {
return fmt.Errorf("订单状态不允许取消")
}
// 回滚库存(略)
return s.orderRepo.UpdateStatus(ctx, order.ID, 1, 5) // 5-已取消
}
type CreateOrderRequest struct {
UserID uint64
Items []OrderItemRequest
Address domain.Address
CouponID uint64
}
type OrderItemRequest struct {
ProductID uint64
ProductName string
Quantity int
UnitPrice float64
}
16.7 权限控制中间件
package middleware
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"myapp/internal/domain"
"myapp/pkg/jwt"
)
// Auth 认证中间件
func Auth() gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "未登录"})
return
}
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "认证格式错误"})
return
}
claims, err := jwt.ParseToken(parts[1])
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "无效的token"})
return
}
c.Set("userID", claims.UserID)
c.Set("username", claims.Username)
c.Next()
}
}
// Permission 权限检查中间件
func Permission(permCode string) gin.HandlerFunc {
return func(c *gin.Context) {
userID, _ := c.Get("userID")
// 从缓存或数据库获取用户权限
userService := c.MustGet("userService").(*service.UserService)
user, err := userService.GetUserWithRoles(c.Request.Context(), userID.(uint64))
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "获取用户信息失败"})
return
}
if !user.HasPermission(permCode) {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "无权限操作"})
return
}
c.Next()
}
}
16.8 性能优化
// 使用缓存优化热点数据
type CachedUserRepository struct {
*repository.UserRepository
cache cache.Cache
}
func (r *CachedUserRepository) FindByID(ctx context.Context, id uint64) (*domain.User, error) {
key := fmt.Sprintf("user:%d", id)
// 先查缓存
if cached, err := r.cache.Get(ctx, key); err == nil {
return cached.(*domain.User), nil
}
// 查数据库
user, err := r.UserRepository.FindByID(ctx, id)
if err != nil {
return nil, err
}
// 写入缓存
r.cache.Set(ctx, key, user, 5*time.Minute)
return user, nil
}
// 使用本地缓存 + Redis 二级缓存
type MultiLevelCache struct {
local *ristretto.Cache
redis *redis.Client
}
16.9 完整启动流程
// cmd/server/main.go
package main
import (
"context"
"log"
"myapp/configs"
"myapp/dao/query"
"myapp/internal/api"
"myapp/internal/repository"
"myapp/internal/service"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
func main() {
cfg := configs.Load()
// 1. 连接数据库
db, err := gorm.Open(mysql.Open(cfg.Database.DSN), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
log.Fatal("数据库连接失败:", err)
}
// 2. 设置 GORM Gen
query.SetDefault(db)
// 3. 初始化 Repository
userRepo := repository.NewUserRepository(db)
orderRepo := repository.NewOrderRepository(db)
// 4. 初始化 Service
userService := service.NewUserService(userRepo)
orderService := service.NewOrderService(orderRepo, userRepo)
// 5. 初始化 Handler
handler := api.NewHandler(userService, orderService)
// 6. 启动服务
router := api.NewRouter(handler)
log.Println("Server starting on :8080")
if err := router.Run(":8080"); err != nil {
log.Fatal("Server failed:", err)
}
}
16.10 小结
本章展示了一个企业级项目的完整架构,综合运用了:
- GORM Gen:类型安全的日常 CRUD
- 原生 GORM:复杂查询和事务
- 领域驱动设计:清晰的业务边界
- 性能优化:缓存、连接池
- 权限控制:RBAC 模型
本文代码地址:https://github.com/LittleMoreInteresting/gorm_study
欢迎关注公众号,一起学习进步!