深入解析go-zero-looklook项目中的用户服务架构与实现
用户服务架构概述
go-zero-looklook项目中的用户服务采用了清晰的分层架构设计,将业务逻辑合理分布在API层和RPC层。这种架构设计充分体现了微服务的思想,使得系统各组件职责分明,便于维护和扩展。
架构图解
用户服务的整体架构如下图所示:
架构主要分为两层:
- API层:负责处理HTTP请求,对外提供RESTful接口
- RPC层:实现核心业务逻辑,为API层提供服务
服务依赖关系
在go-zero-looklook项目中,usercenter-api(用户中心API服务)依赖于usercenter-rpc(用户中心RPC服务)。这种设计使得业务逻辑集中在RPC服务中,API层只需关注接口适配和请求转发。
用户注册功能实现详解
API层实现
1. 接口定义
在usercenter.api文件中定义用户注册接口:
@server(
prefix: usercenter/v1
group: user
)
service usercenter {
@doc "register"
@handler register
post /user/register (RegisterReq) returns (RegisterResp)
...
}
2. 请求响应结构
在user.api文件中定义注册请求和响应结构:
type (
RegisterReq {
Mobile string `json:"mobile"`
Password string `json:"password"`
}
RegisterResp {
AccessToken string `json:"accessToken"`
AccessExpire int64 `json:"accessExpire"`
RefreshAfter int64 `json:"refreshAfter"`
}
)
3. 代码生成与逻辑实现
使用goctl工具生成代码后,在register.go文件中实现注册逻辑:
func (l *RegisterLogic) Register(req *types.RegisterReq) (*types.RegisterResp, error) {
// 调用RPC服务完成注册
resp, err := l.svcCtx.UserRpc.Register(l.ctx, &usercenter.RegisterReq{
Mobile: req.Mobile,
Password: req.Password,
})
if err != nil {
return nil, err
}
// 返回注册结果
return &types.RegisterResp{
AccessToken: resp.AccessToken,
AccessExpire: resp.AccessExpire,
RefreshAfter: resp.RefreshAfter,
}, nil
}
RPC层实现
1. Protobuf定义
在usercenter.proto文件中定义RPC服务和方法:
message RegisterReq {
string mobile = 1;
string nickname = 2;
string password = 3;
string authKey = 4;
string authType = 5;
}
message RegisterResp {
string accessToken = 1;
int64 accessExpire = 2;
int64 refreshAfter = 3;
}
service usercenter {
rpc register(RegisterReq) returns(RegisterResp);
...
}
2. 注册逻辑实现
注册逻辑主要处理以下业务:
- 检查联系方式是否已注册
- 创建用户记录
- 创建用户认证记录
- 生成访问令牌
func (l *RegisterLogic) Register(in *usercenter.RegisterReq) (*usercenter.RegisterResp, error) {
// 1. 检查用户是否已存在
user, err := l.svcCtx.UserModel.FindOneByMobile(l.ctx, in.Mobile)
if err != nil && err != model.ErrNotFound {
return nil, err
}
if user != nil {
return nil, errors.New("用户已存在")
}
var userId int64
// 2. 使用事务处理用户注册
if err := l.svcCtx.UserModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 创建用户记录
user := new(model.User)
user.Mobile = in.Mobile
user.Nickname = tool.Krand(8, tool.KC_RAND_KIND_ALL)
if len(in.Password) > 0 {
user.Password = tool.Md5ByString(in.Password)
}
// 插入用户记录
insertResult, err := l.svcCtx.UserModel.Insert(ctx, session, user)
if err != nil {
return err
}
// 获取用户ID
userId, err = insertResult.LastInsertId()
if err != nil {
return err
}
// 创建用户认证记录
userAuth := new(model.UserAuth)
userAuth.UserId = userId
userAuth.AuthKey = in.AuthKey
userAuth.AuthType = in.AuthType
if _, err := l.svcCtx.UserAuthModel.Insert(ctx, session, userAuth); err != nil {
return err
}
return nil
}); err != nil {
return nil, err
}
// 3. 生成访问令牌
tokenResp, err := NewGenerateTokenLogic(l.ctx, l.svcCtx).GenerateToken(&usercenter.GenerateTokenReq{
UserId: userId,
})
if err != nil {
return nil, err
}
return &usercenter.RegisterResp{
AccessToken: tokenResp.AccessToken,
AccessExpire: tokenResp.AccessExpire,
RefreshAfter: tokenResp.RefreshAfter,
}, nil
}
3. 事务处理
注册过程中涉及用户表和用户认证表的操作,需要使用事务保证数据一致性。项目中通过在Model层暴露Trans方法,使得业务逻辑中可以方便地使用事务:
func (m *defaultUserModel) Trans(ctx context.Context, fn func(ctx context.Context, session sqlx.Session) error) error {
return m.conn.TransactCtx(ctx, fn)
}
获取登录用户ID
在业务逻辑中,经常需要获取当前登录用户的ID。项目中提供了统一的方法从上下文中获取用户ID:
func GetUidFromCtx(ctx context.Context) int64 {
var uid int64
if jsonUid, ok := ctx.Value(CtxKeyJwtUserId).(json.Number); ok {
if int64Uid, err := jsonUid.Int64(); err == nil {
uid = int64Uid
} else {
logx.WithContext(ctx).Errorf("GetUidFromCtx err : %+v", err)
}
}
return uid
}
使用时只需调用:
userId := ctxdata.GetUidFromCtx(l.ctx)
设计亮点
- 分层清晰:API层和RPC层职责分明,API层处理HTTP协议相关逻辑,RPC层专注业务实现
- 事务封装:将事务操作封装在Model层,业务逻辑中可方便使用
- 统一认证:提供统一的用户ID获取方式,简化业务开发
- 灵活扩展:支持多种注册方式(联系方式、小程序等),通过authType区分
通过这种架构设计,go-zero-looklook项目的用户服务实现了高内聚、低耦合,为其他业务模块提供了清晰、稳定的用户管理能力。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考