第一版
This commit is contained in:
parent
fa2af29ea3
commit
b2d53e0692
|
|
@ -14,4 +14,8 @@
|
|||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
go.sum
|
||||
|
||||
campus-supervision
|
||||
logs
|
||||
.vscode
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package gin
|
||||
|
||||
// GIN 配置
|
||||
type Config struct {
|
||||
RootPath string
|
||||
Addr string
|
||||
Port int
|
||||
Ssl bool
|
||||
SslPem string
|
||||
SslKey string
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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和什么字段改什么 覆盖式
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
// }
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
Loading…
Reference in New Issue