- config :敏感的配置一般都是在配置中心配置,比如consul或者阿波罗上面
- controller :写一些handler的,拿到参数要去调用service层的逻辑。(只负责接受参数,怎么绑定参数,要去调用哪个service的,handler的一个入口)
- service:service层才是正真的业务处理层。调用dao层的增删改查操作
- dao:只关心数据库的一个操作,其实你有数据库和es都在dao层
- db:负责数据库,中间件层的初始化
- middleware:存放gin的中间件,在注册路由的时候将中间件引入进来
go常用项目结构
dao层只关心数据库的操作,db层只做Mysql层的初始化。model去定义表结构,定义中间表,多对多的结构体。
中间件有个token的验证。
controller层,先去找路由,这个项目有多少的接口(router.go),每个接口到哪些对应的handler。
controller层接收完参数之后,再看调用到哪个service,service再去干了什么增删改查操作。
数据库增删改查就去dao层查看。
这一层一层非常的分明。上面可以理解为日常开发的目录结构最佳实践。
创建数据库
mysql> create database books charset utf8;
图书管理服务
model层 定义数据库结构
binding标签表示是必填项,token是可以为空的,因为一开始注册的时候token的为空。只有登入的时候才有token。
user.go
package model
type User struct {
ID int64 `gorm:"primaryKey" json:"id"`
UserName string `gorm:"not null" json:"username" binding:"required"`
PassWord string `gorm:"not null" json:"password" binding:"required"`
Token string `json:"token"`
}
func (*User) TableName() string {
return "user"
}
book.go
package model
type Book struct {
ID int64 `gorm:"primaryKey" json:"id"`
Name string `gorm:"not null" json:"name" binding:"required"`
Desc string `json:"desc"`
Users []User `gorm:"many2many:book_users"`
}
func (*Book) TableName() string {
return "book"
}
多对多关系可以在user这层定义也行,在book这一层定义也可以。这个主要看你的一个实际使用的场景,这里在book模型里面去定义就行了。
还需要去定义一个中间表的模型user_m2m_book.go
package model
type BookUser struct {
UserID int64 `gorm:"primaryKey"`
BookID int64 `gorm:"primaryKey"`
}
这里不需要自定义表名,它只有一个主键,也没有其他属性了。它也是永了外键,也是使用了那两个模型的主键。
DB层
模型定义好之后去做数据库的初始化,这里需要预留,因为可能不仅仅只有MySQL的初始化。
这样赋值到一个全局变量,之后使用mysql.DB就可以在任何地方去使用了。
package mysql
import (
"book/model"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// DB 要将DB实例放到全局变量,这样就可以使用mysql.DB在任何地方去使用了.
var DB *gorm.DB
func InitMysql() {
dsn := "root:7PXjAkY!&nlR@tcp(192.168.11.128:3306)/books?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
//这个地方Log和panic都是可以的
if err != nil {
fmt.Println(err)
}
DB = db
err = DB.AutoMigrate(model.Book{}, model.User{}, model.BookUser{})
if err != nil {
fmt.Println(err)
}
}
DAO层
初始化好之后在dao层开始写,dao层的操作就是数据库的增删改查
package dao
import (
"book/db/mysql"
"book/model"
"errors"
"fmt"
"github.com/wonderivan/logger"
"gorm.io/gorm"
)
//定义user结构体,以及User变量,能够直接跨包调用user下面的方法
//只需要一次初始化即可,不用每次调用的都是先初始化
var User user
type user struct {
}
// Add 新增 用于注册
func (*user) Add(user *model.User) error {
if tx := mysql.DB.Create(user); tx.Error != nil {
//打印错误提示
logger.Error(fmt.Sprintf("添加User失败,错误信息:%s", tx.Error))
return errors.New(fmt.Sprintf("添加User失败,错误信息:%s", tx.Error))
}
return nil
}
// Has 查询 基于name 用于新增
func (*user) Has(name string) (*model.User, bool, error) {
//初始化要申请内存,不然会报错
data := &model.User{}
tx := mysql.DB.Where("username = ?", name).Find(data)
//如果记录没有查询到,报错是从tx.error里面拿到的,相当于tx.Error = gorm.ErrRecordNotFound
if errors.Is(tx.Error, gorm.ErrRecordNotFound) {
return nil, false, nil
}
//等会去调用的时候,不会先去判断有没有,要先去判断error,有error就是真正的错误,没有判断是不是false
if tx.Error != nil {
logger.Error(fmt.Sprintf("查询用户失败,错误信息:%s", tx.Error))
return nil, false, errors.New(fmt.Sprintf("查询用户失败,错误信息:%s", tx.Error))
}
return data, true, nil
}
// GetByToken 基于token查询,用于中间件token校验
func (*user) GetByToken(token string) (*model.User, bool, error) {
//初始化要申请内存,不然会报错
data := &model.User{}
tx := mysql.DB.Where("token = ?", token).Find(data)
//如果记录没有查询到,报错是从tx.error里面拿到的,相当于tx.Error = gorm.ErrRecordNotFound
if errors.Is(tx.Error, gorm.ErrRecordNotFound) {
return nil, false, nil
}
if tx.Error != nil {
logger.Error(fmt.Sprintf("查询用户失败%s", tx.Error))
return nil, false, errors.New(fmt.Sprintf("查询用户失败%v/n", tx.Error))
}
return data, true, nil
}
// UPDateToken 更新token,这里其实就是找到user的条件去更新就行了
// 第一个参数可以是id也可以是用户名,如果用户名唯一,只要保证找到指定用户即可
// 除了查询的操作以外,增删改只需要返回一个error即可,判断操作有没有成功
func (*user) UPDateToken(user *model.User, token string) error {
tx := mysql.DB.Model(user).
Where("username= ? and password = ?", user.UserName, user.PassWord).
Update("token", token)
if tx.Error != nil {
logger.Error(fmt.Sprintf("更新user token失败,错误信息:%s", tx.Error))
return errors.New(fmt.Sprintf("更新user token失败,错误信息:%s", tx.Error))
}
return nil
}
service层
和dao层一样,都定义了相同的结构体,不同的包使用相同的结构体变量,但是点出来的方法都是不同的。
package service
import (
"book/dao"
"book/model"
"errors"
"github.com/google/uuid"
)
var User user
type user struct {
}
// Add 这里的返回error是因为在进行数据库操作的时候也会返回error
// 这里的error返回到最上层,controller层
func (*user) Add(user *model.User) error {
//先去校验是否存在
_, has, err := dao.User.Has(user.UserName)
if err != nil {
return err
}
if has {
return errors.New("该用户已经存在,请不要重复添加")
}
//校验完毕,添加用户
if err = dao.User.Add(user); err != nil {
return err
}
return nil
}
// Login 登入其实就是去更新token
func (*user) Login(name, password string) error {
//先去校验是否存在,如果不存着就不能登入
data, has, err := dao.User.Has(name)
if err != nil {
return err
}
if !has {
return errors.New("该用户不存在")
}
if data.PassWord != password {
return errors.New("用户名或密码错误")
}
return nil
}
func (*user) UpDateToken(user *model.User) error {
token := uuid.New().String()
//这个是字符串类型的uuid
//因为这个dao里面方法也是返回error的,所以可以保持一致的
return dao.User.UPDateToken(user, token)
}
controller层
service层写好了之后那么就是controller层。主要就是gin的handler的逻辑。
controller层负责handler的逻辑,同时调用service层,如果调用service层失败或者成功那么将错误信息或者自定义成功信息返回给客户端。
上面的注册逻辑就由各个层组装好了,controller去调用service层,service去调用dao层。
controller主要就是handler,handler第一步是通过shouldbind去获取结构体,之后根据逻辑是否正确返回客户端json字符串。文章来源:https://www.toymoban.com/news/detail-679839.html
package controller
import (
"book/model"
"book/service"
"github.com/gin-gonic/gin"
"net/http"
)
type User user
type user struct {
}
func (*user) Register(c *gin.Context) {
u := &model.User{}
//专门用于标签为json的参数接受,也就是POST PUT DELETE这种带body的
if err := c.ShouldBindJSON(u); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": "90400",
"msg": err.Error(),
"data": nil,
})
return
}
//创建用户
if err := service.User.Add(u); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": "90500",
"msg": err.Error(),
"data": nil,
})
return
}
c.JSON(http.StatusBadRequest, gin.H{
"code": "90400",
"msg": "创建注册成功",
"data": nil,
})
}
func (*User) Login(c *gin.Context) {
u := &model.User{}
if err := c.ShouldBindJSON(u); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": "90400",
"msg": err.Error(),
"data": nil,
})
return
}
//校验账号密码,这里传入结构体或者用户名密码都行,看个人习惯
//如果不return就会继续往下执行,逻辑就会错误
if err := service.User.Login(u.UserName, u.PassWord); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": "90500",
"msg": err.Error(),
"data": nil,
})
return
}
if err := service.User.UpDateToken(u); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": "90500",
"msg": err.Error(),
"data": nil,
})
return
}
c.JSON(http.StatusBadRequest, gin.H{
"code": "90400",
"msg": "用户登入成功",
"data": nil,
})
}
router层
功能写好之后就是去注册路由,文章来源地址https://www.toymoban.com/news/detail-679839.html
到了这里,关于实战 图书馆系统管理案例的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!