第一版

This commit is contained in:
wyh 2023-12-29 12:58:20 +08:00
parent fa2af29ea3
commit b2d53e0692
14 changed files with 613 additions and 0 deletions

4
.gitignore vendored
View File

@ -14,4 +14,8 @@
# Dependency directories (remove the comment below to include it)
# vendor/
go.sum
campus-supervision
logs
.vscode

11
gin/config.go Normal file
View File

@ -0,0 +1,11 @@
package gin
// GIN 配置
type Config struct {
RootPath string
Addr string
Port int
Ssl bool
SslPem string
SslKey string
}

71
gin/gin.go Normal file
View File

@ -0,0 +1,71 @@
package gin
import (
"fmt"
"log"
"net/http"
"strconv"
"time"
"github.com/sirupsen/logrus"
"github.com/gin-gonic/gin"
"github.com/unrolled/secure"
)
func Service(conf *Config) {
if conf == nil {
conf = &Config{}
}
if conf.RootPath == "" {
conf.RootPath = "/"
}
if conf.Addr == "" {
conf.Addr = "0.0.0.0"
}
if conf.Port == 0 {
conf.Port = 8080
}
go func() {
router := gin.New()
routerSetup(router, &conf.RootPath)
if conf.Ssl {
router.Use(tlsHandler(conf))
}
s := &http.Server{
Addr: fmt.Sprintf("%s:%d", conf.Addr, conf.Port),
Handler: router,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
logrus.Printf("start service on %s", fmt.Sprintf("%s:%d", conf.Addr, conf.Port))
if conf.Ssl {
log.Fatal(s.ListenAndServeTLS(conf.SslPem, conf.SslKey))
} else {
log.Fatal(s.ListenAndServe())
}
}()
}
func tlsHandler(conf *Config) gin.HandlerFunc {
return func(c *gin.Context) {
secureMiddleware := secure.New(secure.Options{
SSLRedirect: true,
SSLHost: ":" + strconv.Itoa(conf.Port),
})
err := secureMiddleware.Process(c.Writer, c.Request)
// If there was an error, do not continue.
if err != nil {
return
}
c.Next()
}
}

21
gin/router.go Normal file
View File

@ -0,0 +1,21 @@
package gin
import (
"github.com/gin-gonic/gin"
"myschools.me/tcq/campus-supervision/handler"
)
// 路由配置
func routerSetup(router *gin.Engine, rootpath *string) {
router.Use(gin.Recovery())
r := router.Group(*rootpath)
{
r.POST("/add", handler.SuperAdd) //json输入 返回id
r.GET("/list") //分页形式数据库数据
r.GET("/detail") //返回json
r.POST("/del") //删除数据
r.POST("/save") //给你id和什么字段改什么 覆盖式
}
}

52
go.mod Normal file
View File

@ -0,0 +1,52 @@
module myschools.me/tcq/campus-supervision
go 1.21.4
toolchain go1.21.5
require (
github.com/gin-gonic/gin v1.9.1
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
github.com/sirupsen/logrus v1.9.3
github.com/unrolled/secure v1.14.0
gorm.io/driver/mysql v1.5.2
gorm.io/gorm v1.25.5
gorm.io/plugin/dbresolver v1.5.0
)
require (
github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jonboulle/clockwork v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/lestrrat-go/strftime v1.0.6 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

42
handler/base-handler.go Normal file
View File

@ -0,0 +1,42 @@
package handler
import (
"errors"
"fmt"
"net/url"
"reflect"
)
// 解析结构体数据写入到map中
func inspectStruct(u interface{}) (url.Values, error) {
values := url.Values{}
typev := reflect.TypeOf(u)
if typev.Kind() != reflect.Struct {
return nil, errors.New("typev.Kind() is not a reflect.Struct")
}
v := reflect.ValueOf(u)
for i := 0; i < typev.NumField(); i++ {
field := v.FieldByName(typev.Field(i).Name)
// typev := reflect.TypeOf(u).FieldByName()
switch field.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
values.Add(typev.Field(i).Name, fmt.Sprintf("%d", field.Int()))
// fmt.Printf("field:%s type:%s value:%d\n", typev.Field(i).Name, field.Type().Name(), field.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
values.Add(typev.Field(i).Name, fmt.Sprintf("%d", field.Uint()))
// fmt.Printf("field:%s type:%s value:%d\n", typev.Field(i).Name, field.Type().Name(), field.Uint())
case reflect.Bool:
values.Add(typev.Field(i).Name, fmt.Sprintf("%v", field.Bool()))
// fmt.Printf("field:%s type:%s value:%t\n", typev.Field(i).Name, field.Type().Name(), field.Bool())
case reflect.String:
values.Add(typev.Field(i).Name, field.String())
// fmt.Printf("field:%s type:%s value:%q\n", typev.Field(i).Name, field.Type().Name(), field.String())
// default:
// fmt.Printf("field:%s unhandled kind:%s\n", typev.Field(i).Name, field.Kind())
}
}
return values, nil
}

41
handler/super-handler.go Normal file
View File

@ -0,0 +1,41 @@
package handler
import (
"encoding/json"
"io"
"github.com/gin-gonic/gin"
"myschools.me/tcq/campus-supervision/service"
)
func SuperAdd(c *gin.Context) {
var req map[string]interface{}
// c.Request.Body
b, err := io.ReadAll(c.Request.Body)
if err != nil {
c.JSON(401, gin.H{
"err": err.Error(),
})
}
if err := json.Unmarshal(b, &req); err != nil {
c.JSON(401, gin.H{
"err": err.Error(),
})
}
str, err := json.Marshal(req)
if err != nil {
c.JSON(401, gin.H{
"err": err.Error(),
})
}
res, err := service.SuperAdd(str)
if err != nil {
c.JSON(500, gin.H{
"err": err.Error(),
})
}
c.JSON(200, gin.H{
"Res": *res,
})
}

50
logrus.go Normal file
View File

@ -0,0 +1,50 @@
package main
import (
"fmt"
"os"
"time"
rotatelogrus "github.com/lestrrat-go/file-rotatelogs"
"github.com/rifflock/lfshook"
logrus "github.com/sirupsen/logrus"
)
// 日志初始化
func init() {
logrus.SetLevel(logrus.DebugLevel)
logrus.AddHook(newLfsHook(24))
}
func newLfsHook(maxRemainCnt uint) logrus.Hook {
//检查与创建日志文件夹
_, err := os.Stat("logs")
if os.IsNotExist(err) {
os.Mkdir("logs", 0755)
}
logrusName := fmt.Sprintf(`logs/%s`, "campus-supervision")
writer, err := rotatelogrus.New(
logrusName+"%Y%m%d.log",
rotatelogrus.WithLinkName(logrusName),
rotatelogrus.WithRotationTime(24*time.Hour),
rotatelogrus.WithRotationCount(maxRemainCnt),
)
if err != nil {
panic("config local file system for logrusger error: " + err.Error())
}
logrus.SetLevel(logrus.DebugLevel)
lfsHook := lfshook.NewHook(lfshook.WriterMap{
logrus.DebugLevel: writer,
logrus.InfoLevel: writer,
logrus.WarnLevel: writer,
logrus.ErrorLevel: writer,
logrus.FatalLevel: writer,
logrus.PanicLevel: writer,
}, &logrus.TextFormatter{DisableColors: true})
return lfsHook
}

51
main.go Normal file
View File

@ -0,0 +1,51 @@
package main
import (
"context"
"os"
"os/signal"
"time"
"github.com/sirupsen/logrus"
"myschools.me/tcq/campus-supervision/gin"
"myschools.me/tcq/campus-supervision/mysql"
)
func main() {
// mysql初始化
mysql.Init(&mysql.Config{
ConnString: os.Getenv("MYSQL_HOST"),
ConnMaxLifetime: 5,
MaxIdleConns: 5,
MaxOpenConns: 500,
InitTable: true,
})
// table init
if err := mysql.InitTable(); err != nil {
logrus.WithFields(logrus.Fields{
"func": "main",
}).Warnf("mysql.InitTable(): %s", err.Error())
panic(err)
}
//gin初始化,基于docker部署不再需要调整默认值(80)
gin.Service(&gin.Config{
RootPath: "/api",
Addr: "0.0.0.0",
Port: 8080,
})
// 服务停止相应
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
_, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
logrus.WithFields(logrus.Fields{
"func": "main",
}).Infof("campus-srv service shutting down")
os.Exit(0)
}

29
model/super-model.go Normal file
View File

@ -0,0 +1,29 @@
package model
type Super struct {
ID uint `gorm:"primarykey"`
Content []byte `gorm:"type:json;not null"`
}
// type JSON json.RawMessage
// // 实现 sql.Scanner 接口Scan 将 value 扫描至 Jsonb
// func (j *JSON) Scan(value interface{}) error {
// bytes, ok := value.([]byte)
// if !ok {
// return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
// }
// result := json.RawMessage{}
// err := json.Unmarshal(bytes, &result)
// *j = JSON(result)
// return err
// }
// // 实现 driver.Valuer 接口Value 返回 json value
// func (j JSON) Value() (driver.Value, error) {
// if len(j) == 0 {
// return nil, nil
// }
// return json.RawMessage(j).MarshalJSON()
// }

90
mysql/mysql.go Normal file
View File

@ -0,0 +1,90 @@
package mysql
import (
"errors"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"gorm.io/plugin/dbresolver"
)
var (
_db *gorm.DB
_conf *Config
)
type Config struct {
ConnString string
ConnMaxLifetime int64 //ConnMaxLifetime 最大连接时间,单位:小时
MaxIdleConns int
MaxOpenConns int
InitTable bool
}
// Init mysql初始化
func Init(config *Config) {
if config == nil {
config = &Config{
MaxIdleConns: 10,
MaxOpenConns: 100,
}
}
if config.ConnString == "" {
config.ConnString = "root:root@tcp(127.0.0.1:3306)/mysql?charset=utf8&parseTime=True&loc=Local"
}
if config.ConnMaxLifetime < 1 {
config.ConnMaxLifetime = 1
}
if config.ConnMaxLifetime > 6 {
config.ConnMaxLifetime = 6
}
if config.MaxIdleConns < 1 {
config.MaxIdleConns = 1
}
if config.MaxIdleConns > 50 {
config.MaxIdleConns = 50
}
if config.MaxOpenConns < 1 {
config.MaxOpenConns = 1
}
if config.MaxOpenConns > 500 {
config.MaxOpenConns = 500
}
_conf = config
}
// 创建实例
func NewInstance() (*gorm.DB, error) {
if _db != nil {
return _db, nil
}
if _conf == nil {
return nil, errors.New("组件未初始化,请执行Init!")
}
var err error
_db, err = gorm.Open(mysql.Open(_conf.ConnString), &gorm.Config{
SkipDefaultTransaction: true,
Logger: logger.Default.LogMode(logger.Silent),
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
})
if err != nil {
return nil, err
}
_db.Use(
dbresolver.Register(dbresolver.Config{
Sources: []gorm.Dialector{mysql.Open(_conf.ConnString)},
Replicas: []gorm.Dialector{mysql.Open(_conf.ConnString)},
Policy: dbresolver.RandomPolicy{},
}).SetConnMaxIdleTime(time.Hour).
SetConnMaxLifetime(time.Duration(_conf.ConnMaxLifetime) * time.Hour).
SetMaxIdleConns(_conf.MaxIdleConns).
SetMaxOpenConns(_conf.MaxOpenConns))
return _db, nil
}

21
mysql/tables-mysql.go Normal file
View File

@ -0,0 +1,21 @@
package mysql
import "myschools.me/tcq/campus-supervision/model"
func InitTable() error {
//不初始化表时返回
// if !_conf.InitTable {
// return nil
// }
db, err := NewInstance()
if err != nil {
panic(err)
}
err = db.AutoMigrate(&model.Super{})
if err != nil {
return err
}
return nil
}

82
service/super-service.go Normal file
View File

@ -0,0 +1,82 @@
package service
import (
"errors"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
"myschools.me/tcq/campus-supervision/model"
"myschools.me/tcq/campus-supervision/mysql"
)
func SuperAdd(data []byte) (*uint, error) {
db, _ := mysql.NewInstance()
super := &model.Super{
Content: data,
}
if err := db.Create(&super).Error; err != nil {
logrus.WithFields(logrus.Fields{
"func": "SuperAdd",
}).Warningf("err: %s", err.Error())
return nil, err
}
return &super.ID, nil
}
func SuperList(index, row int) ([]*model.Super, error) {
db, _ := mysql.NewInstance()
var count int64
supers := make([]*model.Super, 0)
if err := db.Count(&count).Offset((index - 1) * row).Limit(row).Find(&supers).Error; err != nil {
logrus.WithFields(logrus.Fields{
"func": "SuperList",
}).Warningf("err: %s", err.Error())
return nil, err
}
return supers, nil
}
func SuperDetail(id uint) (*model.Super, error) {
db, _ := mysql.NewInstance()
super := &model.Super{}
if err := db.Where("id=?", id).First(super).Error; err != nil {
if err != gorm.ErrRecordNotFound {
logrus.WithFields(logrus.Fields{
"func": "SuperDetail",
}).Warningf("err: %s", err.Error())
return nil, err
}
}
if super.ID == 0 {
return nil, errors.New("无效id")
}
return super, nil
}
func SuperDetele(id uint) (bool, error) {
db, _ := mysql.NewInstance()
if err := db.Where("id=?", id).Delete(&model.Super{}).Error; err != nil {
if err != gorm.ErrRecordNotFound {
logrus.WithFields(logrus.Fields{
"func": "SuperDetele",
}).Warningf("err: %s", err.Error())
return false, err
}
}
return true, nil
}
func SuperSave(id uint, data []byte) (bool, error) {
db, _ := mysql.NewInstance()
super := &model.Super{
Content: data,
}
if err := db.Where(&model.Super{ID: id}).Select("content").Updates(&super).Error; err != nil {
logrus.WithFields(logrus.Fields{
"func": "SuperSave",
}).Warningf("err: %s", err.Error())
return false, err
}
return true, nil
}

48
test.http Normal file
View File

@ -0,0 +1,48 @@
@url=http://localhost:8080/api
### add
POST {{url}}/add HTTP/1.1
Content-Type: application/json
{
"NIHAO":"SHFD",
"DFHFS":"fff"
}
### list
GET {{url}}/add HTTP/1.1
Content-Type: application/json
{
"NIHAO":"SHFD",
"DFHFS":"fff"
}
### detail
GET {{url}}/detail HTTP/1.1
Content-Type: application/json
{
"NIHAO":"SHFD",
"DFHFS":"fff"
}
### delete
POST {{url}}/delete HTTP/1.1
Content-Type: application/json
{
"NIHAO":"SHFD",
"DFHFS":"fff"
}
### save
POST {{url}}/save HTTP/1.1
Content-Type: application/json
{
"ID":1,
"NIHAO":"SHFD",
"DFHFS":"fff"
}