generated from kedaya_haitao/template
1. 修改ExportCheapProduct方法,支持直接将Excel数据写入HTTP响应 2. 添加图片下载和嵌入功能,提升Excel报表可视化效果 3. 优化数据查询和处理逻辑,提高导出效率 4. 完善错误处理和日志记录 此次修改避免了临时文件的创建和管理,减少了内存使用,提高了响应速度。
This commit is contained in:
parent
b0b6522902
commit
be5c82683f
@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/bash
|
||||
|
||||
# 打包 Helm Chart
|
||||
helm package chart/
|
||||
|
@ -15,10 +15,10 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 1.0.0
|
||||
version: 1.0.2
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "v1.0.0"
|
||||
appVersion: "v1.0.2"
|
||||
|
@ -68,6 +68,8 @@ ingress:
|
||||
paths:
|
||||
- path: /api/v2/articles
|
||||
pathType: ImplementationSpecific
|
||||
- path: /api/v2/tools/excel
|
||||
pathType: ImplementationSpecific
|
||||
tls: []
|
||||
# - secretName: chart-example-tls
|
||||
# hosts:
|
||||
@ -125,6 +127,8 @@ volumes:
|
||||
items:
|
||||
- key: db
|
||||
path: db.yaml
|
||||
- key: dw
|
||||
path: dw.yaml
|
||||
defaultMode: 420
|
||||
# - name: foo
|
||||
# secret:
|
||||
|
120
cmd/article.go
120
cmd/article.go
@ -1,56 +1,64 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"gitea.timerzz.com/kedaya_haitao/article/service"
|
||||
"gitea.timerzz.com/kedaya_haitao/common/pkg/database"
|
||||
"gitea.timerzz.com/kedaya_haitao/common/pkg/web"
|
||||
"gitea.timerzz.com/kedaya_haitao/common/structs/storage"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/middleware/cors"
|
||||
"github.com/gofiber/fiber/v3/middleware/recover"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
glog.Info(">>> BEGIN INIT<<<")
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Kill, os.Interrupt)
|
||||
defer cancel()
|
||||
// 初始化数据库
|
||||
db, err := database.InitDefaultDatabase()
|
||||
if err != nil {
|
||||
glog.Fatalf("init database failed: %v", err)
|
||||
}
|
||||
// 初始化服务
|
||||
r := fiber.New(fiber.Config{ErrorHandler: web.ErrHandle})
|
||||
r.Use(cors.New(), recover.New())
|
||||
|
||||
stg := storage.NewStorage(db)
|
||||
_ = stg.Article().AutoMigrate()
|
||||
|
||||
svc := []web.Register{
|
||||
web.NewProbe(),
|
||||
service.NewArticle(stg),
|
||||
}
|
||||
|
||||
for _, s := range svc {
|
||||
s.Registry(r)
|
||||
}
|
||||
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "80"
|
||||
}
|
||||
if err = r.Listen(":"+port, fiber.ListenConfig{
|
||||
EnablePrintRoutes: true,
|
||||
GracefulContext: ctx,
|
||||
}); err != nil {
|
||||
glog.Warningf("service over: %v", err)
|
||||
}
|
||||
}
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"gitea.timerzz.com/kedaya_haitao/article/service"
|
||||
"gitea.timerzz.com/kedaya_haitao/common/pkg/database"
|
||||
"gitea.timerzz.com/kedaya_haitao/common/pkg/web"
|
||||
"gitea.timerzz.com/kedaya_haitao/common/structs/storage"
|
||||
dw_sdk "gitea.timerzz.com/kedaya_haitao/dw-sdk"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/middleware/cors"
|
||||
"github.com/gofiber/fiber/v3/middleware/recover"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
glog.Info(">>> BEGIN INIT<<<")
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Kill, os.Interrupt)
|
||||
defer cancel()
|
||||
// 初始化数据库
|
||||
db, err := database.InitDefaultDatabase()
|
||||
if err != nil {
|
||||
glog.Fatalf("init database failed: %v", err)
|
||||
}
|
||||
|
||||
client, err := dw_sdk.InitDefaultDWClient()
|
||||
if err != nil {
|
||||
logrus.Fatalf("初始化redis失败:%v", err)
|
||||
}
|
||||
// 初始化服务
|
||||
r := fiber.New(fiber.Config{ErrorHandler: web.ErrHandle})
|
||||
r.Use(cors.New(), recover.New())
|
||||
|
||||
stg := storage.NewStorage(db)
|
||||
_ = stg.Article().AutoMigrate()
|
||||
|
||||
svc := []web.Register{
|
||||
web.NewProbe(),
|
||||
service.NewArticle(stg),
|
||||
service.NewTools(stg, client), // 添加工具服务
|
||||
}
|
||||
|
||||
for _, s := range svc {
|
||||
s.Registry(r)
|
||||
}
|
||||
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "80"
|
||||
}
|
||||
if err = r.Listen(":"+port, fiber.ListenConfig{
|
||||
EnablePrintRoutes: true,
|
||||
GracefulContext: ctx,
|
||||
}); err != nil {
|
||||
glog.Warningf("service over: %v", err)
|
||||
}
|
||||
}
|
||||
|
15
go.mod
15
go.mod
@ -6,18 +6,25 @@ toolchain go1.24.1
|
||||
|
||||
require (
|
||||
gitea.timerzz.com/kedaya_haitao/common v0.0.0-20250329125718-37b1ee0b6a4b
|
||||
gitea.timerzz.com/kedaya_haitao/dw-sdk v0.0.0-20240904075121-552ef11ea87c
|
||||
github.com/gofiber/fiber/v3 v3.0.0-beta.4
|
||||
github.com/golang/glog v1.2.2
|
||||
github.com/samber/lo v1.49.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/xuri/excelize/v2 v2.9.0
|
||||
gorm.io/gorm v1.25.10
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/bytedance/sonic v1.13.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/expr-lang/expr v1.16.9 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/gofiber/schema v1.2.0 // indirect
|
||||
github.com/gofiber/utils/v2 v2.0.0-beta.7 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
@ -25,15 +32,23 @@ require (
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||
github.com/tinylib/msgp v1.2.5 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.58.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect
|
||||
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect
|
||||
golang.org/x/arch v0.9.0 // indirect
|
||||
golang.org/x/crypto v0.33.0 // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
|
46
go.sum
46
go.sum
@ -1,7 +1,17 @@
|
||||
gitea.timerzz.com/kedaya_haitao/common v0.0.0-20250329125718-37b1ee0b6a4b h1:JPa3QxIOsgiKgT66ujH4EiAoIAHxp++Gacf/NpV1xds=
|
||||
gitea.timerzz.com/kedaya_haitao/common v0.0.0-20250329125718-37b1ee0b6a4b/go.mod h1:cfkwyDHbOjucM8xLLg8yIkZKz33kdVqvBZYrfNjM8oc=
|
||||
gitea.timerzz.com/kedaya_haitao/dw-sdk v0.0.0-20240904075121-552ef11ea87c h1:pami6fFsRqKYJyJbSGqXaz2hsbgims7/3ma7cY9CwuY=
|
||||
gitea.timerzz.com/kedaya_haitao/dw-sdk v0.0.0-20240904075121-552ef11ea87c/go.mod h1:Yfl45xbT2dTh4YHqz4E2i++TYq7fahTeSv9JWNFLjNo=
|
||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
||||
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -17,8 +27,11 @@ github.com/gofiber/utils/v2 v2.0.0-beta.7 h1:NnHFrRHvhrufPABdWajcKZejz9HnCWmT/as
|
||||
github.com/gofiber/utils/v2 v2.0.0-beta.7/go.mod h1:J/M03s+HMdZdvhAeyh76xT72IfVqBzuz/OJkrMa7cwU=
|
||||
github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY=
|
||||
github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
@ -33,6 +46,10 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@ -42,23 +59,39 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
|
||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
|
||||
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
|
||||
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
|
||||
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
|
||||
github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE=
|
||||
@ -67,18 +100,30 @@ github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVS
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d h1:llb0neMWDQe87IzJLS4Ci7psK/lVsjIS2otl+1WyRyY=
|
||||
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/excelize/v2 v2.9.0 h1:1tgOaEq92IOEumR1/JfYS/eR0KHOCsRv/rYXXh6YJQE=
|
||||
github.com/xuri/excelize/v2 v2.9.0/go.mod h1:uqey4QBZ9gdMeWApPLdhm9x+9o2lq4iVmjiLfBS5hdE=
|
||||
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A=
|
||||
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
golang.org/x/arch v0.9.0 h1:ub9TgUInamJ8mrZIGlBG6/4TqWeMszd4N8lNorbrr6k=
|
||||
golang.org/x/arch v0.9.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
@ -89,3 +134,4 @@ gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM=
|
||||
gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
|
||||
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
|
||||
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
|
@ -1,217 +1,217 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitea.timerzz.com/kedaya_haitao/common/pkg/web"
|
||||
"gitea.timerzz.com/kedaya_haitao/common/structs/storage"
|
||||
"gitea.timerzz.com/kedaya_haitao/common/structs/utils"
|
||||
v2 "gitea.timerzz.com/kedaya_haitao/common/structs/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/samber/lo"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Article struct {
|
||||
storage *storage.Storage
|
||||
}
|
||||
|
||||
func NewArticle(storage *storage.Storage) *Article {
|
||||
return &Article{
|
||||
storage: storage,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Article) Registry(r fiber.Router) {
|
||||
api := r.Group("/api/v2")
|
||||
api.Get("articles", s.List)
|
||||
api.Get("articles/u/:id", s.Get)
|
||||
api.Get("articles/dict/brand", s.BrandDict)
|
||||
api.Patch("articles", s.Update)
|
||||
api.Get("articles/provider/history/:id", s.ProviderHistory)
|
||||
api.Get("articles/seller/history/:id", s.SellerHistory)
|
||||
api.Patch("articles/provider", s.UpdateProviderArticle)
|
||||
api.Patch("articles/seller", s.UpdateSellerArticle)
|
||||
|
||||
//屏蔽相关操作
|
||||
api.Get("articles/ban", s.ListBanArticle)
|
||||
api.Post("articles/ban/:ids", s.BanArticle)
|
||||
api.Delete("articles/ban/:ids", s.LiftBanArticle)
|
||||
}
|
||||
|
||||
func (s *Article) List(c fiber.Ctx) error {
|
||||
var q storage.FindArticleQuery
|
||||
if err := c.Bind().Query(&q); err != nil {
|
||||
return err
|
||||
}
|
||||
q.SetBan(false)
|
||||
var query = storage.NewPageListQuery(&q)
|
||||
if err := c.Bind().Query(query); err != nil {
|
||||
return err
|
||||
}
|
||||
articles, total, err := s.storage.Article().List(*query, q.SortScope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(web.NewResponse(web.NewListResponse(total, articles)))
|
||||
}
|
||||
|
||||
func (s *Article) Get(c fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
if id == "" {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "id is required")
|
||||
}
|
||||
i, _ := strconv.Atoi(id)
|
||||
query := storage.NewGetArticleQuery().SetID(uint(i))
|
||||
article, err := s.storage.Article().Get(query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(web.NewResponse(article))
|
||||
}
|
||||
|
||||
func (s *Article) Update(c fiber.Ctx) error {
|
||||
var article v2.Article
|
||||
if err := c.Bind().JSON(&article); err != nil {
|
||||
return err
|
||||
}
|
||||
if article.ID == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "id is required")
|
||||
}
|
||||
if err := s.storage.Article().Update(article); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(web.NewResponse(article))
|
||||
}
|
||||
|
||||
func (s *Article) BrandDict(c fiber.Ctx) error {
|
||||
var dict = []utils.Dict{
|
||||
{
|
||||
Key: string(v2.Brand_Coach),
|
||||
Title: fmt.Sprintf("蔻驰/%s", v2.Brand_Coach),
|
||||
Value: v2.Brand_Coach,
|
||||
},
|
||||
}
|
||||
return c.JSON(web.NewResponse(dict))
|
||||
}
|
||||
|
||||
// 获取供应商历史价格
|
||||
func (s *Article) ProviderHistory(c fiber.Ctx) error {
|
||||
i := c.Params("id")
|
||||
id, _ := strconv.Atoi(i)
|
||||
if id == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "id is required")
|
||||
}
|
||||
prices, err := s.storage.ProviderArticle().ProviderPrice(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(web.NewResponse(prices))
|
||||
}
|
||||
|
||||
// 获取销售商历史价格
|
||||
func (s *Article) SellerHistory(c fiber.Ctx) error {
|
||||
i := c.Params("id")
|
||||
id, _ := strconv.Atoi(i)
|
||||
if id == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "id is required")
|
||||
}
|
||||
prices, err := s.storage.SellerArticle().SellerPrice(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(web.NewResponse(prices))
|
||||
}
|
||||
|
||||
func (s *Article) UpdateProviderArticle(c fiber.Ctx) error {
|
||||
var article v2.ProviderArticle
|
||||
if err := c.Bind().JSON(&article); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.storage.ProviderArticle().Update(article); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(web.NewResponse(article))
|
||||
}
|
||||
|
||||
func (s *Article) UpdateSellerArticle(c fiber.Ctx) error {
|
||||
var article v2.SellerArticle
|
||||
if err := c.Bind().JSON(&article); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.storage.SellerArticle().Update(article); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(web.NewResponse(article))
|
||||
}
|
||||
|
||||
// BanArticle 禁用商品
|
||||
func (s *Article) BanArticle(c fiber.Ctx) error {
|
||||
ids := c.Params("ids")
|
||||
if ids == "" {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "ids is required")
|
||||
}
|
||||
idList := lo.Map(strings.Split(ids, ","), func(item string, index int) int {
|
||||
id, _ := strconv.Atoi(item)
|
||||
return id
|
||||
})
|
||||
|
||||
err := s.storage.DB().Transaction(func(tx *gorm.DB) (err error) {
|
||||
if err = tx.Model(&v2.Article{}).Select("ban", "exclude").Where("id IN ?", idList).Updates(map[string]interface{}{"ban": true, "exclude": true}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err = tx.Model(&v2.ProviderArticle{}).Where("article_id IN ?", idList).Update("exclude", true).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Model(&v2.SellerArticle{}).Where("article_id IN ?", idList).Update("exclude", true).Error
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(web.NewResponse("ok"))
|
||||
}
|
||||
|
||||
// 解禁商品
|
||||
func (s *Article) LiftBanArticle(c fiber.Ctx) error {
|
||||
ids := c.Params("ids")
|
||||
if ids == "" {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "ids is required")
|
||||
}
|
||||
idList := lo.Map(strings.Split(ids, ","), func(item string, index int) int {
|
||||
id, _ := strconv.Atoi(item)
|
||||
return id
|
||||
})
|
||||
|
||||
err := s.storage.DB().Transaction(func(tx *gorm.DB) (err error) {
|
||||
if err = tx.Model(&v2.Article{}).Select("ban", "exclude").Where("id IN ?", idList).Updates(map[string]interface{}{"ban": false, "exclude": false}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err = tx.Model(&v2.ProviderArticle{}).Where("article_id IN ?", idList).Update("exclude", false).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Model(&v2.SellerArticle{}).Where("article_id IN ?", idList).Update("exclude", false).Error
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(web.NewResponse("ok"))
|
||||
}
|
||||
|
||||
func (s *Article) ListBanArticle(c fiber.Ctx) error {
|
||||
var q storage.FindArticleQuery
|
||||
if err := c.Bind().Query(&q); err != nil {
|
||||
return err
|
||||
}
|
||||
q.SetBan(true)
|
||||
var query = storage.NewPageListQuery(&q)
|
||||
if err := c.Bind().Query(query); err != nil {
|
||||
return err
|
||||
}
|
||||
articles, total, err := s.storage.Article().List(*query, q.SortScope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(web.NewResponse(web.NewListResponse(total, articles)))
|
||||
}
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitea.timerzz.com/kedaya_haitao/common/pkg/web"
|
||||
"gitea.timerzz.com/kedaya_haitao/common/structs/storage"
|
||||
"gitea.timerzz.com/kedaya_haitao/common/structs/utils"
|
||||
v2 "gitea.timerzz.com/kedaya_haitao/common/structs/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/samber/lo"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Article struct {
|
||||
storage *storage.Storage
|
||||
}
|
||||
|
||||
func NewArticle(storage *storage.Storage) *Article {
|
||||
return &Article{
|
||||
storage: storage,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Article) Registry(r fiber.Router) {
|
||||
api := r.Group("/api/v2")
|
||||
api.Get("articles", s.List)
|
||||
api.Get("articles/u/:id", s.Get)
|
||||
api.Get("articles/dict/brand", s.BrandDict)
|
||||
api.Patch("articles", s.Update)
|
||||
api.Get("articles/provider/history/:id", s.ProviderHistory)
|
||||
api.Get("articles/seller/history/:id", s.SellerHistory)
|
||||
api.Patch("articles/provider", s.UpdateProviderArticle)
|
||||
api.Patch("articles/seller", s.UpdateSellerArticle)
|
||||
|
||||
//屏蔽相关操作
|
||||
api.Get("articles/ban", s.ListBanArticle)
|
||||
api.Post("articles/ban/:ids", s.BanArticle)
|
||||
api.Delete("articles/ban/:ids", s.LiftBanArticle)
|
||||
}
|
||||
|
||||
func (s *Article) List(c fiber.Ctx) error {
|
||||
var q storage.FindArticleQuery
|
||||
if err := c.Bind().Query(&q); err != nil {
|
||||
return err
|
||||
}
|
||||
q.SetBan(false)
|
||||
var query = storage.NewPageListQuery(&q)
|
||||
if err := c.Bind().Query(query); err != nil {
|
||||
return err
|
||||
}
|
||||
articles, total, err := s.storage.Article().List(*query, q.SortScope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(web.NewResponse(web.NewListResponse(total, articles)))
|
||||
}
|
||||
|
||||
func (s *Article) Get(c fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
if id == "" {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "id is required")
|
||||
}
|
||||
i, _ := strconv.Atoi(id)
|
||||
query := storage.NewGetArticleQuery().SetID(uint(i))
|
||||
article, err := s.storage.Article().Get(query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(web.NewResponse(article))
|
||||
}
|
||||
|
||||
func (s *Article) Update(c fiber.Ctx) error {
|
||||
var article v2.Article
|
||||
if err := c.Bind().JSON(&article); err != nil {
|
||||
return err
|
||||
}
|
||||
if article.ID == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "id is required")
|
||||
}
|
||||
if err := s.storage.Article().Update(article); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(web.NewResponse(article))
|
||||
}
|
||||
|
||||
func (s *Article) BrandDict(c fiber.Ctx) error {
|
||||
var dict = []utils.Dict{
|
||||
{
|
||||
Key: string(v2.Brand_Coach),
|
||||
Title: fmt.Sprintf("蔻驰/%s", v2.Brand_Coach),
|
||||
Value: v2.Brand_Coach,
|
||||
},
|
||||
}
|
||||
return c.JSON(web.NewResponse(dict))
|
||||
}
|
||||
|
||||
// 获取供应商历史价格
|
||||
func (s *Article) ProviderHistory(c fiber.Ctx) error {
|
||||
i := c.Params("id")
|
||||
id, _ := strconv.Atoi(i)
|
||||
if id == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "id is required")
|
||||
}
|
||||
prices, err := s.storage.ProviderArticle().ProviderPrice(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(web.NewResponse(prices))
|
||||
}
|
||||
|
||||
// 获取销售商历史价格
|
||||
func (s *Article) SellerHistory(c fiber.Ctx) error {
|
||||
i := c.Params("id")
|
||||
id, _ := strconv.Atoi(i)
|
||||
if id == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "id is required")
|
||||
}
|
||||
prices, err := s.storage.SellerArticle().SellerPrice(uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(web.NewResponse(prices))
|
||||
}
|
||||
|
||||
func (s *Article) UpdateProviderArticle(c fiber.Ctx) error {
|
||||
var article v2.ProviderArticle
|
||||
if err := c.Bind().JSON(&article); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.storage.ProviderArticle().Update(article); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(web.NewResponse(article))
|
||||
}
|
||||
|
||||
func (s *Article) UpdateSellerArticle(c fiber.Ctx) error {
|
||||
var article v2.SellerArticle
|
||||
if err := c.Bind().JSON(&article); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.storage.SellerArticle().Update(article); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(web.NewResponse(article))
|
||||
}
|
||||
|
||||
// BanArticle 禁用商品
|
||||
func (s *Article) BanArticle(c fiber.Ctx) error {
|
||||
ids := c.Params("ids")
|
||||
if ids == "" {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "ids is required")
|
||||
}
|
||||
idList := lo.Map(strings.Split(ids, ","), func(item string, index int) int {
|
||||
id, _ := strconv.Atoi(item)
|
||||
return id
|
||||
})
|
||||
|
||||
err := s.storage.DB().Transaction(func(tx *gorm.DB) (err error) {
|
||||
if err = tx.Model(&v2.Article{}).Select("ban", "exclude").Where("id IN ?", idList).Updates(map[string]interface{}{"ban": true, "exclude": true}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err = tx.Model(&v2.ProviderArticle{}).Where("article_id IN ?", idList).Update("exclude", true).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Model(&v2.SellerArticle{}).Where("article_id IN ?", idList).Update("exclude", true).Error
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(web.NewResponse("ok"))
|
||||
}
|
||||
|
||||
// 解禁商品
|
||||
func (s *Article) LiftBanArticle(c fiber.Ctx) error {
|
||||
ids := c.Params("ids")
|
||||
if ids == "" {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "ids is required")
|
||||
}
|
||||
idList := lo.Map(strings.Split(ids, ","), func(item string, index int) int {
|
||||
id, _ := strconv.Atoi(item)
|
||||
return id
|
||||
})
|
||||
|
||||
err := s.storage.DB().Transaction(func(tx *gorm.DB) (err error) {
|
||||
if err = tx.Model(&v2.Article{}).Select("ban", "exclude").Where("id IN ?", idList).Updates(map[string]interface{}{"ban": false, "exclude": false}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err = tx.Model(&v2.ProviderArticle{}).Where("article_id IN ?", idList).Update("exclude", false).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Model(&v2.SellerArticle{}).Where("article_id IN ?", idList).Update("exclude", false).Error
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(web.NewResponse("ok"))
|
||||
}
|
||||
|
||||
func (s *Article) ListBanArticle(c fiber.Ctx) error {
|
||||
var q storage.FindArticleQuery
|
||||
if err := c.Bind().Query(&q); err != nil {
|
||||
return err
|
||||
}
|
||||
q.SetBan(true)
|
||||
var query = storage.NewPageListQuery(&q)
|
||||
if err := c.Bind().Query(query); err != nil {
|
||||
return err
|
||||
}
|
||||
articles, total, err := s.storage.Article().List(*query, q.SortScope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(web.NewResponse(web.NewListResponse(total, articles)))
|
||||
}
|
||||
|
72
service/tools_svc.go
Normal file
72
service/tools_svc.go
Normal file
@ -0,0 +1,72 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
coach_outlet "gitea.timerzz.com/kedaya_haitao/article/tools/coach-outlet"
|
||||
"gitea.timerzz.com/kedaya_haitao/common/structs/storage"
|
||||
dw_sdk "gitea.timerzz.com/kedaya_haitao/dw-sdk"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Tools struct {
|
||||
storage *storage.Storage
|
||||
dw *dw_sdk.Client
|
||||
export *coach_outlet.Exporter
|
||||
}
|
||||
|
||||
// NewTools 创建工具服务实例
|
||||
func NewTools(storage *storage.Storage, dw *dw_sdk.Client) *Tools {
|
||||
exporter, err := coach_outlet.NewExporter(storage, dw)
|
||||
if err != nil {
|
||||
logrus.Errorf("初始化Coach Outlet导出工具失败: %v", err)
|
||||
}
|
||||
|
||||
return &Tools{
|
||||
storage: storage,
|
||||
dw: dw,
|
||||
export: exporter,
|
||||
}
|
||||
}
|
||||
|
||||
// Registry 注册路由
|
||||
func (s *Tools) Registry(r fiber.Router) {
|
||||
api := r.Group("/api/v2")
|
||||
// 添加新的Coach Outlet工具接口
|
||||
tools := api.Group("/tools")
|
||||
tools.Get("/excel/coach-outlet/:provider_id", s.CoachOutletExcel)
|
||||
}
|
||||
|
||||
// CoachOutletExcel 导出Coach Outlet商品数据到Excel
|
||||
func (s *Tools) CoachOutletExcel(c fiber.Ctx) error {
|
||||
// 获取供应商ID参数
|
||||
providerId := c.Params("provider_id")
|
||||
if providerId == "" {
|
||||
providerId = coach_outlet.CACoachOutlet // 默认使用加拿大Coach Outlet
|
||||
}
|
||||
|
||||
// 验证供应商ID是否有效
|
||||
if providerId != coach_outlet.CACoachOutlet && providerId != coach_outlet.USCoachOutlet {
|
||||
return fiber.NewError(fiber.StatusBadRequest,
|
||||
fmt.Sprintf("无效的供应商ID,必须是 %s 或 %s", coach_outlet.CACoachOutlet, coach_outlet.USCoachOutlet))
|
||||
}
|
||||
|
||||
// 检查导出工具是否初始化成功
|
||||
if s.export == nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "导出工具未初始化")
|
||||
}
|
||||
|
||||
// 设置文件名和响应头
|
||||
fileName := fmt.Sprintf("coach_outlet_%s.xlsx", providerId)
|
||||
c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, fileName))
|
||||
c.Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
|
||||
// 直接将Excel数据写入响应
|
||||
if err := s.export.ExportCheapProduct(providerId, c); err != nil {
|
||||
logrus.Errorf("导出Coach Outlet商品数据失败: %v", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("导出失败: %v", err))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
294
tools/coach-outlet/excel.go
Normal file
294
tools/coach-outlet/excel.go
Normal file
@ -0,0 +1,294 @@
|
||||
package coach_outlet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.timerzz.com/kedaya_haitao/common/structs/storage"
|
||||
"gitea.timerzz.com/kedaya_haitao/common/structs/utils"
|
||||
v2 "gitea.timerzz.com/kedaya_haitao/common/structs/v2"
|
||||
dw_sdk "gitea.timerzz.com/kedaya_haitao/dw-sdk"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/samber/lo"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/xuri/excelize/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
USCoachOutlet = "us-coach-outlet"
|
||||
CACoachOutlet = "ca-coach-outlet"
|
||||
)
|
||||
|
||||
type Exporter struct {
|
||||
storage *storage.Storage
|
||||
dw *dw_sdk.Client
|
||||
seller v2.Seller
|
||||
}
|
||||
|
||||
func NewExporter(storage *storage.Storage, dw *dw_sdk.Client) (e *Exporter, err error) {
|
||||
e = &Exporter{storage: storage, dw: dw}
|
||||
e.seller, err = storage.Seller().GetBySellerId("dw-normal")
|
||||
return e, err
|
||||
}
|
||||
|
||||
// ExportCheapProduct 导出价格较低的商品数据到Excel,直接写入到HTTP响应
|
||||
func (e *Exporter) ExportCheapProduct(providerId string, c fiber.Ctx) error {
|
||||
// 创建Excel文件
|
||||
f := excelize.NewFile()
|
||||
defer f.Close()
|
||||
|
||||
// 创建工作表
|
||||
sheetName := providerId
|
||||
_, err := f.NewSheet(sheetName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建工作表失败: %w", err)
|
||||
}
|
||||
_ = f.DeleteSheet("Sheet1") // 删除默认的Sheet1
|
||||
|
||||
// 确定查询的供应商ID
|
||||
var cheapId, otherId string
|
||||
cheapId = providerId
|
||||
otherId = lo.Ternary(providerId == CACoachOutlet, USCoachOutlet, CACoachOutlet)
|
||||
|
||||
// 查询符合条件的商品
|
||||
var articles []*v2.Article
|
||||
rowIndex := 1 // 从第1行开始(标题行)
|
||||
setHeader(f, sheetName)
|
||||
err = e.storage.DB().Model(&v2.Article{}).Select("DISTINCT articles.*").
|
||||
Preload("Providers", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("provider_id IN ?", []string{cheapId, otherId})
|
||||
}).
|
||||
Preload("Sellers", "seller_id = ?", "dw-normal").
|
||||
Joins("JOIN provider_articles pa1 ON articles.id = pa1.article_id AND pa1.provider_id = ? AND pa1.available = true", cheapId).
|
||||
Joins("JOIN provider_articles pa2 ON articles.id = pa2.article_id AND pa2.provider_id = ?", otherId).
|
||||
Joins("JOIN seller_articles sa ON articles.id = sa.article_id AND (sa.seller_id = 'dw-normal' AND (sa.exclude = false OR sa.exclude IS NULL))").
|
||||
Where("pa1.cost->>'finalPrice' <= pa2.cost->>'finalPrice'").
|
||||
FindInBatches(&articles, 20, func(tx *gorm.DB, batch int) error {
|
||||
for _, article := range articles {
|
||||
// 更新DW价格(如果需要)
|
||||
dwSeller, idx, exist := lo.FindIndexOf(article.Sellers, func(seller v2.SellerArticle) bool {
|
||||
return seller.SellerId == "dw-normal"
|
||||
})
|
||||
if exist && dwSeller.Sell.CreatedAt.Before(time.Now().AddDate(0, 0, -1)) {
|
||||
// 如果已经是一天前的数据,那么就要更新下
|
||||
dwSeller = e.pullDwPrice(dwSeller)
|
||||
article.Sellers[idx] = dwSeller
|
||||
}
|
||||
|
||||
// 计算利润率
|
||||
utils.ProfitRate(article)
|
||||
|
||||
// 添加到Excel
|
||||
rowIndex++
|
||||
if err = addRow(f, sheetName, rowIndex, article); err != nil {
|
||||
logrus.Errorf("添加行失败: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}).Error
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("查询数据失败: %w", err)
|
||||
}
|
||||
|
||||
// 保存Excel文件
|
||||
filename := fmt.Sprintf("coach_outlet_%s.xlsx", providerId)
|
||||
|
||||
if err = f.SaveAs(filename); err != nil {
|
||||
return fmt.Errorf("保存Excel文件失败: %w", err)
|
||||
}
|
||||
|
||||
// 直接将Excel文件写入到HTTP响应
|
||||
if err = f.Write(c.Response().BodyWriter()); err != nil {
|
||||
return fmt.Errorf("写入Excel数据失败: %w", err)
|
||||
}
|
||||
|
||||
logrus.Infof("成功导出 %d 条数据", rowIndex-1)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 设置标题行
|
||||
func setHeader(f *excelize.File, sheetName string) {
|
||||
// 设置列标题(如果是第一行)
|
||||
headers := []string{"商品名", "图片", "美国官网原价", "美国官网最终价格",
|
||||
"加拿大官网原价", "加拿大官网最终价格", "DW价格", "DW到手价格", "利润", "利润率"}
|
||||
for i, header := range headers {
|
||||
cell, _ := excelize.CoordinatesToCellName(i+1, 1)
|
||||
_ = f.SetCellValue(sheetName, cell, header)
|
||||
}
|
||||
|
||||
// 设置列宽
|
||||
_ = f.SetColWidth(sheetName, "A", "A", 30)
|
||||
_ = f.SetColWidth(sheetName, "B", "B", 20) // 增加图片列宽度
|
||||
_ = f.SetColWidth(sheetName, "C", "J", 15)
|
||||
_ = f.SetRowHeight(sheetName, 1, 20) // 设置标题行高
|
||||
|
||||
// 设置样式
|
||||
style, _ := f.NewStyle(&excelize.Style{
|
||||
Font: &excelize.Font{Bold: true},
|
||||
Alignment: &excelize.Alignment{Horizontal: "center"},
|
||||
})
|
||||
_ = f.SetRowStyle(sheetName, 1, 1, style)
|
||||
}
|
||||
|
||||
// addRow 根据article信息添加一行到excel
|
||||
// 商品名,图片,美国官网原价,美国官网最终价格,加拿大官网原始价格,加拿大官网最终价格,dw价格, dw到手价格,利润,利润率
|
||||
func addRow(f *excelize.File, sheetName string, rowIndex int, article *v2.Article) error {
|
||||
// 获取美国和加拿大的供应商信息
|
||||
usProvider, _ := lo.Find(article.Providers, func(p v2.ProviderArticle) bool {
|
||||
return p.ProviderId == USCoachOutlet
|
||||
})
|
||||
caProvider, _ := lo.Find(article.Providers, func(p v2.ProviderArticle) bool {
|
||||
return p.ProviderId == CACoachOutlet
|
||||
})
|
||||
|
||||
// 获取DW销售商信息
|
||||
dwSeller, _, _ := lo.FindIndexOf(article.Sellers, func(s v2.SellerArticle) bool {
|
||||
return s.SellerId == "dw-normal"
|
||||
})
|
||||
|
||||
// 填充数据
|
||||
cells := []interface{}{
|
||||
article.Name,
|
||||
"", // 图片单元格留空,后面会添加图片
|
||||
usProvider.Cost.OriginalPrice,
|
||||
usProvider.Cost.FinalPrice,
|
||||
caProvider.Cost.OriginalPrice,
|
||||
caProvider.Cost.FinalPrice,
|
||||
dwSeller.Sell.OriginalPrice,
|
||||
dwSeller.Sell.FinalPrice,
|
||||
article.GrossProfit,
|
||||
article.Rate,
|
||||
}
|
||||
|
||||
// 写入数据
|
||||
for i, value := range cells {
|
||||
cell, _ := excelize.CoordinatesToCellName(i+1, rowIndex)
|
||||
_ = f.SetCellValue(sheetName, cell, value)
|
||||
}
|
||||
|
||||
// 设置行高以适应图片
|
||||
_ = f.SetRowHeight(sheetName, rowIndex, 80)
|
||||
|
||||
// 处理图片
|
||||
if len(article.Image) > 0 {
|
||||
imgCell, _ := excelize.CoordinatesToCellName(2, rowIndex)
|
||||
|
||||
// 下载图片
|
||||
imgData, err := downloadImage(article.Image)
|
||||
if err != nil {
|
||||
logrus.Warnf("下载图片失败 %s: %v", article.Image, err)
|
||||
// 如果下载失败,至少显示URL
|
||||
_ = f.SetCellValue(sheetName, imgCell, article.Image)
|
||||
} else {
|
||||
// 添加图片到Excel
|
||||
if err := f.AddPictureFromBytes(sheetName, imgCell, &excelize.Picture{
|
||||
Extension: getImageExtension(article.Image),
|
||||
File: imgData,
|
||||
Format: &excelize.GraphicOptions{
|
||||
AutoFit: true,
|
||||
ScaleX: 0.5,
|
||||
ScaleY: 0.5,
|
||||
Positioning: "oneCell",
|
||||
},
|
||||
}); err != nil {
|
||||
logrus.Warnf("添加图片到Excel失败: %v", err)
|
||||
// 如果添加失败,显示URL
|
||||
_ = f.SetCellValue(sheetName, imgCell, article.Image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// downloadImage 下载图片并返回字节数据
|
||||
func downloadImage(url string) ([]byte, error) {
|
||||
// 创建HTTP客户端,设置超时
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
// 发送GET请求
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("HTTP请求失败: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 检查状态码
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("HTTP状态码错误: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// 读取响应体
|
||||
imgData, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取图片数据失败: %w", err)
|
||||
}
|
||||
|
||||
return imgData, nil
|
||||
}
|
||||
|
||||
// getImageExtension 从URL获取图片扩展名
|
||||
func getImageExtension(url string) string {
|
||||
// 默认扩展名
|
||||
ext := ".jpg"
|
||||
|
||||
// 从URL中提取扩展名
|
||||
if strings.Contains(url, ".") {
|
||||
parts := strings.Split(url, ".")
|
||||
lastPart := parts[len(parts)-1]
|
||||
|
||||
// 处理可能的查询参数
|
||||
if strings.Contains(lastPart, "?") {
|
||||
lastPart = strings.Split(lastPart, "?")[0]
|
||||
}
|
||||
|
||||
// 常见图片扩展名
|
||||
switch strings.ToLower(lastPart) {
|
||||
case "jpg", "jpeg", "png", "gif", "bmp", "webp":
|
||||
ext = "." + strings.ToLower(lastPart)
|
||||
}
|
||||
}
|
||||
|
||||
return ext
|
||||
}
|
||||
|
||||
func (e *Exporter) pullDwPrice(sArticle v2.SellerArticle) v2.SellerArticle {
|
||||
skuId, _ := strconv.Atoi(sArticle.SkuID)
|
||||
resp, err := e.dw.NormalBidClient().LowestPrice(skuId)
|
||||
if err != nil {
|
||||
return sArticle
|
||||
}
|
||||
lowest, exist := lo.Find(resp.Items, func(item dw_sdk.LowestPriceItem) bool {
|
||||
return item.BiddingType == dw_sdk.BiddingType_Normal
|
||||
})
|
||||
if !exist {
|
||||
// 没有拿到对应类型的价格,那就拿一个最低的
|
||||
lowest = lo.MinBy(resp.Items, func(item1, item2 dw_sdk.LowestPriceItem) bool {
|
||||
return item1.LowestPrice < item2.LowestPrice && item1.LowestPrice > 0
|
||||
})
|
||||
}
|
||||
if lowest.LowestPrice == 0 {
|
||||
// 没有拿到价格,将exclude设置为true,如果后面要拉取,需要手动打开
|
||||
return sArticle
|
||||
}
|
||||
sell := utils.CalculateSellerPrice(append(e.seller.CalculateProcess, sArticle.CalculateProcess...), map[string]float64{
|
||||
"originalPrice": float64(lowest.LowestPrice / 100),
|
||||
})
|
||||
if sell.OriginalPrice != sArticle.Sell.OriginalPrice {
|
||||
sArticle.Sell = sell
|
||||
sArticle.HistoryPrice = append(sArticle.HistoryPrice, sArticle.Sell)
|
||||
}
|
||||
if err = e.storage.SellerArticle().Upsert(sArticle); err != nil {
|
||||
logrus.Errorf("保存sellerArticle失败:%v", err)
|
||||
}
|
||||
return sArticle
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user