Hertz实践-备忘录项目 | 青训营笔记

100 阅读4分钟

这是我参与「 第五届青训营 」伴学笔记创作活动的第 7 天 介绍了Hertz的基本使用以及Hertz中间件的配置,并使用Hertz、gorm设计实现了个人备忘录项目。

项目介绍

1. 功能介绍

可实现用户登陆后创建个人备忘录功能,可在备忘录列表查看个人的全部备忘录,可实现对个人备忘录的更新与删除。

2. 技术栈

  • http框架:Hertz
  • 持久层框架:gorm
  • 数据库:mysql
  • 中间件:
    • 跨域CORS:go get github.com/hertz-contrib/cors
    • 权限认证JWT:go get github.com/hertz-contrib/jwt
    • 异常处理Recovery

代码实现

IDL

api注解

service代码生成需要引入api.proto,在idl文件夹下创建即可

syntax = "proto3";

package api;

import "google/protobuf/descriptor.proto";

option go_package = "/api";

extend google.protobuf.FieldOptions {
  optional string raw_body = 50101;
  optional string query = 50102;
  optional string header = 50103;
  optional string cookie = 50104;
  optional string body = 50105;
  optional string path = 50106;
  optional string vd = 50107;
  optional string form = 50108;
  optional string go_tag = 51001;
  optional string js_conv = 50109;
}

extend google.protobuf.MethodOptions {
  optional string get = 50201;
  optional string post = 50202;
  optional string put = 50203;
  optional string delete = 50204;
  optional string patch = 50205;
  optional string options = 50206;
  optional string head = 50207;
  optional string any = 50208;
  optional string gen_path = 50301;
  optional string api_version = 50302;
  optional string tag = 50303;
  optional string name = 50304;
  optional string api_level = 50305;
  optional string serializer = 50306;
  optional string param = 50307;
  optional string baseurl = 50308;
}

extend google.protobuf.EnumValueOptions {
  optional int32 http_code = 50401;
}

用户模块

封装user服务模块 该文件定义了user的服务函数,以及请求与响应的结构体

// idl/user/user.proto
syntax = "proto3";
package user; // 指定message(model)的包名
option go_package = "hertz/user"; // 指定service(router)的包名

import "api.proto";

// 定义模型
message UserModel {
  int64 ID = 1; // 用户id
  string Username = 2; // 用户名
  string Password = 3; // 密码
  int64 CreatedAt = 4; // gorm.Model 创建时间
  int64 UpdatedAt = 5; // gorm.Model 更新时间
  int64 IsDeleted = 6; // 删除标识
}

// 封装用户请求
message UserRequest {
  string Username = 1[(api.body)="username"];
  string Password = 2[(api.body)="password"];
}

// 封装用户响应
message UserDetailResponse {
  uint32 Code = 1;
  UserModel UserDetail = 2;
}

service UserService {
  // 注册
  rpc UserRegister(UserRequest) returns(UserDetailResponse){
    option (api.post) = "/user/register";
  }
}

备忘录模块

封装task服务模块 该文件定义了task的服务函数,以及请求与响应的结构体

// idl/task/task.proto
syntax = "proto3";
package service;
option go_package = "task";

import "api.proto";

message TaskModel {
  int64 ID = 1; // id
  int64 UID = 2; // 用户id
  string Title = 3; // 标题
  string Content = 4; // 内容
  int64 CreatedAt = 7; // gorm.Model 创建时间
  int64 UpdatedAt = 8; // gorm.Model 更新时间
  int64 IsDeleted = 9; // 删除标识
}

// 封装task服务请求参数
message TaskRequest {
  uint64 Id = 1[(api.body)="id",(api.path)="id"];
  uint64 Uid = 2[(api.body)="uid"];
  string Title = 3[(api.body)="id"];
  string Content = 4[(api.body)="id"];
  uint32 PageNum = 8[(api.query)="pageNum"];
  uint32 PageSize = 9[(api.query)="pageSize"];
}

// 获取全部备忘录的响应结构体
message TaskListResponse {
  repeated TaskModel taskModel = 1;
  int64 Count = 2;
}

// 获取备忘录详情的响应结构体
message TaskResponse {
  TaskModel TaskDetail = 1;
}

service TaskService {
  // 创建
  rpc CreateTask(TaskRequest) returns (TaskResponse) {
    option (api.post) = "/task/create";
  }
  // 更新
  rpc UpdateTask(TaskRequest) returns (TaskResponse) {
    option (api.put) = "/task/update";
  }
  // 删除
  rpc DeleteTask(TaskRequest) returns (TaskResponse) {
    option (api.delete) = "/task/delete/:id";
  }
  // 获取详情
  rpc GetTaskDetail(TaskRequest) returns (TaskResponse) {
    option (api.get) = "/task/getDetail/:id";
  }
  // 获取全部
  rpc GetTaskList(TaskRequest) returns (TaskListResponse) {
    option (api.get) = "/task/getDetail/:pageSize/:pageNum";
  }
}

代码生成

在项目目录下执行命令

hz new -idl idl/task/task.proto -mod hertz-mylist
hz new -idl idl/user/user.proto -mod hertz-mylist
go mod tidy

目录结构

image.png

中间件

详情见: Hertz中间件 | 青训营笔记

业务逻辑

用户模块

// biz/handler/user/user_service.go
func UserRegister(ctx context.Context, c *app.RequestContext) {
	var req user.UserRequest
    if err := c.BindAndValidate(&req); err != nil {
            panic(result.NewError(10000, "数据绑定异常"))
    }
    resp, err := user.Register(&req)

    if err != nil {
            panic(err)
    }

    c.JSON(consts.StatusOK, resp)
}

// biz/model/user/user.go
func Register(req *UserRequest) (*UserDetailResponse, error) {
    username := req.Username
    password := req.Password
    if password == "" {
            return nil, result.NewIError(10003, "输入密码为空")
    }
    var count int64
    gorm.DB.Model(User{}).Where("username = ?", username).Count(&count)
    if count > 0 {
            return nil, result.NewIError(10004, "用户名已存在")
    }
    var userReg User
    if err := userReg.SetPassword(password); err != nil {
            return nil, result.NewIError(10005, "密码生成错误")
    }
    userReg.Username = username
    if err := gorm.DB.Create(&userReg).Error; err != nil {
            return nil, result.NewIError(10006, "创建用户失败")
    }
    return BuildUserDetailResponse(&userReg), nil
}

备忘录模块

// biz/handler/task/task_service.go
func CreateTask(ctx context.Context, c *app.RequestContext) {
    var err error
    var req task.TaskRequest
    err = c.BindAndValidate(&req)
    claim, exists := c.Get(middleware.IdentityKey)
    if exists {
            req.Uid = uint64(claim.(*middleware.Claim).ID)
    }
    if err != nil {
            c.String(consts.StatusBadRequest, err.Error())
            return
    }
    taskResponse, err := task.CreateTask(&req)
    if err != nil {
            panic(err)
    }
    c.JSON(consts.StatusOK, taskResponse)
}

// biz/model/task/task.go
func CreateTask(req *TaskRequest) (*TaskResponse, error) {
    task := Task{
            Uid:     uint(req.Uid),
            Title:   req.Title,
            Content: req.Content,
    }
    if err := gorm.DB.Create(&task).Error; err != nil {
            return nil, result.NewIError(10006, "创建备忘录失败")
    }
    return BuildTaskResponse(&task), nil
}

源码

Hanabi-wxl/hertz-project-myList (github.com)

参考文章

hertz文档
字节开源WEB框架Hertz太香啦!