空谈误国,实干兴邦,零碎的知识掌握的再多,如果不将它们整个的串联起来就是一堆没有头绪的点,为了充分的将之前讲解的相关的gin、gorm相关的知识有逻辑的串联起来,接下来的教程将实现一个具有restfui风格的api,需要具备的知识点有gin、gorm、viper等。
one
- conf
- config
- enum
- handler
- model
- query
- repository
- resp
- service
- utils
go.mod
Handler.go
main.go
conf目录主要存放项目的配置文件config.yaml, config目录主要存放config.go加载项目的配置文件的实现,enum存放定义的常量、model存放实体类、repository存放数据库交互接口、service存放数据库接口调用类、handler存放对应的处理器、query包和utils分贝存放分页查询实体和工具类,Handler.go实现配置DB对象、handler对象、配置文件初始化,main.go配置路由。
one/conf/config.yaml
mode: debug
port: :9090
url: http://127.0.0.1:9090
max_check_count: 10
database:
name: test
host: 127.0.0.1:3306
username: root
password: root
one/config/config.go
import (
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"log"
)
const PAGE_SIZE int = 10
type Config struct {
Name string
}
func Init(name string) error {
c := Config{
Name: name,
}
//初始化配置文件
if err := c.initConfig(); err != nil {
return err
}
//监控配置文件变化并热加载程序
c.watchConfig()
return nil
}
func (c *Config) initConfig() error {
//如果指定了配置文件,则解析指定的配置文件
if c.Name != "" {
viper.SetConfigFile(c.Name)
} else {
//如果没有指定配置文件,则解析默认的配置文件
viper.AddConfigPath("conf")
viper.SetConfigName("config")
}
// 设置配置文件格式为YAML
viper.SetConfigType("yaml")
if err := viper.ReadInConfig(); err != nil { // viper解析配置文件
return err
}
return nil
}
// 监控配置文件变化并热加载程序
func (c *Config) watchConfig() {
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
log.Printf("Config file changed: %s", e.Name)
})
}
one/repository/user.go
import (
"fmt"
"gorm.io/gorm"
"one/model"
"one/query"
"one/utils"
)
type UserRepository struct {
DB *gorm.DB
}
type UserRepoInterface interface {
List(req *query.ListQuery) (users []*model.User, err error)
GetTotal(req *query.ListQuery) (total int64, err error)
Get(user model.User) (*model.User, error)
Exist(user model.User) *model.User
ExistByUserID(id string) *model.User
ExistByMobile(mobile string) *model.User
Add(user model.User) (*model.User, error)
Edit(user model.User) (bool, error)
Delete(u model.User) (bool, error)
}
func (repo *UserRepository) List(req *query.ListQuery) (users []*model.User, err error) {
fmt.Println(req)
db := repo.DB
limit, offset := utils.Page(req.PageSize, req.Page) // 分页
if err := db.Order("id desc").Limit(limit).Offset(offset).Find(&users).Error; err != nil {
return nil, err
}
return users, nil
}
func (repo *UserRepository) GetTotal(req *query.ListQuery) (total int64, err error) {
var users []model.User
db := repo.DB
if err := db.Find(&users).Count(&total).Error; err != nil {
return total, err
}
return total, nil
}
func (repo *UserRepository) Get(user model.User) (*model.User, error) {
if err := repo.DB.Where(&user).Find(&user).Error; err != nil {
return nil, err
}
return &user, nil
}
func (repo *UserRepository) Exist(user model.User) *model.User {
var count int
repo.DB.Find(&user).Where("nick_name = ?", user.NickName)
if count > 0 {
return &user
}
return nil
}
func (repo *UserRepository) ExistByMobile(mobile string) *model.User {
var count int
var user model.User
repo.DB.Find(&user).Where("mobile = ?", mobile)
if count > 0 {
return &user
}
return nil
}
func (repo *UserRepository) ExistByUserID(id string) *model.User {
var user model.User
repo.DB.Where("user_id = ?", id).First(&user)
return &user
}
func (repo *UserRepository) Add(user model.User) (*model.User, error) {
if exist := repo.Exist(user); exist != nil {
return nil, fmt.Errorf("用户注册已存在")
}
err := repo.DB.Create(&user).Error
if err != nil {
return nil, fmt.Errorf("用户注册失败")
}
return &user, nil
}
func (repo *UserRepository) Edit(user model.User) (bool, error) {
err := repo.DB.Model(&user).Where("user_id=?", user.UserId).Updates(map[string]interface{}{"nick_name": user.NickName, "mobile": user.Mobile, "address": user.Address}).Error
//err := repo.DB.Save(&user).Error
if err != nil {
return false, err
}
return true, nil
}
func (repo *UserRepository) Delete(u model.User) (bool, error) {
err := repo.DB.Model(&u).Where("user_id=?", u.UserId).Update("is_deleted", u.IsDeleted).Error
if err != nil {
return false, err
}
return true, nil
}
one/service/user.go
import (
"errors"
"fmt"
uuid "github.com/satori/go.uuid"
"one/config"
"one/model"
"one/query"
"one/repository"
"one/utils"
)
type UserSrv interface {
List(req *query.ListQuery) (users []*model.User, err error)
GetTotal(req *query.ListQuery) (total int64, err error)
Get(user model.User) (*model.User, error)
Exist(user model.User) *model.User
ExistByUserID(id string) *model.User
Add(user model.User) (*model.User, error)
Edit(user model.User) (bool, error)
Delete(id string) (bool, error)
}
type UserService struct {
Repo repository.UserRepoInterface
}
func (srv *UserService) List(req *query.ListQuery) (users []*model.User, err error) {
if req.PageSize < 1 {
req.PageSize = config.PAGE_SIZE
}
return srv.Repo.List(req)
}
func (srv *UserService) GetTotal(req *query.ListQuery) (total int64, err error) {
return srv.Repo.GetTotal(req)
}
func (srv *UserService) Get(user model.User) (*model.User, error) {
return srv.Repo.Get(user)
}
func (srv *UserService) Exist(user model.User) *model.User {
return srv.Repo.Exist(user)
}
func (srv *UserService) ExistByUserID(id string) *model.User {
return srv.Repo.ExistByUserID(id)
}
func (srv *UserService) Add(user model.User) (*model.User, error) {
//根据手机号判断是否存在用户
result := srv.Repo.ExistByMobile(user.Mobile)
if result != nil {
return nil, errors.New("用户已经存在")
}
user.UserId = uuid.NewV4().String()
if user.Password == "" {
user.Password = utils.Md5("123456")
}
user.IsDeleted = false
user.IsLocked = false
return srv.Repo.Add(user)
}
func (srv *UserService) Edit(user model.User) (bool, error) {
if user.UserId == "" {
return false, fmt.Errorf("参数错误")
}
exist := srv.Repo.ExistByUserID(user.UserId)
if exist == nil {
return false, errors.New("参数错误")
}
exist.NickName = user.NickName
exist.Mobile = user.Mobile
exist.Address = user.Address
return srv.Repo.Edit(*exist)
}
func (srv *UserService) Delete(id string) (bool, error) {
if id == "" {
return false, errors.New("参数错误")
}
user := srv.ExistByUserID(id)
if user == nil {
return false, errors.New("参数错误")
}
user.IsDeleted = !user.IsDeleted
return srv.Repo.Delete(*user)
}
one/handler/user.go
import (
"github.com/gin-gonic/gin"
"net/http"
"one/enum"
"one/model"
"one/query"
"one/resp"
"one/service"
)
type UserHandler struct {
UserSrv service.UserSrv
}
func (h *UserHandler) GetEntity(result model.User) resp.User {
return resp.User{
Id: result.UserId,
Key: result.UserId,
UserId: result.UserId,
NickName: result.NickName,
Mobile: result.Mobile,
Address: result.Address,
IsDeleted: result.IsDeleted,
IsLocked: result.IsLocked,
}
}
func (h *UserHandler) UserInfoHandler(c *gin.Context) {
entity := resp.Entity{
Code: int(enum.OperateFail),
Msg: enum.OperateFail.String(),
Total: 0,
TotalPage: 1,
Data: nil,
}
userId := c.Param("id")
if userId == "" {
c.JSON(http.StatusInternalServerError, gin.H{"entity": entity})
return
}
u := model.User{
UserId: userId,
}
result, err := h.UserSrv.Get(u)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"entity": entity})
return
}
r := h.GetEntity(*result)
entity = resp.Entity{
Code: http.StatusOK,
Msg: "OK",
Total: 0,
TotalPage: 0,
Data: r,
}
c.JSON(http.StatusOK, gin.H{"entity": entity})
}
func (h *UserHandler) UserListHandler(c *gin.Context) {
var q query.ListQuery
entity := resp.Entity{
Code: int(enum.OperateFail),
Msg: enum.OperateFail.String(),
Total: 0,
TotalPage: 1,
Data: nil,
}
err := c.ShouldBindQuery(&q)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"entity": entity})
return
}
list, err := h.UserSrv.List(&q)
total, err := h.UserSrv.GetTotal(&q)
if err != nil {
panic(err)
}
if q.PageSize == 0 {
q.PageSize = 5
}
ret := int(total) % q.PageSize
ret2 := int(total) / q.PageSize
totalPage := 0
if ret == 0 {
totalPage = ret2
} else {
totalPage = ret2 + 1
}
var newList []*resp.User
for _, item := range list {
r := h.GetEntity(*item)
newList = append(newList, &r)
}
entity = resp.Entity{
Code: http.StatusOK,
Msg: "OK",
Total: total,
TotalPage: totalPage,
Data: newList,
}
c.JSON(http.StatusOK, gin.H{"entity": entity})
}
func (h *UserHandler) AddUserHandler(c *gin.Context) {
entity := resp.Entity{
Code: int(enum.OperateFail),
Msg: enum.OperateFail.String(),
Total: 0,
Data: nil,
}
u := model.User{}
err := c.ShouldBindJSON(&u)
if err != nil {
c.JSON(http.StatusOK, gin.H{"entity": entity})
return
}
r, err := h.UserSrv.Add(u)
if err != nil {
entity.Msg = err.Error()
return
}
if r.UserId == "" {
c.JSON(http.StatusOK, gin.H{"entity": entity})
return
}
entity.Code = int(enum.OperateOk)
entity.Msg = enum.OperateOk.String()
c.JSON(http.StatusOK, gin.H{"entity": entity})
}
func (h *UserHandler) EditUserHandler(c *gin.Context) {
u := model.User{}
entity := resp.Entity{
Code: int(enum.OperateFail),
Msg: enum.OperateFail.String(),
Total: 0,
Data: nil,
}
err := c.ShouldBindJSON(&u)
if err != nil {
c.JSON(http.StatusOK, gin.H{"entity": entity})
return
}
b, err := h.UserSrv.Edit(u)
if err != nil {
c.JSON(http.StatusOK, gin.H{"entity": entity})
return
}
if b {
entity.Code = int(enum.OperateOk)
entity.Msg = enum.OperateOk.String()
c.JSON(http.StatusOK, gin.H{"entity": entity})
}
}
func (h *UserHandler) DeleteUserHandler(c *gin.Context) {
id := c.Param("id")
b, err := h.UserSrv.Delete(id)
entity := resp.Entity{
Code: int(enum.OperateFail),
Msg: enum.OperateFail.String(),
Total: 0,
Data: nil,
}
if err != nil {
c.JSON(http.StatusOK, gin.H{"entity": entity})
return
}
if b {
entity.Code = int(enum.OperateOk)
entity.Msg = enum.OperateOk.String()
c.JSON(http.StatusOK, gin.H{"entity": entity})
}
}
one/utils/utils.go
import (
"crypto/md5"
"fmt"
"io"
"one/config"
"time"
)
// Page 分页
func Page(Limit, Page int) (limit, offset int) {
if Limit > 0 {
limit = Limit
} else {
limit = config.PAGE_SIZE
}
if Page > 0 {
offset = (Page - 1) * limit
} else {
offset = -1
}
return limit, offset
}
// Sort 排序
// 默认 created_at desc
func Sort(Sort string) (sort string) {
if Sort != "" {
sort = Sort
} else {
sort = "create_at desc"
}
return sort
}
const TimeLayout = "2006-01-02 15:04:05"
var (
Local = time.FixedZone("CST", 8*3600)
)
func GetNow() string {
now := time.Now().In(Local).Format(TimeLayout)
return now
}
func TimeFormat(s string) string {
result, err := time.ParseInLocation(TimeLayout, s, time.Local)
if err != nil {
panic(err)
}
fmt.Println(result)
return result.In(Local).Format(TimeLayout)
}
func Md5(str string) string {
w := md5.New()
io.WriteString(w, str)
md5str := fmt.Sprintf("%x", w.Sum(nil))
return md5str
}
one/enum/user_enum.go
type ResponseType int
const (
OperateOk ResponseType = 200
OperateFail ResponseType = 500
)
func (p ResponseType) String() string {
switch p {
case OperateOk:
return "Ok"
case OperateFail:
return "Fail"
default:
return "UNKNOWN"
}
}
one/resp/user.go
type User struct {
Id string `json:"id"`
Key string `json:"key"`
UserId string `json:"userId" gorm:"column:user_id"`
NickName string `json:"nickName" gorm:"column:nick_name"`
Mobile string `json:"mobile" gorm:"column:mobile" binding:"required"`
Address string `json:"address" gorm:"column:address"`
IsDeleted bool `json:"isDeleted" gorm:"column:is_deleted"`
IsLocked bool `json:"isLocked" gorm:"column:is_locked"`
}
one/Handler.go
package main
import (
"fmt"
"github.com/spf13/viper"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"log"
"one/config"
"one/handler"
"one/model"
"one/repository"
"one/service"
)
var (
DB *gorm.DB
BannerHandler handler.BannerHandler
CategoryHandler handler.CategoryHandler
OrderHandler handler.OrderHandler
ProductHandler handler.ProductHandler
UserHandler handler.UserHandler
)
func initViper() {
if err := config.Init(""); err != nil {
panic(err)
}
}
func initDB() {
fmt.Println("数据库 init")
var err error
conf := &model.DBConf{
Host: viper.GetString("database.host"),
User: viper.GetString("database.username"),
Password: viper.GetString("database.password"),
DbName: viper.GetString("database.name"),
}
config := fmt.Sprintf("%s:%s@tcp(%s)/%s?parseTime=true&charset=utf8&parseTime=%t&loc=%s",
conf.User,
conf.Password,
conf.Host,
conf.DbName,
true,
"Local")
DB, err = gorm.Open(mysql.Open(config), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
//禁用表的复数形式
SingularTable: true,
},
})
if err != nil {
log.Fatalf("connect error: %v\n", err)
}
fmt.Println("数据库 init 结束...")
}
func initHandler() {
UserHandler = handler.UserHandler{
UserSrv: &service.UserService{
Repo: &repository.UserRepository{
DB: DB,
},
}}
}
func init() {
initViper()
initDB()
initHandler()
}
package main
import (
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
"net/http"
)
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token")
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
c.Header("Access-Control-Allow-Credentials", "true")
if method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
}
// 处理请求
c.Next()
}
}
func main() {
r := gin.Default()
r.Use(Cors())
gin.SetMode(viper.GetString("mode"))
user := r.Group("/api/user")
{
user.GET("/list", UserHandler.UserListHandler)
user.GET("/info/:id", UserHandler.UserInfoHandler)
user.POST("/add", UserHandler.AddUserHandler)
user.POST("/edit", UserHandler.EditUserHandler)
user.POST("/delete/:id", UserHandler.DeleteUserHandler)
}
port := viper.GetString("port")
r.Run(port)
}
启动
go build main.go Handler.go
上面简单的实现了一个restfui风格的架构,重在实现思想实现,可能实际运用的时候根据项目的不同有所差异,但是上述的知识基本实现了gin、grom 知识点的整合。