项目整体架构分层结构:
- API层:定义了前端/后端接口的请求和响应结构
- 控制器层(Controller):处理请求,调用服务层的函数
- 服务层(Service):定义了业务接口和依赖注入
- 逻辑层(Logic):实现具体的业务逻辑
- 数据访问层(DAO):负责数据库操作
- 模型层(Model):定义了数据结构和业务输入输出模型
项目分为前台和后台两大部分:
-
前台:用户注册、登录、浏览商品、加入购物车、下单等
-
后台:管理员登录、商品管理、订单管理、数据统计等
假设有一个用户登录请求,执行流程大致为:
- api层:定义登录接口及数据结构。
- controller层:接收到请求后,解析参数并调用登录对应的 service 方法。
- service层:负责调用 logic 层执行密码加密、用户校验,同时使用 internal 层的 DAO 进行数据库查询。
- logic层:实现具体的加密与验证逻辑。
- internal层:返回查询结果,并由各层逐层汇总最终返回前端。
用户完整购物流程
以用户从注册到购买商品为例,整个系统的工作流程:
1. 用户注册
- 用户提交注册信息,请求发送到 /frontend/register 接口
- API层的 api/frontend/user.go 定义了请求参数结构
- 控制器层 internal/controller/user.go 中的 Register 方法处理请求
- 控制器将请求转发给服务层 service.User().Register()
- 服务层通过依赖注入调用逻辑层 internal/logic/user/user.go 中的 Register 方法
- 逻辑层进行密码加密,然后调用DAO层将数据存入数据库
- 返回注册成功的响应
关键代码:
密码处理:使用随机盐值进行加密 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. 用户请求商品详情,发送到 /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
}