以用户从注册到购买商品为例阐述整个goframe电商的工作流程:

143 阅读5分钟

项目整体架构分层结构:

  1. API层:定义了前端/后端接口的请求和响应结构
  2. 控制器层(Controller):处理请求,调用服务层的函数
  3. 服务层(Service):定义了业务接口和依赖注入
  4. 逻辑层(Logic):实现具体的业务逻辑
  5. 数据访问层(DAO):负责数据库操作
  6. 模型层(Model):定义了数据结构和业务输入输出模型

49043a74-808b-49bb-8184-fe2cf9c90712.png 项目分为前台和后台两大部分:

  • 前台:用户注册、登录、浏览商品、加入购物车、下单等

  • 后台:管理员登录、商品管理、订单管理、数据统计等

假设有一个用户登录请求,执行流程大致为:
  1. api层:定义登录接口及数据结构。
  2. controller层:接收到请求后,解析参数并调用登录对应的 service 方法。
  3. service层:负责调用 logic 层执行密码加密、用户校验,同时使用 internal 层的 DAO 进行数据库查询。
  4. logic层:实现具体的加密与验证逻辑。
  5. internal层:返回查询结果,并由各层逐层汇总最终返回前端。

用户完整购物流程

以用户从注册到购买商品为例,整个系统的工作流程:

1. 用户注册

  1. 用户提交注册信息,请求发送到 /frontend/register 接口
  2. API层的 api/frontend/user.go 定义了请求参数结构
  3. 控制器层 internal/controller/user.go 中的 Register 方法处理请求
  4. 控制器将请求转发给服务层 service.User().Register()
  5. 服务层通过依赖注入调用逻辑层 internal/logic/user/user.go 中的 Register 方法
  6. 逻辑层进行密码加密,然后调用DAO层将数据存入数据库
  7. 返回注册成功的响应

关键代码:

密码处理:使用随机盐值进行加密 UserSalt := grand.S(10)

数据存储:dao.UserInfo.Ctx(ctx).Data(in).InsertAndGetId()

2. 用户登录

流程:

  • 用户提交登录表单,请求发送到 /frontend/login 接口

  • 系统使用 gtoken 进行认证(在 internal/cmd/loginAuth.go 中定义)

loginFuncFrontend 方法验证用户名和密码:

  • 从数据库读取用户信息

  • 验证密码是否匹配

  • 登录成功后,loginAfterFuncFrontend 方法生成 token

  • 返回登录成功响应,包含 token 和用户基础信息

认证机制:

  • 使用 gtoken 生成 token

  • token 包含用户唯一标识

  • 后续请求在中间件中验证 token

3. 浏览商品

流程:

  • 用户请求商品列表,发送到 /frontend/goods/list 接口
  1. 控制器处理请求,调用服务层
  • 逻辑层查询数据库,获取商品信息

  • 返回商品列表信息

商品详情:1. 用户请求商品详情,发送到 /frontend/goods/detail 接口

  • 系统返回包含商品信息、规格选项和评论的详细信息

4. 加入购物车

流程:

  • 用户选择商品规格和数量,请求发送到 /frontend/add/cart 接口

  • 请求中包含 goods_options_id 和 count

  • 控制器处理请求,调用购物车服务

  • 逻辑层将商品加入购物车表

  • 返回成功响应

购物车操作:

  • 查看购物车:/frontend/cart/list

  • 删除购物车项:/frontend/delete/cart

5. 下单支付

流程:

  • 用户提交订单,请求发送到 /frontend/add/order 接口

  • 请求包含收货信息、商品信息、价格信息等

  • 控制器 internal/controller/order.go 中的 Add 方法处理请求

  • 调用订单服务 service.Order().Add()

  • 逻辑层执行订单创建逻辑:

  • 创建主订单

  • 创建订单商品关联

  • 扣减库存(调用库存服务)

  • 可能有优惠券处理逻辑

  • 返回订单创建成功的响应

订单结构:

  • 主订单表:包含订单总金额、收货信息等

  • 订单商品表:包含订单中的各个商品信息

6. 订单评价

流程:

  • 用户对收到的商品进行评价,请求发送到相关评价接口

  • 系统保存评价信息

  • 后续用户浏览商品时可以看到这些评价

系统的核心技术组件

  • 认证授权:使用 gtoken 实现用户认证和授权

  • 中间件:处理跨域、上下文设置、响应格式化等通用逻辑

  • 安全处理:密码加密存储,使用随机盐值

  • 数据库交互:使用 GoFrame 的 ORM 操作数据库

  • 缓存:可以使用 Redis 或内存缓存

数据流转示

1.请求层 (API定义) 请求结构在 api/frontend/order.go 中定义:

     type AddOrderReq struct {
    g.Meta `path:"/add/order" method:"post" tags:"前台订单" summary:"创建订单"`
    //主订单维度
    Price            int    `json:"price"            description:"订单金额 单位分"`
    CouponPrice      int    `json:"coupon_price"      description:"优惠券金额 单位分"`
    ActualPrice      int    `json:"actual_price"      description:"实际支付金额 单位分"`
    ConsigneeName    string `json:"consignee_name"    description:"收货人姓名"`
    ConsigneePhone   string `json:"consignee_phone"   description:"收货人手机号"`
    ConsigneeAddress string `json:"consignee_address" description:"收货人详细地址"`
    Remark           string `json:"remark"           description:"备注"`
    //商品订单维度
    OrderAddGoodsInfos []*OrderAddGoodsInfo `json:"order_add_goods_infos"`
}

2.控制器层 (Controller) 控制器在 internal/controller/order.go 中实现:

    orderAddInput := model.OrderAddInput{}
    //注意:这里要用scan 而不是struct
    if err = gconv.Scan(req, &orderAddInput); err != nil {
        return nil, err
    }

    addRes, err := service.Order().Add(ctx, orderAddInput)
    if err != nil {
        return nil, err
    }

    return &frontend.AddOrderRes{
        Id: addRes.Id,
    }, err
}

3.服务层 (Service) 服务层接口在 internal/service/order.go 中定义,通过依赖注入调用逻辑层。

4.逻辑层 (Logic) 逻辑层在 internal/logic/order/order.go 中实现了订单创建的核心业务逻辑:

    in.UserId = gconv.Uint(ctx.Value(consts.CtxUserId))
    in.Number = utility.GetOrderNum()
    out = &model.OrderAddOutput{}
    //官方建议的事务闭包处理
    err = g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
        //生成主订单
        lastInsertId, err := dao.OrderInfo.Ctx(ctx).InsertAndGetId(in)
        if err != nil {
            return err
        }
        //生成商品订单
        for _, info := range in.OrderAddGoodsInfos {
            info.OrderId = gconv.Int(lastInsertId)
            _, err := dao.OrderGoodsInfo.Ctx(ctx).Insert(info)
            if err != nil {
                return err
            }
        }
        //更新商品销量和库存,todo 后期接入消息
        for _, info := range in.OrderAddGoodsInfos {
            //商品增加销量
            _, err := dao.GoodsInfo.Ctx(ctx).WherePri(info.GoodsId).Increment(dao.GoodsInfo.Columns().Sale, info.Count)
            if err != nil {
                return err
            }
            //商品减少库存
            _, err2 := dao.GoodsInfo.Ctx(ctx).WherePri(info.GoodsId).Decrement(dao.GoodsInfo.Columns().Stock, info.Count)
            if err2 != nil {
                return err
            }
            //商品规格减少库存
            _, err3 := dao.GoodsOptionsInfo.Ctx(ctx).WherePri(info.GoodsOptionsId).Decrement(dao.GoodsOptionsInfo.Columns().Stock, info.Count)
            if err3 != nil {
                return err
            }
        }
        out.Id = uint(lastInsertId)
        return nil
    })
    if err != nil {
        return nil, err
    }
    return
}