这是我参与「 第五届青训营 」伴学笔记创作活动的第 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
- 跨域CORS:
代码实现
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
目录结构
中间件
详情见: 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)