此项目使用Gin+Gorm ,基于RESTful API实现的一个备忘录

https://blog.csdn.net/weixin_45304503/article/details/120680957

项目主要功能介绍

  • 用户注册登录 ( jwt-go鉴权 )
  • 新增 / 删除 / 修改 / 查询 备忘录
  • 存储每条备忘录的浏览次数view
  • 分页功能

项目主要依赖:

Golang V1.15

  • Gin
  • Gorm
  • mysql
  • redis
  • ini
  • jwt-go

项目结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
TodoList/
├── api
├── cache
├── conf
├── middleware
├── model
├── pkg
  ├── e
  ├── logging
  ├── util
├── routes
├── serializer
└── service

api : 用于定义接口函数 cache : 放置redis缓存 conf : 用于存储配置文件 middleware : 应用中间件 model : 应用数据库模型 pkg / e : 封装错误码 pkg / logging : 日志打印 pkg / util : 工具函数 routes : 路由逻辑处理 serializer : 将数据序列化为 json 的函数 service : 接口函数的实现

项目结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
douyin_demo/
├── controller
├── logs
├── conf
├── middleware
├── repository
├── pkg
  ├── e
  ├── util
├── serializer
└── service

controller: 用于定义接口函数 logs: 日志 conf : 用于存储配置文件 middleware : 应用中间件 repository: 应用数据库模型 pkg / e : 封装错误码 pkg / util : 工具函数 serializer : 将数据序列化为 json 的函数 service : 接口函数的实现

1
2
go mod init to-do-list
go mod tidy

开发过程

1、使用ini管理mysql和redis等配置

2、在model文件夹使用gorm来管理数据库

在实际项目中定义数据库模型注意以下几点:

1、结构体的名称必须首字母大写 ,并和数据库表名称对应。例如:表名称为 user 结构体 名称定义成 User,表名称为 article_cate 结构体名称定义成 ArticleCate

2、结构体中的字段名称首字母必须大写,并和数据库表中的字段一一对应。例如:下面结 构体中的 Id 和数据库中的 id 对应,Username 和数据库中的 username 对应,Age 和数据库中 的 age 对应,Email 和数据库中的 email 对应,AddTime 和数据库中的 add_time 字段对应

3、默认情况表名是结构体名称的复数形式。如果我们的结构体名称定义成 User,表示这个 模型默认操作的是 users 表

4、gorm.Model 是一个包含了 ID, CreatedAt, UpdatedAt, DeletedAt 四个字段的Golang结构体

5、数据库中存储密码要加密,使用bcrypt

6、AutoMigrate自动迁移(牛

3、路由中使用swagger

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//swag
go get github.com/swaggo/swag/cmd/swag
//$ GOPATH / bin /下会看到多了一个swag。把$ GOPATH / bin /加到PATH后,就可以直接用swag命令行了
//在包含main.go的Go工程的根目录下执行swag init,swag会检索当前工程里的swag注解(类似上述Java中的注解),生成docs.go以及swagger.json/yaml

swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
_ "to-do-list/docs" // 这里需要引入本地已生成文档

r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

4、路由中使用中间件

所有路由都需要通过logging和cors中间件

1
r.Use(middleware.NewLogger(),middleware.Cors())

将所有日志输出到logs文件夹下,并以日期为文件夹名

1
2
3
time="2022-03-26 20:50:37" level=info msg="| 200 |    466.1842ms |             ::1 | POST | /api/v1/user/register |"
time="2022-03-26 20:50:43" level=info msg="| 200 |     15.2263ms |             ::1 | POST | /api/v1/user/register |"
time="2022-03-26 21:15:47" level=info msg="| 200 |    376.1199ms |             ::1 | POST | /api/v1/user/login |"

5、登录保护

注册用户

api中

ShouldBind能够基于请求的不同,自动提取JSON、form表单和QueryString类型的数据,并把值绑定到指定的结构体对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func UserRegister(c *gin.Context) {
	var userRegisterService service.UserService //相当于创建了一个UserRegisterService对象,调用这个对象中的Register方法。
	if err := c.ShouldBind(&userRegisterService); err == nil {
		res := userRegisterService.Register()
		c.JSON(200, res)
	} else {
		c.JSON(400, ErrorResponse(err))
		util.Logger().Info(err)
	}
}

在service的逻辑为:

1、查询数据库中是否由此用户名

2、var user model.User

3、将前端传来的用户名存入user

3、将前端传来的密码加密后存入user

4、在数据库中保存user并返回

登录用户

逻辑很简单,这里会返回一个token,后续进行备忘录的操作都需要这个token

1
2
authed := v1.Group("/") //需要登陆保护
authed.Use(middleware.JWT())

6、操作备忘录

操作备忘录时传入的uid、tid都是从token中解析的claims中获得,确保用户已登录

7、前后端分离

使用PostForm获取不到vue传来的数据,因为vue把数据封装成了json,可以使用map,json解析或gin自带的Bind

image-20220428165024492

image-20220429191650151

axios实例创建成功之后,不会因为我们的storage储存的token变化而变化

为了让每次请求都能动态的变化head里的token,需要配置一个interceptor

1
2
3
4
5
6
7
8
9
// Add a request interceptor
service.interceptors.request.use((config) => {
  // Do something before request is sent
  Object.assign(config.headers, { Authorization: `Bearer ${storageService.get(storageService.USER_TOKEN)}` });
  return config;
}, (error) => {
  // Do something with request error
  return Promise.reject(error);
});

image-20220429192120544

本地缓存不是响应式的