此项目使用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
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);
});
|
本地缓存不是响应式的