补充mvc
This commit is contained in:
parent
68861a43f2
commit
048ac6cf39
@ -1 +1,3 @@
|
||||
# mvc 功能分层架构
|
||||
|
||||
PO, 数据库对象
|
20
book/v3/config/README.md
Normal file
20
book/v3/config/README.md
Normal file
@ -0,0 +1,20 @@
|
||||
# 程序的配置管理
|
||||
|
||||
|
||||
## 配置的加载
|
||||
```go
|
||||
// 用于加载配置
|
||||
config.LoadConfigFromYaml(yamlConfigFilePath)
|
||||
```
|
||||
|
||||
## 程序内部如何使用配置
|
||||
```go
|
||||
// Get Config --> ConfigObject
|
||||
config.C().MySQL.Host
|
||||
// config.ConfigObjectInstance
|
||||
```
|
||||
|
||||
## 为你的包添加单元测试
|
||||
|
||||
如何验证我们这个包的 业务逻辑是正确
|
||||
|
86
book/v3/config/config.go
Normal file
86
book/v3/config/config.go
Normal file
@ -0,0 +1,86 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"122.51.31.227/go-course/go18/book/v3/models"
|
||||
"github.com/infraboard/mcube/v2/tools/pretty"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Default() *Config {
|
||||
return &Config{
|
||||
Application: &application{
|
||||
Host: "127.0.0.1",
|
||||
Port: 8080,
|
||||
},
|
||||
MySQL: &mySQL{
|
||||
Host: "127.0.0.1",
|
||||
Port: 3306,
|
||||
DB: "test",
|
||||
Username: "root",
|
||||
Password: "123456",
|
||||
Debug: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 这歌对象就是程序配置
|
||||
// yaml, toml
|
||||
type Config struct {
|
||||
Application *application `toml:"app" yaml:"app" json:"app"`
|
||||
MySQL *mySQL `toml:"mysql" yaml:"mysql" json:"mysql"`
|
||||
}
|
||||
|
||||
func (c *Config) String() string {
|
||||
return pretty.ToJSON(c)
|
||||
}
|
||||
|
||||
// 应用服务
|
||||
type application struct {
|
||||
Host string `toml:"host" yaml:"host" json:"host" env:"HOST"`
|
||||
Port int `toml:"port" yaml:"port" json:"port" env:"PORT"`
|
||||
}
|
||||
|
||||
// db对象也是一个单列模式
|
||||
type mySQL struct {
|
||||
Host string `json:"host" yaml:"host" toml:"host" env:"DATASOURCE_HOST"`
|
||||
Port int `json:"port" yaml:"port" toml:"port" env:"DATASOURCE_PORT"`
|
||||
DB string `json:"database" yaml:"database" toml:"database" env:"DATASOURCE_DB"`
|
||||
Username string `json:"username" yaml:"username" toml:"username" env:"DATASOURCE_USERNAME"`
|
||||
Password string `json:"password" yaml:"password" toml:"password" env:"DATASOURCE_PASSWORD"`
|
||||
Debug bool `json:"debug" yaml:"debug" toml:"debug" env:"DATASOURCE_DEBUG"`
|
||||
|
||||
// gorm db对象, 只需要有1个,不运行重复生成
|
||||
db *gorm.DB
|
||||
// 互斥锁
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func (m *mySQL) GetDB() *gorm.DB {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
|
||||
if m.db == nil {
|
||||
// 初始化数据库
|
||||
// dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
||||
m.Username,
|
||||
m.Password,
|
||||
m.Host,
|
||||
m.Port,
|
||||
m.DB,
|
||||
)
|
||||
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
panic("failed to connect database")
|
||||
}
|
||||
db.AutoMigrate(&models.Book{}) // 自动迁移
|
||||
m.db = db
|
||||
}
|
||||
|
||||
return m.db
|
||||
}
|
26
book/v3/config/config_test.go
Normal file
26
book/v3/config/config_test.go
Normal file
@ -0,0 +1,26 @@
|
||||
package config_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"122.51.31.227/go-course/go18/book/v3/config"
|
||||
)
|
||||
|
||||
func TestLoadConfigFromYaml(t *testing.T) {
|
||||
err := config.LoadConfigFromYaml(fmt.Sprintf("%s/book/v2/application.yaml", os.Getenv("workspaceFolder")))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(config.C())
|
||||
}
|
||||
|
||||
func TestLoadConfigFromEnv(t *testing.T) {
|
||||
os.Setenv("DATASOURCE_HOST", "localhost")
|
||||
err := config.LoadConfigFromEnv()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(config.C())
|
||||
}
|
52
book/v3/config/load.go
Normal file
52
book/v3/config/load.go
Normal file
@ -0,0 +1,52 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/caarlos0/env/v6"
|
||||
"gopkg.in/yaml.v3"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 配置加载
|
||||
// file/env/... ---> Config
|
||||
// 全局一份
|
||||
|
||||
// config 全局变量, 通过函数对我提供访问
|
||||
var config *Config
|
||||
|
||||
func C() *Config {
|
||||
// 没有配置文件怎么办?
|
||||
// 默认配置, 方便开发者
|
||||
if config == nil {
|
||||
config = Default()
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func DB() *gorm.DB {
|
||||
return C().MySQL.GetDB()
|
||||
}
|
||||
|
||||
// 加载配置 把外部配置读到 config全局变量里面来
|
||||
// yaml 文件yaml --> conf
|
||||
func LoadConfigFromYaml(configPath string) error {
|
||||
content, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 默认值
|
||||
config = C()
|
||||
return yaml.Unmarshal(content, config)
|
||||
}
|
||||
|
||||
// 从环境变量读取配置
|
||||
// "github.com/caarlos0/env/v6"
|
||||
func LoadConfigFromEnv() error {
|
||||
config = C()
|
||||
// MYSQL_DB <---> DB
|
||||
// config.MySQL.DB = os.Getenv("MYSQL_DB")
|
||||
return env.Parse(config)
|
||||
}
|
3
book/v3/controllers/README.md
Normal file
3
book/v3/controllers/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# 控制器
|
||||
|
||||
业务处理
|
3
book/v3/handlers/README.md
Normal file
3
book/v3/handlers/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# api handlers
|
||||
|
||||
HTTP RESTful 接口
|
180
book/v3/handlers/book.go
Normal file
180
book/v3/handlers/book.go
Normal file
@ -0,0 +1,180 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"122.51.31.227/go-course/go18/book/v3/config"
|
||||
"122.51.31.227/go-course/go18/book/v3/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var Book = &BookApiHandler{}
|
||||
|
||||
type BookApiHandler struct {
|
||||
}
|
||||
|
||||
func (h *BookApiHandler) Registry(r gin.IRouter) {
|
||||
// Book Restful API
|
||||
// List of books
|
||||
r.GET("/api/books", h.listBook)
|
||||
// Create new book
|
||||
// Body: HTTP Entity
|
||||
r.POST("/api/books", h.createBook)
|
||||
// Get book by book number
|
||||
r.GET("/api/books/:bn", h.getBook)
|
||||
// Update book
|
||||
r.PUT("/api/books/:bn", h.updateBook)
|
||||
// Delete book
|
||||
r.DELETE("/api/books/:bn", h.deleteBook)
|
||||
}
|
||||
|
||||
// 实现后端分页的
|
||||
func (h *BookApiHandler) listBook(ctx *gin.Context) {
|
||||
set := &models.BookSet{}
|
||||
|
||||
// List<*Book>
|
||||
// *Set[T]
|
||||
// types.New[*Book]()
|
||||
|
||||
// 给默认值
|
||||
pn, ps := 1, 20
|
||||
// /api/books?page_number=1&page_size=20
|
||||
pageNumber := ctx.Query("page_number")
|
||||
if pageNumber != "" {
|
||||
pnInt, err := strconv.ParseInt(pageNumber, 10, 64)
|
||||
if err != nil {
|
||||
ctx.JSON(400, gin.H{"code": 400, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
pn = int(pnInt)
|
||||
}
|
||||
|
||||
pageSize := ctx.Query("page_size")
|
||||
if pageSize != "" {
|
||||
psInt, err := strconv.ParseInt(pageSize, 10, 64)
|
||||
if err != nil {
|
||||
ctx.JSON(400, gin.H{"code": 400, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
ps = int(psInt)
|
||||
}
|
||||
|
||||
query := config.DB().Model(&models.Book{})
|
||||
// 关键字过滤
|
||||
kws := ctx.Query("keywords")
|
||||
if kws != "" {
|
||||
// where title like %kws%
|
||||
query = query.Where("title LIKE ?", "%"+kws+"%")
|
||||
}
|
||||
|
||||
// 其他过滤条件
|
||||
|
||||
// select * from books
|
||||
// 通过sql的offset limte 来实现分页
|
||||
// offset (page_number -1) * page_size, limit page_size
|
||||
// 2 offset 20, 20
|
||||
// 3 offset 40, 20
|
||||
// 4 offset 3 * 20, 20
|
||||
offset := (pn - 1) * ps
|
||||
if err := query.Count(&set.Total).Offset(int(offset)).Limit(int(ps)).Find(&set.Items).Error; err != nil {
|
||||
ctx.JSON(500, gin.H{"code": 500, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取总数, 总共多少个, 总共有多少页
|
||||
ctx.JSON(200, set)
|
||||
}
|
||||
|
||||
func (h *BookApiHandler) createBook(ctx *gin.Context) {
|
||||
// payload, err := io.ReadAll(ctx.Request.Body)
|
||||
// if err != nil {
|
||||
// ctx.JSON(400, gin.H{"code": 400, "message": err.Error()})
|
||||
// return
|
||||
// }
|
||||
// defer ctx.Request.Body.Close()
|
||||
// // {"title": "Go语言"}
|
||||
|
||||
// c.Request.Header.Get(key)
|
||||
// ctx.GetHeader("Authincation")
|
||||
|
||||
// new(Book)
|
||||
bookSpecInstance := &models.BookSpec{}
|
||||
// // 通过JSON的 Struct Tag
|
||||
// // bookInstance.Title = "Go语言"
|
||||
// if err := json.Unmarshal(payload, bookInstance); err != nil {
|
||||
// ctx.JSON(400, gin.H{"code": 400, "message": err.Error()})
|
||||
// return
|
||||
// }
|
||||
// 获取到bookInstance
|
||||
// 参数是不是为空
|
||||
if err := ctx.BindJSON(bookSpecInstance); err != nil {
|
||||
ctx.JSON(400, gin.H{"code": 400, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 有没有能够检查某个字段是否是必须填
|
||||
// Gin 集成 validator这个库, 通过 struct tag validate 来表示这个字段是否允许为空
|
||||
// validate:"required"
|
||||
// 在数据Bind的时候,这个逻辑会自动运行
|
||||
// if bookSpecInstance.Author == "" {
|
||||
// ctx.JSON(400, gin.H{"code": 400, "message": err.Error()})
|
||||
// return
|
||||
// }
|
||||
|
||||
bookInstance := &models.Book{BookSpec: *bookSpecInstance}
|
||||
|
||||
// 数据入库(Grom), 补充自增Id的值
|
||||
if err := config.DB().Save(bookInstance).Error; err != nil {
|
||||
ctx.JSON(400, gin.H{"code": 500, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 返回响应
|
||||
ctx.JSON(http.StatusCreated, bookInstance)
|
||||
}
|
||||
|
||||
func (h *BookApiHandler) getBook(ctx *gin.Context) {
|
||||
bookInstance := &models.Book{}
|
||||
// 需要从数据库中获取一个对象
|
||||
if err := config.DB().Where("id = ?", ctx.Param("bn")).Take(bookInstance).Error; err != nil {
|
||||
ctx.JSON(400, gin.H{"code": 500, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(200, bookInstance)
|
||||
}
|
||||
|
||||
func (h *BookApiHandler) updateBook(ctx *gin.Context) {
|
||||
bnStr := ctx.Param("bn")
|
||||
bn, err := strconv.ParseInt(bnStr, 10, 64)
|
||||
if err != nil {
|
||||
ctx.JSON(400, gin.H{"code": 400, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 读取body里面的参数
|
||||
bookInstance := &models.Book{
|
||||
Id: uint(bn),
|
||||
}
|
||||
// 获取到bookInstance
|
||||
if err := ctx.BindJSON(&bookInstance.BookSpec); err != nil {
|
||||
ctx.JSON(400, gin.H{"code": 400, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := config.DB().Where("id = ?", bookInstance.Id).Updates(bookInstance).Error; err != nil {
|
||||
ctx.JSON(400, gin.H{"code": 400, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(200, bookInstance)
|
||||
}
|
||||
|
||||
func (h *BookApiHandler) deleteBook(ctx *gin.Context) {
|
||||
if err := config.DB().Where("id = ?", ctx.Param("bn")).Delete(&models.Book{}).Error; err != nil {
|
||||
ctx.JSON(400, gin.H{"code": 400, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusNoContent, "ok")
|
||||
}
|
20
book/v3/main.go
Normal file
20
book/v3/main.go
Normal file
@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"122.51.31.227/go-course/go18/book/v3/handlers"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
server := gin.Default()
|
||||
|
||||
handlers.Book.Registry(server)
|
||||
|
||||
if err := server.Run(":8080"); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
1
book/v3/models/README.md
Normal file
1
book/v3/models/README.md
Normal file
@ -0,0 +1 @@
|
||||
# 数据模型
|
30
book/v3/models/book.go
Normal file
30
book/v3/models/book.go
Normal file
@ -0,0 +1,30 @@
|
||||
package models
|
||||
|
||||
type BookSet struct {
|
||||
// 总共多少个
|
||||
Total int64 `json:"total"`
|
||||
// book清单
|
||||
Items []*Book `json:"items"`
|
||||
}
|
||||
|
||||
type Book struct {
|
||||
// 对象Id
|
||||
Id uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
|
||||
BookSpec
|
||||
}
|
||||
|
||||
type BookSpec struct {
|
||||
// type 用于要使用gorm 来自动创建和更新表的时候 才需要定义
|
||||
Title string `json:"title" gorm:"column:title;type:varchar(200)" validate:"required"`
|
||||
Author string `json:"author" gorm:"column:author;type:varchar(200);index" validate:"required"`
|
||||
Price float64 `json:"price" gorm:"column:price" validate:"required"`
|
||||
// bool false
|
||||
// nil 是零值, false
|
||||
IsSale *bool `json:"is_sale" gorm:"column:is_sale"`
|
||||
}
|
||||
|
||||
// books
|
||||
func (b *Book) TableName() string {
|
||||
return "books"
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user