用户服务设计

用户服务

(只是进行一个学习笔记的记录)

身份管理

  • 身份认证:身份证,邮箱,用户名,密码,手机号
  • 授权:服务端确认了用户的身份,但是用户无法直接与服务端进行交互,实际情况会委派给客户端进行一定的数据资源操作。授权的结果一般会是 cookie ,session,token .
  • 鉴权:对上述确认过的信息和凭证进行解析和确认
  • 权限的控制:对可执行的操作进行组合配置,成为一个权限的列表,根据执行者的权限,允许或者禁止。

Hertz Middleware

用户登录,注册

frontend 前端文件夹中进行 登录路由 的编写,在 main.go 文件夹中,添加以下内容加载前端,以及登录逻辑。
main.go:

func main() {

	_ = godotenv.Load()

	// init dal
	// dal.Init()
	rpc.InitClient()
	address := conf.GetConf().Hertz.Address
	h := server.New(server.WithHostPorts(address))

	registerMiddleware(h)

	// add a ping route to test
	h.GET("/ping",func(c context.Context, ctx *app.RequestContext) {
		ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
	})

	router.GeneratedRegister(h)
	h.LoadHTMLGlob("template/*")
	h.Static("/static", "./")

	h.GET("/about",func(c context.Context, ctx *app.RequestContext) {
		ctx.HTML(consts.StatusOK, "about", utils.H{"title": "About"})
	})

	h.GET("/sign-in", func(c context.Context, ctx *app.RequestContext) {
		data := utils.H{
			"Title": "Sign In",
			"Next":  ctx.Query("next"),

		}
		ctx.HTML(consts.StatusOK, "sign-in", data)
	})

	h.GET("/sign-up", func(c context.Context, ctx *app.RequestContext) {
		ctx.HTML(consts.StatusOK, "sign-up", utils.H{"title": "Sign Up"})
	})

	h.Spin()
}

其他的可以先不用管,就看/sign-in 部分:

//注册 sign-in http路由, 执行函数是后面的
//定义了一个 回调函数 作为处理逻辑
//c context.Context用于控制超时、取消等上下文管理。
	h.GET("/sign-in", func(c context.Context, ctx *app.RequestContext) {
		data := utils.H{
			"Title": "Sign In",
			"Next":  ctx.Query("next"),
		}
		ctx.HTML(consts.StatusOK, "sign-in", data)
	})
  • ctx *app.RequestContext:Kitex 的 RequestContext,用于访问请求相关信息(如查询参数、请求体)和返回响应。
  • “Title”: “Sign In”:设置模板变量 Title 为 “Sign In”,用于页面显示
  • ctx.Query(“next”) 用于获取 URL 查询参数 next,即用户访问 /sign-in?next=/dashboard 时,Next 变量会被赋值为 “/dashboard”
  • Next 可能用于登录成功后的跳转,即登录完成后,重定向到 next 指定的页面
ctx.HTML(consts.StatusOK, "sign-in", data)
  • ctx.HTML() 方法用于渲染 HTML 页面
  • “sign-in”:模板文件名,通常指 sign-in.html(这里省掉了,是因为在 html 文件中进行了编辑,改动)
  • data:传递给模板的渲染数据,在 sign-in.html 里可以使用 Title 和 Next。

前端登录html
这里使用 define 定义即可 在 渲染的时候,去掉 tmpl 后缀

{{define "sign-in"}}  <!-- 定义一个名为 "sign-in" 的模板 -->

{{ template "header" . }}  <!-- 引用并渲染名为 "header" 的模板,通常包含 HTML 头部信息 -->

<div class="row justify-content-center">  <!-- Bootstrap 的行布局,使内容居中 -->
    <div class="col-4">  <!-- 设定列的宽度为 4/12,使其在页面中适当居中 -->

        <!-- 登录表单,使用 POST 方法提交 -->
        <form method="post" action="/auth/login?next={{ .Next }}">
            <!-- action="/auth/login?next={{ .Next }}"  
                 说明:
                 - 当表单提交时,数据将被发送到 `/auth/login` 端点。
                 - `next={{ .Next }}` 可能用于重定向用户到原先想访问的页面(如未登录前访问的页面)。
            -->

            <!-- 邮箱输入框 -->
            <div class="mb-3">
                <label for="email" class="form-label">
                    Email {{template "required"}}  
                    <!-- 显示“Email”标签,并插入 "required" 模板(通常用于显示必填字段标记 *) -->
                </label>
                <input type="email" class="form-control" id="email" name="email">
                <!-- type="email"  确保输入值符合 Email 格式 -->
            </div>

            <!-- 密码输入框 -->
            <div class="mb-3">
                <label for="password" class="form-label">
                    Password {{template "required"}}  
                    <!-- 显示“Password”标签,并插入 "required" 模板(通常用于显示必填字段标记 *) -->
                </label>
                <input type="password" class="form-control" id="password" name="password">
                <!-- type="password"  确保输入值不会明文显示 -->
            </div>

            <!-- 注册提示 -->
            <div class="mb-3">
                Don't have an account? Click here to <a href="/sign-up">Sign Up</a>.
                <!-- 如果用户没有账号,提供跳转到注册页面的链接 -->
            </div>

            <!-- 提交按钮 -->
            <button type="submit" class="btn btn-primary">Sign In</button>

        </form>  <!-- 表单结束 -->

    </div>
</div>

{{ template "footer" .}}  <!-- 引用并渲染名为 "footer" 的模板,通常包含 HTML 页脚信息 -->

{{end}}  <!-- 结束 "sign-in" 模板定义 -->

  • {{ template “header” . }} 引入 header 头文件, footer 尾文件

这个时候就有一个问题了,就是点击 sign-in 怎么去 进入这个 sign-in 文件渲染的 html 页面呢,那我们来 进行改动吧。
加入href=“/sign-in”,即可完成跳转。

        <div class="ms-3">
        <a type="button" class="btn btn-primary" href="/sign-in">Sign In</a>
        </div>
  • class=“ms-3” ,ms-3 是 Bootstrap 的 margin-start 类(即左外边距)
  • < a > :超链接元素,用户点击后跳转到 /sign-in 登录页面
  • class=“btn btn-primary”: btn:Bootstrap 按钮基础样式,btn-primary:使用 Bootstrap 的蓝色主按钮样式。

主界面
登录界面
提交数据
这个前端输入的数据怎么传给后端呢?
在 sign-in 模板中,表单 () 使用了 POST 方法提交数据到 /auth/login 端点

<form method="post" action="/auth/login?next={{ .Next }}">
    <input type="email" class="form-control" id="email" name="email">
    <input type="password" class="form-control" id="password" name="password">
    <button type="submit" class="btn btn-primary">Sign In</button>
</form>

  • method=“post”:数据不会通过 URL 传递,而是通过 HTTP 请求体 传输,更加安全
  • name=“email” 和 name=“password”:输入的数据会以 键值对 的形式提交到服务器,键名对应 name 属性

数据提交方式
当用户点击 Sign In 按钮时,浏览器会向后端发送 HTTP POST 请求,其请求体(body)大概是:

email=user@example.com&password=123456

处理请求:
示例:

func main() {
    r := gin.Default()

    // 处理登录请求
    r.POST("/auth/login", func(c *gin.Context) {
        // 从表单解析 email 和 password
        email := c.PostForm("email")         // 取出用户输入的 email
        password := c.PostForm("password")   // 取出用户输入的 password

        // 打印日志(生产环境不要打印密码)
        println("Email:", email, "Password:", password)

        // 假设这里是数据库验证逻辑
        if email == "user@example.com" && password == "123456" {
            c.JSON(http.StatusOK, gin.H{"message": "Login successful"})
        } else {
            c.JSON(http.StatusUnauthorized, gin.H{"message": "Invalid email or password"})
        }
    })

    r.Run(":3000") // 监听 3000 端口
}

但是大多数都是使用框架进行开发的,只需要编写登录逻辑即可:

func (s *LoginService) Run(req *user.LoginReq) (resp *user.LoginResp, err error) {
	// Finish your business logic.

	if req.Email == "" || req.Password == "" {
		return nil, errors.New("email or password is empty")
	}
	row, err := model.GetByEmail(s.ctx, mysql.DB, req.Email)
	if err != nil {
		return nil, err
	}
	err = bcrypt.CompareHashAndPassword([]byte(row.PasswordHashed), []byte(req.Password))
	if err != nil {
		return nil, err
	}
	resp = &user.LoginResp{
		UserId: int32(row.ID),
	}
	return resp, nil
}

重定向到主页:

func Login(ctx context.Context, c *app.RequestContext) {
	var err error
	var req auth.LoginReq

	// 解析前端传来的 JSON 或表单数据,填充到req 中,并进行格式校验
	err = c.BindAndValidate(&req)
	if err != nil {
		// 如果解析失败,返回错误信息
		utils.SendErrResponse(ctx, c, consts.StatusOK, err)
		return
	}

	// 调用 LoginService 处理登录逻辑(检查用户是否存在、密码是否正确等)
	redirect, err := service.NewLoginService(ctx, c).Run(&req)
	if err != nil {
		// 如果登录失败,返回错误信息
		utils.SendErrResponse(ctx, c, consts.StatusOK, err)
		return
	}

	// 登录成功后,进行页面重定向
	// ⚠️ 注意:通常重定向应该使用 302 (consts.StatusFound) 而不是 200 (StatusOK)
	c.Redirect(consts.StatusOK, []byte(redirect))

	// 登录成功后的 JSON 响应(已注释掉,因为使用了 Redirect)
	// utils.SendSuccessResponse(ctx, c, consts.StatusOK, "done")
}

然后这里的 redirect 逻辑:
前端处理逻辑


func (h *LoginService) Run(req *auth.LoginReq) (redirect string, err error) {
	// defer func() {
	//     hlog.CtxInfof(h.Context, "req = %+v", req)  // 记录请求信息
	//     hlog.CtxInfof(h.Context, "resp = %+v", resp)  // 记录响应信息
	// }()

	// 调用远程 RPC 服务,验证用户的邮箱和密码是否正确
	resp, err := rpc.UserClient.Login(h.Context, &user.LoginReq{
		Email:    req.Email,
		Password: req.Password,
	})
	if err != nil {
		// 如果 RPC 调用失败,返回错误
		return "", err
	}

	// 获取当前会话(Session)
	session := sessions.Default(h.RequestContext)

	// 在 Session 中存储用户 ID,表示用户已登录
	session.Set("user_id", resp.UserId)

	// 保存 Session 变更,如果失败则返回错误
	err = session.Save()
	if err != nil {
		return "", err
	}

	// 默认重定向到主页 "/"
	redirect = "/"

	// 如果 `req.Next` 不为空,则重定向到用户指定的页面
	if req.Next != "" {
		redirect = req.Next
	}
	return
}

这里的 session 是基于 redis 存储的一个会话。

登录登出的一个界面转变设计,
判断是否有 use.id 如果有,就显示下拉菜单,没有就进行显示 Sign-in

        {{ if .user_id }}
        <div class="dropdown">
            <div class="ms-3 dropdown-toggle" data-bs-toggle="dropdown">
                <i class="fa-solid fa-user fa-lg"></i>
                <span>Yehan</span>
            </div>
            <ul class="dropdown-menu">
                <li><a class="dropdown-item" href="#">Order Center</a></li>
                <li>
                    <form action="/auth/logout" method="post">
                        <button class="dropdown-item" type="submit">Logout</button>
                    </form>
                </li>
            </ul>
        </div>

        {{ else }}
        <div class="ms-3">
        <a type="button" class="btn btn-primary" href="/sign-in">Sign In</a>
        </div>
        {{end}}
        </div>

由于 session的设计是后面每个都要进行去判断,就可以抽出来,设计一个函数进行调用:

func WarpResponse(ctx context.Context, c *app.RequestContext, content map[string]any) map[string]any {
	userId := frontendUtils.GetUserIdFromCtx(ctx)
	content["user_id"] = userId

	if userId > 0 {
		cartResp, err := rpc.CartClient.GetCart(ctx, &cart.GetCartReq{
			UserId: uint32(userId),
		})
		if err == nil && cartResp != nil {
			content["cart_num"] = len(cartResp.Items)
		}
	}
	return content
}
  • 用户注册
    同样改一下代码,biz/handler/auth/auth_service.go, biz/service/register.go
func Register(ctx context.Context, c *app.RequestContext) {
	var err error
	var req auth.RegisterReq
	err = c.BindAndValidate(&req)
	if err != nil {
		utils.SendErrResponse(ctx, c, consts.StatusOK, err)
		return
	}


	_, err = service.NewRegisterService(ctx, c).Run(&req)
	if err != nil {
		utils.SendErrResponse(ctx, c, consts.StatusOK, err)
		return
	}

	//utils.SendSuccessResponse(ctx, c, consts.StatusOK, resp)
	c.Redirect(consts.StatusOK, []byte("/"))
}

func (h *RegisterService) Run(req *auth.RegisterReq) (resp *common.Empty, err error) {
	//defer func() {
	// hlog.CtxInfof(h.Context, "req = %+v", req)
	// hlog.CtxInfof(h.Context, "resp = %+v", resp)
	//}()
	// todo edit your code
	userResp, err := rpc.UserClient.Register(h.Context, &user.RegisterReq{
		Email:           req.Email,
		Password:        req.Password,
		PasswordConfirm: req.PasswordConfirm,
	})
	session := sessions.Default(h.RequestContext)
	session.Set("user_id", userResp.UserId)
	err = session.Save()
	if err != nil {
		return nil, err
	}
	
	return
}

以及注册的html页面:

{{define "sign-up"}}
{{ template "header" . }}
    <div class="row justify-content-center">
        <div class="col-4">
        <form method="post" action="/auth/register">
            <div class="mb-3">
              <label for="email" class="form-label">Email {{template "required"}}</label>
              <input type="email" class="form-control" id="email" name="email">
            </div>
            <div class="mb-3">
              <label for="password" class="form-label">Password {{template "required"}}</label>
              <input type="password" class="form-control" id="password" name="password">
            </div>
            <div class="mb-3">
                <label for="password_confirm" class="form-label">Password Confirm {{template "required"}}</label>
                <input type="password" class="form-control" id="password_confirm" name="password_confirm">
              </div>
            <div class="mb-3">
                Already have a account, click here to <a href="/sign-in">Sign In</a>.
            </div>
            <button type="submit" class="btn btn-primary">Sign Up</button>
          </form>
    </div>
</div>
{{ template "footer" .}}
{{end}}

在用户服务中进行改动添加注册逻辑;

func (s *RegisterService) Run(req *user.RegisterReq) (resp *user.RegisterResp, err error) {
	// Finish your business logic.
	if req.Email == "" || req.Password == "" || req.PasswordConfirm == "" {
		return nil, errors.New("email or password is empty")
	}
	if req.Password != req.PasswordConfirm {
		return nil, errors.New("password not match")
	}
	passwordHashed, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
	if err != nil {
		return nil, err
	}
	newUser := &model.User{
		Email:          req.Email,
		PasswordHashed: string(passwordHashed),
	}
	err = model.Create(s.ctx, mysql.DB, newUser)
	if err != nil {
		return nil, err
	}

	return &user.RegisterResp{UserId: int32(newUser.ID)}, nil
}

用户登出

biz/handler/auth/auth_service.go, biz/service/logout.go

<form action="/auth/logout" method="post">
      <button class="dropdown-item" type="submit">Logout</button>
</form>

在上面idl 的生成代码,用户登出的基本代码也已经生成。
改一下代码,

func Logout(ctx context.Context, c *app.RequestContext) {
	var err error
	var req common.Empty
	err = c.BindAndValidate(&req)
	if err != nil {
		utils.SendErrResponse(ctx, c, consts.StatusOK, err)
		return
	}

	_, err = service.NewLogoutService(ctx, c).Run(&req)
	if err != nil {
		utils.SendErrResponse(ctx, c, consts.StatusOK, err)
		return
	}

	//utils.SendSuccessResponse(ctx, c, consts.StatusOK, resp)
	c.Redirect(consts.StatusOK, []byte("/"))
}

func (h *LogoutService) Run(req *common.Empty) (resp *common.Empty, err error) {
	//defer func() {
	// hlog.CtxInfof(h.Context, "req = %+v", req)
	// hlog.CtxInfof(h.Context, "resp = %+v", resp)
	//}()
	// todo edit your code
	session := sessions.Default(h.RequestContext)
	session.Clear()
	err = session.Save()
	if err != nil {
		return nil, err
	}
	return
}

优化

  1. 点击home 可以直接回到主界面:
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/">Home</a>
</li>
  1. about 界面的设计:
<li class="nav-item">
<a class="nav-link" href="/about">About</a>
</li>

关于吉祥物的介绍:

{{define "about"}}
{{template "header" .}}
  <div>
    <img src="/static/image/logo.jpg" alt="CloudWeGo" class="col-lg-2 col-md-3 col-sm-4"/>
    <p>This is community a driven project.</p>
  </div>
{{template "footer"}}
{{end}}
  1. 登录完成后可以自动返回上一个页面:
    使用 next 记录上一个页面的信息,
	h.GET("/sign-in", func(c context.Context, ctx *app.RequestContext) {
		data := utils.H{
			"Title": "Sign In",
			"Next":  ctx.Query("next"),

		}
		ctx.HTML(consts.StatusOK, "sign-in", data)
	})

传递query参数 next

<form method="post" action="/auth/login?next={{ .Next }}">

然后 auth_service 中的 login 函数接收 redirect next ,进行重定向。

  1. 鉴权
    新增中间件,mniddleware 文件夹,auth.go ,以及 middleware.go,
type SessionUserIdKey string

const SessionUserId  SessionUserIdKey  ="user_id"


func GlobalAuth() app.HandlerFunc {
	return func(ctx context.Context, c *app.RequestContext) {
		s := sessions.Default(c)
		ctx = context.WithValue(ctx, frontendUtils.SessionUserId, s.Get("user_id"))
		c.Next(ctx)
	}
}

func Auth() app.HandlerFunc {
	return func(ctx context.Context, c *app.RequestContext) {
		s := sessions.Default(c)
		userId := s.Get("user_id")
		if userId == nil {
			c.Redirect(consts.StatusFound, []byte("/sign-in?next="+c.FullPath()))
			c.Abort()
			return
		}
		c.Next(ctx)
	}
}
func Register(h *server.Hertz){
	h.Use(GlobalAuth())
}

然后 main,.go 函数中要记得添加路由中间件:

	middleware.Register(h)
  1. rpc 客户端代码
    rpc 客户端代码存储到 rpc_gen 中, 如 user 代码生成到此,
    服务端代码生成到 app/user 中,
    idl 生成代码:
syntax = "proto3";

package user;

option go_package = "/user";


message RegisterReq {
    string email = 1;
    string password = 2;
    string password_confirm = 3;
}

message RegisterResp {
    int32 user_id = 1;
}

message LoginReq {
    string email = 1;
    string password = 2;
}

message LoginResp {
    int32 user_id = 1;
}

service UserService {
  rpc Register (RegisterReq) returns (RegisterResp) {}
  rpc Login (LoginReq) returns (LoginResp) {}
}

使用 make 命令进行 生成:

.PHONY: gen-user
gen-user: 
	@cd rpc_gen && cwgo client --type RPC --service user --module ${ROOT_MOD}/rpc_gen  -I ../idl  --idl ../idl/user.proto
	@cd app/user && cwgo server --type RPC --service user --module ${ROOT_MOD}/app/user --pass "-use ${ROOT_MOD}/rpc_gen/kitex_gen"  -I ../../idl  --idl ../../idl/user.proto

这里记得 go mod tidy 会不成功,如果没有远程仓库的话,
所以记得在 go.mod 中添加。

replace github.com/yehan5555/gomallWSL/rpc_gen => ../../rpc_gen

定义用户模型:

type User struct {
	gorm.Model
	Email          string `gorm:"uniqueIndex;type:varchar(255) not null"`
	PasswordHashed string `gorm:"type:varchar(255) not null"`
}

func (User) TableName() string {
	return "user"
}

func Create(ctx context.Context, db *gorm.DB, user *User) error {
	return db.WithContext(ctx).Create(user).Error
}


func GetByEmail(ctx context.Context, db *gorm.DB, email string) (*User, error) {
	var user User
	err := db.WithContext(ctx).Where("email = ?", email).First(&user).Error
	return &user, err
}

执行 go mod tidy

使用 consul 中间件:

	// service info
	opts = append(opts, server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{
		ServiceName: conf.GetConf().Kitex.Service,
	}))

	r, err := consul.NewConsulRegister(conf.GetConf().Registry.RegistryAddress[0])
	if err != nil {
		klog.Fatal(err)
	}
	opts = append(opts, server.WithRegistry(r))

注册中心的配置需要进行改动,yaml 文件中进行改动,端口改成 8500, protobuf 版本也可以查看是否报错(可能由于版本问题会报错),对数据库mysql 配置进行改动:

func Init() {
	dsn := fmt.Sprintf("%s:%s@tcp(%s:3306)/user?charset=utf8mb4&parseTime=True&loc=Local", os.Getenv("MYSQL_USER"), os.Getenv("MYSQL_PASSWORD"), os.Getenv("MYSQL_HOST"))
	DB, err = gorm.Open(mysql.Open(dsn),
		&gorm.Config{
			PrepareStmt:            true,
			SkipDefaultTransaction: true,
		},
	)
	_=DB.AutoMigrate(&model.User{})
	if err != nil {
		panic(err)
	}
}

yaml 文件也要进行改动:

mysql:
  dsn: "%s:%s@tcp(%s:3306)/user?charset=utf8mb4&parseTime=True&loc=Local"

main 函数 使用 godotenv加载环境变量.

密码的加密使用 哈希加密,加密使用的库:

go get golang.org/x/crypto

然后注册函数:

func (s *RegisterService) Run(req *user.RegisterReq) (resp *user.RegisterResp, err error) {
	// Finish your business logic.
	if req.Email == "" || req.Password == "" || req.PasswordConfirm == "" {
		return nil, errors.New("email or password is empty")
	}
	if req.Password != req.PasswordConfirm {
		return nil, errors.New("password not match")
	}
	passwordHashed, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
	if err != nil {
		return nil, err
	}
	newUser := &model.User{
		Email:          req.Email,
		PasswordHashed: string(passwordHashed),
	}
	err = model.Create(s.ctx, mysql.DB, newUser)
	if err != nil {
		return nil, err
	}

	return &user.RegisterResp{UserId: int32(newUser.ID)}, nil
}

写入数据的操作放在model 中进行;

type User struct {
	gorm.Model
	Email          string `gorm:"uniqueIndex;type:varchar(255) not null"`
	PasswordHashed string `gorm:"type:varchar(255) not null"`
}

func (User) TableName() string {
	return "user"
}

func Create(ctx context.Context, db *gorm.DB, user *User) error {
	return db.WithContext(ctx).Create(user).Error
}


func GetByEmail(ctx context.Context, db *gorm.DB, email string) (*User, error) {
	var user User
	err := db.WithContext(ctx).Where("email = ?", email).First(&user).Error
	return &user, err
}

这个可以创建表,并写入数据。

	_=DB.AutoMigrate(&model.User{})

数据库的自动迁移。

用户服务的登录:

func (s *LoginService) Run(req *user.LoginReq) (resp *user.LoginResp, err error) {
	// Finish your business logic.

	if req.Email == "" || req.Password == "" {
		return nil, errors.New("email or password is empty")
	}
	row, err := model.GetByEmail(s.ctx, mysql.DB, req.Email)
	if err != nil {
		return nil, err
	}
	err = bcrypt.CompareHashAndPassword([]byte(row.PasswordHashed), []byte(req.Password))
	if err != nil {
		return nil, err
	}
	resp = &user.LoginResp{
		UserId: int32(row.ID),
	}
	return resp, nil
}

对接服务发现:
新建 infra/rpc:

编写 client.go 文件

var(
	UserClient userservice.Client
	once sync.Once
)


func InitClient() {
	once.Do(func() {
		initUserClient()
	})
}

func initUserClient() {
	r, err := consul.NewConsulResolver(conf.GetConf().Hertz.RegistryAddr)
	frontendUtils.MustHandleError(err)
	UserClient,err=userservice.NewClient("user",client.WithResolver(r))
	frontendUtils.MustHandleError(err)
}

main 函数进行初始化:
rpc.Init()

然后前端即可使用 服务来对接 用户服务,互获取用户数据。

func (h *RegisterService) Run(req *auth.RegisterReq) (resp *common.Empty, err error) {
	//defer func() {
	// hlog.CtxInfof(h.Context, "req = %+v", req)
	// hlog.CtxInfof(h.Context, "resp = %+v", resp)
	//}()
	// todo edit your code
	userResp, err := rpc.UserClient.Register(h.Context, &user.RegisterReq{
		Email:           req.Email,
		Password:        req.Password,
		PasswordConfirm: req.PasswordConfirm,
	})
	session := sessions.Default(h.RequestContext)
	session.Set("user_id", userResp.UserId)
	err = session.Save()
	if err != nil {
		return nil, err
	}
	return
}

继续优化

utils 中对错误判断函数进行包装:

func MustHandleError(err error) {
	if err != nil {
		hlog.Fatal(err)
	}
}

sessiongkey 也可以通过 utils 进行定义 user_id:

type SessionUserIdKey string

const SessionUserId SessionUserIdKey = "user_id"

获取 user_id 的函数也可以抽离;

func GetUserIdFromCtx(ctx context.Context) int32 {
	userId := ctx.Value(SessionUserId)
	if userId == nil {
		return 0
	}
	return userId.(int32)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值