diff --git a/.gitignore b/.gitignore index f4d432a..bfee436 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,8 @@ # Dependency directories (remove the comment below to include it) # vendor/ +go.sum +campus-supervision +logs +.vscode diff --git a/gin/config.go b/gin/config.go new file mode 100644 index 0000000..0ff824b --- /dev/null +++ b/gin/config.go @@ -0,0 +1,11 @@ +package gin + +// GIN 配置 +type Config struct { + RootPath string + Addr string + Port int + Ssl bool + SslPem string + SslKey string +} diff --git a/gin/gin.go b/gin/gin.go new file mode 100644 index 0000000..fb0f08a --- /dev/null +++ b/gin/gin.go @@ -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() + } +} diff --git a/gin/router.go b/gin/router.go new file mode 100644 index 0000000..4f1ccad --- /dev/null +++ b/gin/router.go @@ -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和什么字段改什么 覆盖式 + } + +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..06e1c4d --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/handler/base-handler.go b/handler/base-handler.go new file mode 100644 index 0000000..85ac456 --- /dev/null +++ b/handler/base-handler.go @@ -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 +} diff --git a/handler/super-handler.go b/handler/super-handler.go new file mode 100644 index 0000000..fcaab9a --- /dev/null +++ b/handler/super-handler.go @@ -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, + }) +} diff --git a/logrus.go b/logrus.go new file mode 100644 index 0000000..c0972d1 --- /dev/null +++ b/logrus.go @@ -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 +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..a31a8b9 --- /dev/null +++ b/main.go @@ -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) + +} diff --git a/model/super-model.go b/model/super-model.go new file mode 100644 index 0000000..f26b259 --- /dev/null +++ b/model/super-model.go @@ -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() +// } diff --git a/mysql/mysql.go b/mysql/mysql.go new file mode 100644 index 0000000..b90d31f --- /dev/null +++ b/mysql/mysql.go @@ -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 +} diff --git a/mysql/tables-mysql.go b/mysql/tables-mysql.go new file mode 100644 index 0000000..a7eaa84 --- /dev/null +++ b/mysql/tables-mysql.go @@ -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 +} diff --git a/service/super-service.go b/service/super-service.go new file mode 100644 index 0000000..701c9e5 --- /dev/null +++ b/service/super-service.go @@ -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 +} diff --git a/test.http b/test.http new file mode 100644 index 0000000..8ac9b1e --- /dev/null +++ b/test.http @@ -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" +} \ No newline at end of file