From e45f0591ab12167673159038d076ea512f9f9b97 Mon Sep 17 00:00:00 2001 From: workabee <2950914812@qq.com> Date: Thu, 1 Feb 2024 13:44:54 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9B=BD=E5=B1=95=E6=8A=A5=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/app.go | 56 ++++++ cmd/wire.go | 17 ++ cmd/wire_gen.go | 23 +++ conf/dev/dubbogo.yaml | 5 +- conf/prod/dubbogo.yaml | 5 +- conf/test/dubbogo.yaml | 5 +- go.mod | 26 ++- internal/config/app_config.go | 82 +++++++++ internal/model/baidu.go | 12 ++ internal/model/common.go | 18 ++ internal/msg/message.go | 9 +- internal/response/response.go | 158 +++++++++++++++++ pkg/logic/baidu.go | 58 +++++++ pkg/logic/upload.go | 119 +++++++++++++ pkg/router/router.go | 55 ++---- pkg/service/common/common.go | 309 +++++++++++++++++++++++++++++++++ pkg/service/cron.go | 14 ++ pkg/service/init.go | 5 +- pkg/service/register_record.go | 60 +++++++ pkg/utils/picture.go | 82 +++++++++ pkg/utils/untils.go | 47 +++++ 21 files changed, 1117 insertions(+), 48 deletions(-) create mode 100644 cmd/app.go create mode 100644 cmd/wire.go create mode 100644 cmd/wire_gen.go create mode 100644 internal/model/baidu.go create mode 100644 pkg/logic/baidu.go create mode 100644 pkg/logic/upload.go create mode 100644 pkg/service/common/common.go create mode 100644 pkg/service/cron.go create mode 100644 pkg/utils/picture.go create mode 100644 pkg/utils/untils.go diff --git a/cmd/app.go b/cmd/app.go new file mode 100644 index 0000000..64e0f88 --- /dev/null +++ b/cmd/app.go @@ -0,0 +1,56 @@ +package main + +import ( + "fmt" + appConfig "github.com/exhibition-main/internal/config" + "github.com/exhibition-main/pkg/router" + "github.com/exhibition-main/pkg/service" + "github.com/exhibition-main/pkg/tracing" + "github.com/exhibition-main/pkg/utils" + "github.com/go-redis/redis" + + "github.com/robfig/cron/v3" + "github.com/streadway/amqp" + "go.uber.org/zap" +) + +type App struct { + lg *zap.Logger + RedisClient *redis.Client + JaegerTracer *tracing.JaegerProvider + RabbitMqLink *amqp.Connection +} + +func NewApp(lg *zap.Logger, RedisClient *redis.Client, JaegerTracer *tracing.JaegerProvider) *App { + return &App{ + lg: lg, + RedisClient: RedisClient, + JaegerTracer: JaegerTracer, + } +} + +func main() { + _, err := InitApp() + if err != nil { + panic(err) + } + modules() + r := router.NewRouter() + if appConfig.Data.System.IsHttps { + _ = r.RunTLS(fmt.Sprintf(":%d", appConfig.Data.System.Port), appConfig.Data.System.CertPath, appConfig.Data.System.KeyPath) + } else { + _ = r.Run(fmt.Sprintf(":%d", appConfig.Data.System.Port)) + } + select {} +} + +func modules() { + if err := utils.InitTrans("zh"); err != nil { + panic(err) + } + if appConfig.Data.System.Cron { + c := cron.New() + service.Task(c) + c.Start() + } +} diff --git a/cmd/wire.go b/cmd/wire.go new file mode 100644 index 0000000..2b2653b --- /dev/null +++ b/cmd/wire.go @@ -0,0 +1,17 @@ +// go:build wireinject +//go:build wireinject +// +build wireinject + +package main + +import ( + "github.com/exhibition-main/pkg/cache" + "github.com/exhibition-main/pkg/logger" + "github.com/exhibition-main/pkg/tracing" + "github.com/google/wire" +) + +func InitApp() (*App, error) { + wire.Build(logger.Provider, cache.RedisProvider, tracing.Provider, NewApp) + return &App{}, nil +} diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go new file mode 100644 index 0000000..769ace1 --- /dev/null +++ b/cmd/wire_gen.go @@ -0,0 +1,23 @@ +// Code generated by Wire. DO NOT EDIT. + +//go:generate go run github.com/google/wire/cmd/wire +//go:build !wireinject +// +build !wireinject + +package main + +import ( + "github.com/exhibition-main/pkg/cache" + "github.com/exhibition-main/pkg/logger" + "github.com/exhibition-main/pkg/tracing" +) + +// Injectors from wire.go: + +func InitApp() (*App, error) { + zapLogger := logger.ZapInit() + client := cache.NewRedis() + jaegerProvider := tracing.NewTracing() + app := NewApp(zapLogger, client, jaegerProvider) + return app, nil +} diff --git a/conf/dev/dubbogo.yaml b/conf/dev/dubbogo.yaml index 2709a4c..619aa43 100644 --- a/conf/dev/dubbogo.yaml +++ b/conf/dev/dubbogo.yaml @@ -11,7 +11,10 @@ dubbo: max-call-send-msg-size: 8000000 max-call-recv-msg-size: 8000000 references: - + ExhibitionClientImpl: + protocol: tri + filter: tps + interface: com.fontree.microservices.common.Exhibition logger: zap-config: level: error # 日志级别 diff --git a/conf/prod/dubbogo.yaml b/conf/prod/dubbogo.yaml index deb1f2d..92a4918 100644 --- a/conf/prod/dubbogo.yaml +++ b/conf/prod/dubbogo.yaml @@ -11,7 +11,10 @@ dubbo: max-call-send-msg-size: 8000000 max-call-recv-msg-size: 8000000 references: - + ExhibitionClientImpl: + protocol: tri + filter: tps + interface: com.fontree.microservices.common.Exhibition logger: zap-config: level: error # 日志级别 diff --git a/conf/test/dubbogo.yaml b/conf/test/dubbogo.yaml index deb1f2d..92a4918 100644 --- a/conf/test/dubbogo.yaml +++ b/conf/test/dubbogo.yaml @@ -11,7 +11,10 @@ dubbo: max-call-send-msg-size: 8000000 max-call-recv-msg-size: 8000000 references: - + ExhibitionClientImpl: + protocol: tri + filter: tps + interface: com.fontree.microservices.common.Exhibition logger: zap-config: level: error # 日志级别 diff --git a/go.mod b/go.mod index fd579eb..df78874 100644 --- a/go.mod +++ b/go.mod @@ -2,12 +2,25 @@ module github.com/exhibition-main go 1.20 +replace ( + github.com/fonchain_enterprise/utils/aes => ../utils/aes + github.com/fonchain_enterprise/utils/chain => ../utils/chain + github.com/fonchain_enterprise/utils/feie => ../utils/feie + github.com/fonchain_enterprise/utils/ipAddrQuery => ../utils/ipAddrQuery + github.com/fonchain_enterprise/utils/jwt => ../utils/jwt + github.com/fonchain_enterprise/utils/logger => ../utils/logger + github.com/fonchain_enterprise/utils/objstorage => ../utils/objstorage + github.com/fonchain_enterprise/utils/rand => ../utils/rand + github.com/fonchain_enterprise/utils/utils => ../utils/utils +) + require ( dubbo.apache.org/dubbo-go/v3 v3.0.5 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dubbogo/gost v1.13.2 github.com/dubbogo/grpc-go v1.42.10 github.com/dubbogo/triple v1.2.2-rc2 + github.com/fonchain_enterprise/utils/objstorage v0.0.0-00010101000000-000000000000 github.com/gin-contrib/gzip v0.0.6 github.com/gin-contrib/pprof v1.4.0 github.com/gin-gonic/gin v1.9.0 @@ -19,12 +32,17 @@ require ( github.com/google/wire v0.5.0 github.com/mwitkow/go-proto-validators v0.3.2 github.com/natefinch/lumberjack v2.0.0+incompatible + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/opentracing/opentracing-go v1.2.0 github.com/robfig/cron/v3 v3.0.1 + github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd + github.com/satori/go.uuid v1.2.0 github.com/spf13/viper v1.7.0 github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271 + github.com/u2takey/ffmpeg-go v0.5.0 github.com/uber/jaeger-client-go v2.30.0+incompatible go.uber.org/zap v1.21.0 + golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 google.golang.org/protobuf v1.28.1 ) @@ -36,8 +54,12 @@ require ( github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 // indirect github.com/alibaba/sentinel-golang v1.0.4 // indirect github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704 // indirect + github.com/aliyun/aliyun-oss-go-sdk v2.2.4+incompatible // indirect github.com/apache/dubbo-getty v1.4.9-0.20221022181821-4dc6252ce98c // indirect github.com/apache/dubbo-go-hessian2 v1.11.5 // indirect + github.com/aws/aws-sdk-go v1.38.20 // indirect + github.com/baidubce/bce-sdk-go v0.9.123 // indirect + github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect @@ -112,7 +134,7 @@ require ( github.com/prometheus/statsd_exporter v0.21.0 // indirect github.com/shirou/gopsutil/v3 v3.22.2 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/spf13/afero v1.1.2 // indirect + github.com/spf13/afero v1.2.2 // indirect github.com/spf13/cast v1.3.0 // indirect github.com/spf13/jwalterweatherman v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -120,6 +142,7 @@ require ( github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/numcpus v0.4.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/u2takey/go-utils v0.3.1 // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect github.com/ugorji/go/codec v1.2.9 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect @@ -138,6 +161,7 @@ require ( golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect golang.org/x/sys v0.5.0 // indirect golang.org/x/text v0.7.0 // indirect + golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect google.golang.org/appengine v1.6.6 // indirect google.golang.org/genproto v0.0.0-20220504150022-98cd25cafc72 // indirect google.golang.org/grpc v1.51.0 // indirect diff --git a/internal/config/app_config.go b/internal/config/app_config.go index 2e26893..dcdc608 100644 --- a/internal/config/app_config.go +++ b/internal/config/app_config.go @@ -10,6 +10,79 @@ import ( var Data = new(AppConfig) +var ( + AppMode string + HttpPort string + Key string + Cert string + IsHttps bool + RunHttps bool + Domain string + OssDomain string + ServerDM string + Env string + DriverUrl string + AppointUrl string + MaterialHost string + BosAk string + BosSk string + BosBucketName string + BosBaseDir string + BosUrl string + BosHttp string + BosDomain string + ProjectMapDir string + JaegerHost string + JaegerOpen bool + Cron bool + DingAccessToken string + DingUrl string + HolidayID string + HolidaySecret string + HolidaySingleUrl string + HolidayMultiUrl string + + ErpAk string + ErpSk string + + // rabbitmq + RabbitmqUser string + RabbitmqPassword string + RabbitmqHost string + RabbitmqPort int + RabbitmqVhost string + RabbitmqVhostArtwork string + + RedisDB int + RedisAddr string + RedisPw string + + //shop配置 + ShopHost string + //ShopDetail string + + // 地图 + MapAppCode string + // es + ElasticHost string + SendIndexesUrl string + ChatGptHost string + + ApiHost string + + Level string + Filename string + MaxSize string + MaxAge string + MaxBackups string + + YearGiftCheckHost string + Aliyun struct { + AccessKeyId string + AccessKeySecret string + } +) + type AppConfig struct { System struct { Domain string @@ -42,6 +115,15 @@ type AppConfig struct { Key string Expire int32 } + Bos struct { + AccessKeyId string + AccessKeySecret string + Endpoint string + Host string + BucketName string + BosBaseDir string + CdnHost string + } } func GetConf() (iniConf string, err error) { diff --git a/internal/model/baidu.go b/internal/model/baidu.go new file mode 100644 index 0000000..1d0c1a9 --- /dev/null +++ b/internal/model/baidu.go @@ -0,0 +1,12 @@ +package model + +type BaiduAccessToken struct { + Refresh_token string `json:"refresh_token"` + Expires_in uint64 `json:"expires_in"` + Scope string `json:"scope"` + Session_key string `json:"session_key"` + Access_token string `json:"access_token"` + Session_secret string `json:"session_secret"` + Error string `json:"error"` + Error_description string `json:"error_description"` +} diff --git a/internal/model/common.go b/internal/model/common.go index 18e8e0d..da4d341 100644 --- a/internal/model/common.go +++ b/internal/model/common.go @@ -6,10 +6,28 @@ const ( MODE_ENV = "MODE_ENV" ) +const ( + HttpType = "http://" + HttpsType = "https://" + TmpArtworkDir = "./runtime/tmp/artworks" + TmpArtistDir = "./runtime/tmp/artists" + MediaPath = "./runtime/" + TplPath = "./data/" + ImgActionRotate = "rotate" //旋转 + MediaTypeVideo = "video" + MediaTypeImage = "video" +) + type Response struct { Status int `json:"status"` Data interface{} `json:"data"` Msg string `json:"msg"` + Code int `json:"code"` + Error error `json:"error"` + Err string `json:"err"` + Keys []string `json:"keys"` + Mark string `json:"mark,omitempty"` + Page *PageInfo `json:"page,omitempty"` } type PageInfo struct { diff --git a/internal/msg/message.go b/internal/msg/message.go index dff71ed..c3f5b95 100644 --- a/internal/msg/message.go +++ b/internal/msg/message.go @@ -24,5 +24,12 @@ const ( NEED_LOGIN = "请先登录" - INVALID_TOKEN = "token不合法" + INVALID_TOKEN = "token不合法" + INVALID_PARAMS = "参数错误" + JSON_UNMARSHAL = "Json解析错误" + + ERROR_BAIDU_IMAGE = "图片审核不通过" + ERROR_BAIDU_FAIL = "图片审核失败" + + ErrorUploadBos ) diff --git a/internal/response/response.go b/internal/response/response.go index c0268b6..6986c09 100644 --- a/internal/response/response.go +++ b/internal/response/response.go @@ -1,9 +1,12 @@ package response import ( + "fmt" "github.com/exhibition-main/internal/model" + "github.com/exhibition-main/internal/msg" "github.com/gin-gonic/gin" "net/http" + "reflect" ) func ResponseMsg(c *gin.Context, httpCode int, Resp model.Response) { @@ -25,3 +28,158 @@ func ResponseQuickMsg(c *gin.Context, Status uint8, Msg string, Data interface{} Data: Data, }) } + +func Success(c *gin.Context, datas ...interface{}) { + var data any + if datas != nil { + data = datas[0] + } + dataFieldStr, _ := c.Get("dataField") + key := []string{"*"} + + if dataFieldStr != "" && dataFieldStr != nil { + dataField := dataFieldStr.([]string) + key = dataField + + if len(dataField) == 1 && dataField[0] == "*" { //不做处理 + + } else if len(dataField) >= 1 { + isList := isList(reflect.TypeOf(data).Elem()) + + // 是列表返回 + if isList == false { + ChangeFromReflect(data, dataField) + } else { + ChangeRawFromReflect(data, dataField) + } + } + } + + c.JSON(http.StatusOK, model.Response{ + Status: msg.Ok, + Code: msg.StatusOK, + Data: data, + // Keys: dataField, + Keys: key, + }) + c.Abort() +} + +func IsContainString(items []string, item string) bool { + for _, eachItem := range items { + if eachItem == item { + return true + } + } + return false +} + +func ChangeFromReflect(data interface{}, dataField []string) { + m := reflect.TypeOf(data).Elem() + for i := 0; i < m.NumField(); i++ { + fieldType := m.Field(i) + if IsContainString(dataField, fieldType.Name) == false { + field := reflect.ValueOf(data).Elem().FieldByName(fieldType.Name) + + switch m.Field(i).Type.Name() { + case "int": + if field.CanSet() { + field.SetInt(0) + } + case "uint": + if field.CanSet() { + field.SetUint(0) + } + case "uint64": + if field.CanSet() { + field.SetUint(0) + } + case "string": + if field.CanSet() { + field.SetString("") + } + default: + if reflect.ValueOf(data).Elem().FieldByName(fieldType.Name).CanAddr() { + if reflect.ValueOf(data).Elem().FieldByName(fieldType.Name).Kind() == reflect.Slice { + field := reflect.ValueOf(data).Elem().FieldByName(fieldType.Name) + + if field.CanSet() { + field.Set(reflect.MakeSlice(field.Type(), 0, 0)) + } + } + + if reflect.ValueOf(data).Elem().FieldByName(fieldType.Name).Kind() == reflect.Struct { + } + + if reflect.ValueOf(data).Elem().FieldByName(fieldType.Name).Kind() == reflect.Ptr { + field := reflect.ValueOf(data).Elem().FieldByName(fieldType.Name) + if !reflect.ValueOf(data).Elem().FieldByName(fieldType.Name).IsNil() { + fieldType := reflect.ValueOf(data).Elem().FieldByName(fieldType.Name).Elem().Type() + + if field.CanSet() { + field.Set(reflect.New(fieldType)) + } + } + + } + } + + } + } + } + +} + +func ChangeRawFromReflect(data interface{}, dataField []string) { + + sliceContent := reflect.ValueOf(data).Elem().FieldByName("Data") + for i := 0; i < sliceContent.Len(); i++ { + sliceContent.Index(i) + ChangeFromReflect(sliceContent.Index(i).Interface(), dataField) + } + +} + +func isList(m reflect.Type) bool { + dataExist := false + + for i := 0; i < m.NumField(); i++ { + + fieldType := m.Field(i) + + if fieldType.Name == "Data" { + getValue := reflect.ValueOf(fieldType) + + if getValue.Kind() != reflect.Slice { + dataExist = true + } + + break + } + } + + return dataExist +} + +// Error 统一错误返回 +func Error(c *gin.Context, code int, err error, message ...string) { + + fmt.Println("err:", err) + status := msg.Fail + if code == msg.StatusUnauthorized { + status = msg.StatusUnauthorized + } + var msgStr string + if message != nil { + msgStr = message[0] + } else if err != nil { + msgStr = err.Error() + } + c.JSON(msg.StatusOK, model.Response{ + Status: status, + Msg: msgStr, + Data: nil, + }) + + c.Abort() +} diff --git a/pkg/logic/baidu.go b/pkg/logic/baidu.go new file mode 100644 index 0000000..c8a6adc --- /dev/null +++ b/pkg/logic/baidu.go @@ -0,0 +1,58 @@ +package logic + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/dubbogo/gost/log/logger" + "github.com/exhibition-main/internal/model" + "github.com/exhibition-main/pkg/utils" + "time" +) + +const ( + grantType = "client_credentials" + clientId = "SjscdLEybzyxiV7lXKA5iSvZ" + clientSecret = "22fCduMdCarO6WWOi4WiSmt9rIeez3FW" +) + +var accessToken string +var expiresIn uint64 + +func GetImageAccessToken() (string, error) { + var ( + resObj model.BaiduAccessToken + err error + // daoAccessToken entity.AccessToken + ) + if expiresIn == 0 || expiresIn < uint64(time.Now().Unix()) { + fmt.Println(1) + if resObj, err = getAccessTokenWithApi(); err != nil { //从链上获取 + return "", err + } + accessToken = resObj.Access_token + expiresIn = resObj.Expires_in + } + + return accessToken, nil + +} + +func getAccessTokenWithApi() (model.BaiduAccessToken, error) { + var ( + resObj model.BaiduAccessToken + err error + ) + url := "https://aip.baidubce.com/oauth/2.0/token" + urlReq := "?grant_type=" + grantType + "&client_id=" + clientId + "&client_secret=" + clientSecret + res := utils.Get(url + urlReq) + if err = json.Unmarshal([]byte(res), &resObj); err != nil { + logger.Error("getAccessTokenWithApi json err", err) + return resObj, err + } + if resObj.Error != "" { + logger.Error("getAccessTokenWithApi err", err) + return resObj, errors.New(resObj.Error_description) + } + return resObj, err +} diff --git a/pkg/logic/upload.go b/pkg/logic/upload.go new file mode 100644 index 0000000..1af02fc --- /dev/null +++ b/pkg/logic/upload.go @@ -0,0 +1,119 @@ +package logic + +import ( + "github.com/dubbogo/gost/log/logger" + "github.com/rwcarlsen/goexif/exif" + "golang.org/x/image/bmp" + "image" + "image/jpeg" + "image/png" + "os" + "path" + "strings" +) + +// ReadOrientation 方向判断 +func ReadOrientation(filename string) (direct int, err error) { + file, err := os.Open(filename) + defer file.Close() + if err != nil { + logger.Error("failed to open file, err: ", err) + return + } + x, err := exif.Decode(file) + if err != nil { + logger.Error("failed to decode file, err: ", err) + return + } + orientation, err := x.Get(exif.Orientation) + if err != nil { + logger.Error("failed to orientation file, err: ", err) + return + } + direct, err = orientation.Int(0) + if err != nil { + logger.Error("failed to convert type file, err: ", err) + return + } + return +} + +// 旋转90度 +func rotate90(m image.Image) image.Image { + rotate90 := image.NewRGBA(image.Rect(0, 0, m.Bounds().Dy(), m.Bounds().Dx())) + // 矩阵旋转 + for x := m.Bounds().Min.Y; x < m.Bounds().Max.Y; x++ { + for y := m.Bounds().Max.X - 1; y >= m.Bounds().Min.X; y-- { + // 设置像素点 + rotate90.Set(m.Bounds().Max.Y-x, y, m.At(y, x)) + } + } + return rotate90 +} + +// 旋转180度 +func rotate180(m image.Image) image.Image { + rotate180 := image.NewRGBA(image.Rect(0, 0, m.Bounds().Dx(), m.Bounds().Dy())) + // 矩阵旋转 + for x := m.Bounds().Min.X; x < m.Bounds().Max.X; x++ { + for y := m.Bounds().Min.Y; y < m.Bounds().Max.Y; y++ { + // 设置像素点 + rotate180.Set(m.Bounds().Max.X-x, m.Bounds().Max.Y-y, m.At(x, y)) + } + } + return rotate180 +} + +// 旋转270度 +func rotate270(m image.Image) image.Image { + rotate270 := image.NewRGBA(image.Rect(0, 0, m.Bounds().Dy(), m.Bounds().Dx())) + // 矩阵旋转 + for x := m.Bounds().Min.Y; x < m.Bounds().Max.Y; x++ { + for y := m.Bounds().Max.X - 1; y >= m.Bounds().Min.X; y-- { + // 设置像素点 + rotate270.Set(x, m.Bounds().Max.X-y, m.At(y, x)) + } + } + return rotate270 +} + +func MakeThumbnail(imagePath, savePath string) error { + prefix := strings.ToLower(path.Ext("./2.jpg")) + ori, err := ReadOrientation(imagePath) + if err != nil { + return err + } + file, _ := os.Open(imagePath) + defer file.Close() + img, _, err := image.Decode(file) + if err != nil { + return err + } + //苹果手机拍照的图片,会有方向属性Orientation, + //经过Decode和Encode,编码处理后,方向属性会丢失,导致图片被旋转 + switch ori { + case 6: //90度图片旋转 + img = rotate90(img) + case 3: + img = rotate180(img) + case 8: + img = rotate270(img) + } + newImg, _ := os.Create(savePath) + defer newImg.Close() + switch prefix { + case ".jpg", ".jpeg": + err = jpeg.Encode(newImg, img, &jpeg.Options{Quality: 100}) + case "png": + err = png.Encode(newImg, img) + case "bmp": + err = bmp.Encode(newImg, img) + default: + err = jpeg.Encode(newImg, img, &jpeg.Options{Quality: 100}) + } + if err != nil { + logger.Error("Encode err", err) + return err + } + return nil +} diff --git a/pkg/router/router.go b/pkg/router/router.go index c0594ac..0daa76e 100644 --- a/pkg/router/router.go +++ b/pkg/router/router.go @@ -2,6 +2,8 @@ package router import ( "github.com/exhibition-main/internal/middleware" + "github.com/exhibition-main/pkg/service" + "github.com/exhibition-main/pkg/service/common" "net/http" "github.com/gin-contrib/gzip" @@ -16,49 +18,20 @@ func NewRouter() *gin.Engine { r.Use(gzip.Gzip(gzip.DefaultCompression)) //加入日志中间件,跨域中间件 r.Use(middleware.NewLogger(), middleware.Cors(), middleware.GinRecovery(true)) - //noAuth := r.Group("") auth := r.Group("") auth.Use(middleware.JWTAuthMiddleware()) - //userAuth := auth.Group("user") - //{ - // userAuth.POST("info", api.UserInfo) - // userAuth.POST("tickets", api.Tickets) - // userAuth.POST("login-out", api.LoginOut) - // userAuth.POST("destroy", api.Destroy) - //} - //userNoAuth := noAuth.Group("user") - //{ - // userNoAuth.POST("login", api.Login) - //} - //goodsAuth := auth.Group("goods") - //{ - // goodsAuth.POST("drink-list", api.DrinkList) - // goodsAuth.POST("ticket-list", api.TickerList) - // goodsAuth.POST("set-meal-list", api.SetMealList) - //} - // - //orderAuth := auth.Group("order") - //{ - // orderAuth.POST("buy-goods", api.BuyGoods) - // orderAuth.POST("buy-tickets", api.BuyTicket) - // orderAuth.POST("buy-balance", api.BuyBalance) - // orderAuth.POST("buy-set-meal", api.BuySetMeal) - // orderAuth.POST("history-goods", api.HistoryGoods) - // orderAuth.POST("history-tickets", api.HistoryTickets) - // orderAuth.POST("history-balance", api.HistoryBalance) - //} - // - //orderNoAuth := noAuth.Group("order") - //{ - // orderNoAuth.POST("wx_callback", api.WxCallback) - // orderAuth.POST("order-data-h5", api.OrderDataH5) - // orderAuth.POST("update-h5-order", api.UpdateH5Order) - //} - // - //miniNoAuth := noAuth.Group("mini") - //{ - // miniNoAuth.POST("url-scheme", api.UrlScheme) - //} + // 上传 + upload := auth.Group("upload") + { + upload.POST("img", common.UploadImg) + } + + registerAuth := auth.Group("register") + { + registerAuth.POST("register_record_list", service.RegisterRecordList) + registerAuth.POST("check_by_phone", service.CheckPhone) + registerAuth.POST("save_register_info", service.SaveRegisterRecord) + } //静态文件 r.StaticFS("/static", http.Dir("./runtime")) diff --git a/pkg/service/common/common.go b/pkg/service/common/common.go new file mode 100644 index 0000000..10ed065 --- /dev/null +++ b/pkg/service/common/common.go @@ -0,0 +1,309 @@ +package common + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "github.com/disintegration/imaging" + "github.com/dubbogo/gost/log/logger" + "github.com/exhibition-main/internal/config" + "github.com/exhibition-main/internal/model" + "github.com/exhibition-main/internal/msg" + "github.com/exhibition-main/internal/response" + "github.com/exhibition-main/pkg/logic" + "github.com/exhibition-main/pkg/utils" + "github.com/fonchain_enterprise/utils/objstorage" + "github.com/gin-gonic/gin" + uuid "github.com/satori/go.uuid" + ffmpeg "github.com/u2takey/ffmpeg-go" + "go.uber.org/zap" + "io/ioutil" + "mime/multipart" + "net/url" + "os" + "path" + "path/filepath" + "strconv" + "strings" + "sync" +) + +var ( + wg sync.WaitGroup +) + +const ( + MediaPath = "./runtime/" + RouteType = "static/" + VideoType = "video" + ImageType = "image" + PngType = "png" + ArtworkFilePath = "artwork" + ArtworkChunkBasePath = "./runtime/tmp/artworks" +) + +func UploadImg(c *gin.Context) { + var err error + source := c.PostForm("source") + mask := c.PostForm("mask") + action := c.PostForm("action") + if mask == "" { + mask = "default" + } + mediaType := c.PostForm("type") + logger.Errorf("UploadImg 1 %+v", mask) + var BasePath string + if mediaType == "" || mediaType == ImageType { + mediaType = ImageType + } + BasePath = fmt.Sprintf("%s%s", MediaPath, mediaType) + //BaseRoute = fmt.Sprintf("%s%s", RouteType, mediaType) + var isCompress int + if cStr, ok := c.GetPostForm("is_compress"); ok { + var errS error + isCompress, errS = strconv.Atoi(cStr) + if errS != nil { + response.ResponseQuickMsg(c, msg.Fail, errS.Error(), nil) + return + } + } + logger.Errorf("UploadImg 2 %+v", mask) + // 检验参数 + if mask == "" || source == "" { + response.ResponseQuickMsg(c, msg.Fail, msg.INVALID_PARAMS, nil) + return + } + file, err := c.FormFile("file") + // 检验文件 + if err != nil { + logger.Errorf("Upload FormFile err", err) + response.ResponseQuickMsg(c, msg.Fail, err.Error(), nil) + return + } + logger.Errorf("UploadImg 3 %+v", mask) + // 判断是不是视频或者需要压缩 + var oriUrl string + if isCompress != 1 && mediaType != "video" && action == "" { + oriUrl, err = quickBos(file, mediaType, mask, source) + if err != nil { + response.ResponseQuickMsg(c, msg.Fail, err.Error(), nil) + return + } + response.ResponseQuickMsg(c, msg.Ok, msg.SUCCESS, map[string]interface{}{ + "ori_url": oriUrl, + }) + return + } + logger.Errorf("UploadImg 4 %+v", mask) + //创建文件名 + fileExt := strings.ToLower(path.Ext(file.Filename)) + filename := uuid.NewV4() + if err != nil { + response.ResponseQuickMsg(c, msg.Fail, err.Error(), nil) + return + } + fileFullName := fmt.Sprintf("%s%s", filename, fileExt) + //检测文件夹 不存在就创建 + imgPath := fmt.Sprintf("%s/%s/%s", BasePath, source, mask) + _, err = utils.CheckDirPath(imgPath, true) + if err != nil { + response.ResponseQuickMsg(c, msg.Fail, err.Error(), nil) + return + } + dst := fmt.Sprintf("%s/%s", imgPath, fileFullName) + logger.Errorf("UploadImg 5 %+v", mask) + // 保存文件至指定路径 + err = c.SaveUploadedFile(file, dst) + if err != nil { + logger.Errorf("Upload FormFile err", err) + response.ResponseQuickMsg(c, msg.Fail, err.Error(), nil) + return + } + logger.Errorf("UploadImg 6 %+v", mask) + if action == model.ImgActionRotate { + fileFullName = fmt.Sprintf("%s%s", filename, fileExt) + newDst := fmt.Sprintf("%s/%s_rotate%v", imgPath, filename, fileExt) + if err = logic.MakeThumbnail(dst, newDst); err != nil { + //ResponseQuickMsg(c, e.Failed, e.GetMsg(e.ERROR_ROTATE_IMG), nil) + //return + } else { + _ = os.Remove(dst) + dst = newDst + } + } + //localUrl := fmt.Sprintf("%s/%s/%s/%s/%s", config.ServerDM, BaseRoute, source, mask, fileFullName) + var data map[string]string = make(map[string]string, 2) + //data["ori_url"] = localUrl + + if int32(isCompress) == 1 { + //压缩图片并存储在原图路径,命名格式xx.jpg_small.jpg + fileFullName = fmt.Sprintf("%s_small%s", filename, fileExt) + newDst := fmt.Sprintf("%s/%s", imgPath, fileFullName) + //compressUrl := fmt.Sprintf("%s/%s/%s/%s/%s", config.ServerDM, BaseRoute, source, mask, fileFullName) + err = utils.CompressJPG(dst, newDst) + compressUrl, err := PutBos(newDst, mediaType, true) + if err != nil { + logger.Errorf("Upload compress err", err) + response.ResponseQuickMsg(c, msg.Fail, err.Error(), data) + return + } + data["compress_url"] = compressUrl + } + logger.Errorf("UploadImg 7 %+v", mask) + // 如果是视频需要截图图片做封面 + if mediaType == VideoType { + videoCover := fmt.Sprintf("%s/%s", imgPath, filename) + _, err = GetSnapshot(dst, videoCover, 1) + if err != nil { + logger.Errorf("GetSnapshot err %+v", err.Error()) + response.ResponseQuickMsg(c, msg.Fail, "获取封面失败", err.Error()) + return + } + logger.Errorf("UploadImg 8 %+v", mask) + logger.Errorf("UploadImg 8.1 videoCover %+v", videoCover) + //data["cover_url"] = fmt.Sprintf("%s/%s/%s/%s/%s", config.ServerDM, BaseRoute, source, mask, fmt.Sprintf("%s.%s", filename, PngType)) + coverUrl, err := PutBos(videoCover+"."+PngType, mediaType, true) + data["cover_url"] = coverUrl + if err != nil { + logger.Errorf("Upload GetSnapshot err", zap.Error(err)) + response.ResponseQuickMsg(c, msg.Fail, err.Error(), nil) + return + } + } + ossUrl, err := PutBos(dst, mediaType, true) + if err != nil { + response.ResponseQuickMsg(c, msg.Fail, err.Error(), nil) + return + } + data["ori_url"] = ossUrl + response.ResponseQuickMsg(c, msg.Ok, msg.SUCCESS, data) + return +} + +func GetSnapshot(videoPath, snapshotPath string, frameNum int) (snapshotName string, err error) { + buf := bytes.NewBuffer(nil) + err = ffmpeg.Input(videoPath). + Filter("select", ffmpeg.Args{fmt.Sprintf("gte(n,%d)", frameNum)}). + Output("pipe:", ffmpeg.KwArgs{"vframes": 1, "format": "image2", "vcodec": "mjpeg"}). + WithOutput(buf, os.Stdout). + Run() + if err != nil { + logger.Errorf("GetSnapshot Input err:", zap.Error(err)) + return "", err + } + + img, err := imaging.Decode(buf) + if err != nil { + logger.Errorf("GetSnapshot Decode err:", zap.Error(err)) + return "", err + } + + err = imaging.Save(img, snapshotPath+"."+PngType) + if err != nil { + logger.Errorf("GetSnapshot Save err:", zap.Error(err)) + return "", err + } + + names := strings.Split(snapshotPath, "\\") + snapshotName = names[len(names)-1] + "." + PngType + return +} + +func PutBos(filePath string, mediaType string, needRemove bool) (url string, err error) { + BOSClient, err := objstorage.NewBOS(config.BosAk, config.BosSk, objstorage.BOS_BJ) + if err != nil { + logger.Errorf("PutBos NewBOS err ", err) + err = errors.New(msg.ErrorUploadBos) + return + } + f, err := os.Open(filePath) + if err != nil { + logger.Errorf("PutBos Open err %+v", err.Error()) + return + } + fileBytes, _ := ioutil.ReadAll(f) + f.Close() + //删除本地文件 + if needRemove { + os.Remove(filePath) + } + if mediaType == "image" { + if err = BaiduCheckImage(fileBytes); err != nil { + return + } + } + filePath = strings.Replace(filePath, "./runtime", "", 1) + var objectName string = fmt.Sprintf("%s/%s%s", config.BosBaseDir, config.Env, filePath) + _, err = BOSClient.PutObjectFromBytes(config.BosBucketName, objectName, fileBytes) + if err != nil { + logger.Errorf("PutBos PutObject err %+v", err.Error()) + err = errors.New(msg.ErrorUploadBos) + return + } + //url = fmt.Sprintf("%s%s%s/%s", config.BosHttp, config.BosBucketName, config.BosUrl, objectName) + url = fmt.Sprintf("%s%s/%s", config.BosHttp, config.BosDomain, objectName) + return +} + +func quickBos(file *multipart.FileHeader, mediaType string, mask string, source string) (url string, err error) { + newFile, _ := file.Open() + defer newFile.Close() + uuids := uuid.NewV4() + filePath := fmt.Sprintf("%s/%s/%s/%s%s", mediaType, mask, source, uuids, filepath.Ext(file.Filename)) + fileBytes, _ := ioutil.ReadAll(newFile) + if mediaType == "image" { + if err = BaiduCheckImage(fileBytes); err != nil { + return + } + } + var objectName string = fmt.Sprintf("%s/%s/%s", config.BosBaseDir, config.Env, filePath) + BOSClient, _ := objstorage.NewBOS(config.BosAk, config.BosSk, objstorage.BOS_BJ) + _, err = BOSClient.PutObjectFromBytes(config.BosBucketName, objectName, fileBytes) + if err != nil { + logger.Errorf("quickBos err", err) + return + } + //url = fmt.Sprintf("%s%s%s/%s", config.BosHttp, config.BosBucketName, config.BosUrl, objectName) + url = fmt.Sprintf("%s%s/%s", config.BosHttp, config.BosDomain, objectName) + return +} + +// BaiduCheckImage 图片鉴黄 +func BaiduCheckImage(imageByte []byte) (err error) { + return + var ( + accesstoken string + response string + ) + sourcestring := base64.StdEncoding.EncodeToString(imageByte) + if accesstoken, err = logic.GetImageAccessToken(); err != nil { + return err + } + host := "https://aip.baidubce.com/rest/2.0/solution/v1/img_censor/v2/user_defined?access_token=[" + accesstoken + "]" + if response, err = utils.PostForm(host, url.Values{"image": {sourcestring}}); err != nil { + logger.Error("user_defined PostForm err", err) + return err + } + var res struct { + ErrorCode int64 `json:"error_code"` + ErrorMsg string `json:"error_msg"` + Conclusion string `json:"conclusion"` + Log_id uint64 `json:"log_id"` + IsHitMd5 bool `json:"isHitMd5"` + ConclusionType int64 `json:"conclusionType"` + } + if err = json.Unmarshal([]byte(response), &res); err != nil { + err = errors.New(msg.JSON_UNMARSHAL) + return + } + logger.Error("user_defined res", res) + if res.ErrorCode != 0 || res.ErrorMsg != "" { + return errors.New(msg.ERROR_BAIDU_FAIL) + } + if res.Conclusion != "合规" && res.Conclusion != "疑似" { + return errors.New(msg.ERROR_BAIDU_IMAGE) + } + return nil +} diff --git a/pkg/service/cron.go b/pkg/service/cron.go new file mode 100644 index 0000000..5571e30 --- /dev/null +++ b/pkg/service/cron.go @@ -0,0 +1,14 @@ +package service + +import ( + "dubbo.apache.org/dubbo-go/v3/config" + "github.com/robfig/cron/v3" +) + +func Task(c *cron.Cron) { + if config.GetConsumerService("ArtworkClientImpl") != nil { + _, _ = c.AddFunc("@every 5m", func() { + + }) + } +} diff --git a/pkg/service/init.go b/pkg/service/init.go index cb94d9c..85b0ea3 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -3,13 +3,14 @@ package service import ( "dubbo.apache.org/dubbo-go/v3/config" _ "dubbo.apache.org/dubbo-go/v3/imports" + "github.com/exhibition-main/api/exhibition" appConfig "github.com/exhibition-main/internal/config" ) -//var GrpcAccountClientImpl = new(account.AccountClientImpl) +var GrpcExhibitionClientImpl = new(exhibition.ExhibitionClientImpl) func init() { - //config.SetConsumerService(GrpcAccountClientImpl) + config.SetConsumerService(GrpcExhibitionClientImpl) appConfig.GetOptions() if err := config.Load(); err != nil { panic(err) diff --git a/pkg/service/register_record.go b/pkg/service/register_record.go index 6d43c33..69693b1 100644 --- a/pkg/service/register_record.go +++ b/pkg/service/register_record.go @@ -1 +1,61 @@ package service + +import ( + "context" + "github.com/dubbogo/gost/log/logger" + "github.com/exhibition-main/api/exhibition" + "github.com/exhibition-main/internal/msg" + "github.com/exhibition-main/internal/response" + "github.com/gin-gonic/gin" +) + +func RegisterRecordList(c *gin.Context) { + var recordListReq exhibition.RecordListReq + if err := c.ShouldBind(&recordListReq); err != nil { + logger.Errorf("RegisterRecordList ShouldBind err", err) + response.ResponseQuickMsg(c, msg.Fail, msg.INVALID_PARAMS, nil) + return + } + resp, err := GrpcExhibitionClientImpl.RegisterRecordList(context.Background(), &recordListReq) + if err != nil { + response.ResponseQuickMsg(c, msg.Fail, err.Error(), nil) + return + } + response.ResponseQuickMsg(c, msg.Ok, resp.Msg, resp) + return + +} + +func CheckPhone(c *gin.Context) { + var registerInfo exhibition.RegisterInfo + if err := c.ShouldBind(®isterInfo); err != nil { + logger.Errorf("CheckPhone ShouldBind err", err) + response.ResponseQuickMsg(c, msg.Fail, msg.INVALID_PARAMS, nil) + return + } + resp, err := GrpcExhibitionClientImpl.CheckPhone(context.Background(), ®isterInfo) + if err != nil { + response.ResponseQuickMsg(c, msg.Fail, err.Error(), nil) + return + } + response.ResponseQuickMsg(c, msg.Ok, resp.Msg, resp) + return + +} + +func SaveRegisterRecord(c *gin.Context) { + var registerInfo exhibition.RegisterInfo + if err := c.ShouldBind(®isterInfo); err != nil { + logger.Errorf("SaveRegisterRecord ShouldBind err", err) + response.ResponseQuickMsg(c, msg.Fail, msg.INVALID_PARAMS, nil) + return + } + resp, err := GrpcExhibitionClientImpl.SaveRegisterRecord(context.Background(), ®isterInfo) + if err != nil { + response.ResponseQuickMsg(c, msg.Fail, err.Error(), nil) + return + } + response.ResponseQuickMsg(c, msg.Ok, resp.Msg, resp) + return + +} diff --git a/pkg/utils/picture.go b/pkg/utils/picture.go new file mode 100644 index 0000000..0f71d25 --- /dev/null +++ b/pkg/utils/picture.go @@ -0,0 +1,82 @@ +package utils + +import ( + "bytes" + "errors" + "fmt" + "github.com/exhibition-main/internal/msg" + "github.com/nfnt/resize" + "go.uber.org/zap" + "image" + "image/jpeg" + "os" +) + +const ( + CompressLimit = 1024 * 1024 * 1 + MaxHeight uint = 600 +) + +func CheckDirPath(path string, create bool) (exists bool, err error) { + exists = false + if path == "" { + err = errors.New(msg.INVALID_PARAMS) + return + } + if _, err = os.Stat(path); os.IsNotExist(err) { + if !create { + return + } + if err = os.MkdirAll(path, os.ModePerm); err != nil { + return + } + } + exists = true + return +} + +// CompressJPG 压缩图片,并返回缩略图路径 +func CompressJPG(dst string, newDst string) (err error) { + filebytes, err := os.ReadFile(dst) + if err != nil { + return err + } + compressbytes := compressImageResource(filebytes) + fo, err := os.Create(newDst) + defer fo.Close() + if err != nil { + return err + } + _, err = fo.Write(compressbytes) + if err != nil { + return err + } + return nil +} + +func compressImageResource(data []byte) []byte { + if len(data) < CompressLimit { + return data + } + img, _, err := image.Decode(bytes.NewReader(data)) + if err != nil { + return data + } + m := resize.Resize(0, MaxHeight, img, resize.Lanczos3) + buf := bytes.Buffer{} + + err = jpeg.Encode(&buf, m, &jpeg.Options{Quality: 100}) + if err != nil { + zap.L().Error("compressImageResource Encode err", zap.Error(err)) + return data + } + if buf.Len() > len(data) { + return data + } + fmt.Println(buf.Len()) + if buf.Len() >= CompressLimit { + bytes := compressImageResource(buf.Bytes()) + return bytes + } + return buf.Bytes() +} diff --git a/pkg/utils/untils.go b/pkg/utils/untils.go new file mode 100644 index 0000000..2d625d9 --- /dev/null +++ b/pkg/utils/untils.go @@ -0,0 +1,47 @@ +package utils + +import ( + "bytes" + "io" + "io/ioutil" + "net/http" + "net/url" + "time" +) + +// PostForm 发送post请求 +func PostForm(urlStr string, data url.Values) (string, error) { + resp, err := http.PostForm(urlStr, data) + + if err != nil { + // handle error + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + // handle error + } + return string(body), err +} + +func Get(url string) string { + client := &http.Client{Timeout: 5 * time.Second} + resp, err := client.Get(url) + if err != nil { + panic(err) + } + defer resp.Body.Close() + var buffer [512]byte + result := bytes.NewBuffer(nil) + for { + n, err := resp.Body.Read(buffer[0:]) + result.Write(buffer[0:n]) + if err != nil && err == io.EOF { + break + } else if err != nil { + panic(err) + } + } + + return result.String() +}