Merge remote-tracking branch 'origin/v4' into v4
# Conflicts: # internal/tools/bbxt/bbxt.go
This commit is contained in:
commit
35bdd4fb43
|
|
@ -5,3 +5,4 @@ docs
|
||||||
cmd/server/wire_gen.go
|
cmd/server/wire_gen.go
|
||||||
__debug*
|
__debug*
|
||||||
.bin/
|
.bin/
|
||||||
|
.idea/
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,12 @@ redis:
|
||||||
db:
|
db:
|
||||||
driver: mysql
|
driver: mysql
|
||||||
source: root:SD###sdf323r343@tcp(121.199.38.107:3306)/sys_ai_test?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
|
source: root:SD###sdf323r343@tcp(121.199.38.107:3306)/sys_ai_test?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
|
||||||
|
oss:
|
||||||
|
access_key: "LTAI5tGGZzjf3tvqWk8SQj2G"
|
||||||
|
secret_key: "S0NKOAUaYWoK4EGSxrMFmYDzllhvpq"
|
||||||
|
bucket: "attachment-public"
|
||||||
|
domain: "https://attachment-public.oss-cn-hangzhou.aliyuncs.com"
|
||||||
|
endpoint: "https://oss-cn-hangzhou.aliyuncs.com"
|
||||||
|
|
||||||
tools:
|
tools:
|
||||||
zltxOrderDetail:
|
zltxOrderDetail:
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,12 @@ redis:
|
||||||
db:
|
db:
|
||||||
driver: mysql
|
driver: mysql
|
||||||
source: root:SD###sdf323r343@tcp(121.199.38.107:3306)/sys_ai_test?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
|
source: root:SD###sdf323r343@tcp(121.199.38.107:3306)/sys_ai_test?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
|
||||||
|
oss:
|
||||||
|
access_key: "LTAI5tGGZzjf3tvqWk8SQj2G"
|
||||||
|
secret_key: "S0NKOAUaYWoK4EGSxrMFmYDzllhvpq"
|
||||||
|
bucket: "attachment-public"
|
||||||
|
domain: "https://attachment-public.oss-cn-hangzhou.aliyuncs.com"
|
||||||
|
endpoint: "https://oss-cn-hangzhou.aliyuncs.com"
|
||||||
|
|
||||||
tools:
|
tools:
|
||||||
zltxOrderDetail:
|
zltxOrderDetail:
|
||||||
|
|
|
||||||
3
go.mod
3
go.mod
|
|
@ -46,6 +46,7 @@ require (
|
||||||
github.com/alibabacloud-go/gateway-dingtalk v1.0.2 // indirect
|
github.com/alibabacloud-go/gateway-dingtalk v1.0.2 // indirect
|
||||||
github.com/alibabacloud-go/openapi-util v0.1.1 // indirect
|
github.com/alibabacloud-go/openapi-util v0.1.1 // indirect
|
||||||
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
|
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
|
||||||
|
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible // indirect
|
||||||
github.com/aliyun/credentials-go v1.4.6 // indirect
|
github.com/aliyun/credentials-go v1.4.6 // indirect
|
||||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||||
|
|
@ -95,6 +96,7 @@ require (
|
||||||
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||||
|
github.com/shopspring/decimal v1.4.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f // indirect
|
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
|
|
@ -120,6 +122,7 @@ require (
|
||||||
golang.org/x/net v0.46.0 // indirect
|
golang.org/x/net v0.46.0 // indirect
|
||||||
golang.org/x/sys v0.37.0 // indirect
|
golang.org/x/sys v0.37.0 // indirect
|
||||||
golang.org/x/text v0.30.0 // indirect
|
golang.org/x/text v0.30.0 // indirect
|
||||||
|
golang.org/x/time v0.5.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
|
||||||
google.golang.org/protobuf v1.34.1 // indirect
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
|
|
||||||
6
go.sum
6
go.sum
|
|
@ -94,6 +94,8 @@ github.com/alibabacloud-go/tea-utils/v2 v2.0.6 h1:ZkmUlhlQbaDC+Eba/GARMPy6hKdCLi
|
||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
|
||||||
github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0=
|
github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0=
|
||||||
github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
|
github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
|
||||||
|
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
|
||||||
|
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||||
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
|
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
|
||||||
github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
|
github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
|
||||||
github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
|
github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
|
||||||
|
|
@ -392,6 +394,8 @@ github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWR
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
||||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||||
|
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
|
@ -690,6 +694,8 @@ golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ package config
|
||||||
import (
|
import (
|
||||||
"ai_scheduler/pkg"
|
"ai_scheduler/pkg"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/spf13/viper"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config 应用配置
|
// Config 应用配置
|
||||||
|
|
@ -19,6 +20,7 @@ type Config struct {
|
||||||
Logging LoggingConfig `mapstructure:"logging"`
|
Logging LoggingConfig `mapstructure:"logging"`
|
||||||
Redis Redis `mapstructure:"redis"`
|
Redis Redis `mapstructure:"redis"`
|
||||||
DB DB `mapstructure:"db"`
|
DB DB `mapstructure:"db"`
|
||||||
|
Oss Oss `mapstructure:"oss"`
|
||||||
DefaultPrompt SysPrompt `mapstructure:"default_prompt"`
|
DefaultPrompt SysPrompt `mapstructure:"default_prompt"`
|
||||||
PermissionConfig PermissionConfig `mapstructure:"permissionConfig"`
|
PermissionConfig PermissionConfig `mapstructure:"permissionConfig"`
|
||||||
LLM LLM `mapstructure:"llm"`
|
LLM LLM `mapstructure:"llm"`
|
||||||
|
|
@ -136,6 +138,15 @@ type DB struct {
|
||||||
IsDebug bool `mapstructure:"isDebug"`
|
IsDebug bool `mapstructure:"isDebug"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Oss 阿里云OSS配置
|
||||||
|
type Oss struct {
|
||||||
|
AccessKey string `mapstructure:"access_key"`
|
||||||
|
SecretKey string `mapstructure:"secret_key"`
|
||||||
|
Bucket string `mapstructure:"bucket"`
|
||||||
|
Domain string `mapstructure:"domain"`
|
||||||
|
Endpoint string `mapstructure:"endpoint"`
|
||||||
|
}
|
||||||
|
|
||||||
// ToolsConfig 工具配置
|
// ToolsConfig 工具配置
|
||||||
type ToolsConfig struct {
|
type ToolsConfig struct {
|
||||||
Weather ToolConfig `mapstructure:"weather"`
|
Weather ToolConfig `mapstructure:"weather"`
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package oss
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||||
|
"github.com/go-kratos/kratos/v2/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
config config.Oss
|
||||||
|
client *oss.Client
|
||||||
|
bucket *oss.Bucket
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient 初始化 OSS 客户端
|
||||||
|
func NewClient(cfg config.Oss) (*Client, error) {
|
||||||
|
client, err := oss.New(cfg.Endpoint, cfg.AccessKey, cfg.SecretKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oss new client failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket, err := client.Bucket(cfg.Bucket)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oss get bucket failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
config: cfg,
|
||||||
|
client: client,
|
||||||
|
bucket: bucket,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadBytes 上传字节数组到 OSS
|
||||||
|
// objectKey: OSS 中的文件路径,例如 "ai_scheduler/test.png"
|
||||||
|
// fileBytes: 文件内容
|
||||||
|
// 返回: 文件的访问 URL
|
||||||
|
func (c *Client) UploadBytes(objectKey string, fileBytes []byte) (string, error) {
|
||||||
|
err := c.bucket.PutObject(objectKey, bytes.NewReader(fileBytes))
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("oss PutObject failed: %v", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造返回 URL
|
||||||
|
var url string
|
||||||
|
if c.config.Domain != "" {
|
||||||
|
url = fmt.Sprintf("%s/%s", c.config.Domain, objectKey)
|
||||||
|
} else {
|
||||||
|
// 这里简单处理协议头
|
||||||
|
url = fmt.Sprintf("https://%s.%s/%s", c.config.Bucket, c.config.Endpoint, objectKey)
|
||||||
|
}
|
||||||
|
return url, nil
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"ai_scheduler/internal/pkg/dingtalk"
|
"ai_scheduler/internal/pkg/dingtalk"
|
||||||
|
"ai_scheduler/internal/pkg/oss"
|
||||||
"ai_scheduler/internal/pkg/utils_langchain"
|
"ai_scheduler/internal/pkg/utils_langchain"
|
||||||
"ai_scheduler/internal/pkg/utils_ollama"
|
"ai_scheduler/internal/pkg/utils_ollama"
|
||||||
"ai_scheduler/internal/pkg/utils_vllm"
|
"ai_scheduler/internal/pkg/utils_vllm"
|
||||||
|
|
@ -19,4 +20,6 @@ var ProviderSetClient = wire.NewSet(
|
||||||
dingtalk.NewOldClient,
|
dingtalk.NewOldClient,
|
||||||
dingtalk.NewContactClient,
|
dingtalk.NewContactClient,
|
||||||
dingtalk.NewNotableClient,
|
dingtalk.NewNotableClient,
|
||||||
|
|
||||||
|
oss.NewClient,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
package bbxt
|
package bbxt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"ai_scheduler/internal/pkg/oss"
|
||||||
"ai_scheduler/pkg"
|
"ai_scheduler/pkg"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"math/rand"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -16,9 +18,10 @@ import (
|
||||||
type BbxtTools struct {
|
type BbxtTools struct {
|
||||||
cacheDir string
|
cacheDir string
|
||||||
excelTempDir string
|
excelTempDir string
|
||||||
|
ossClient *oss.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBbxtTools() (*BbxtTools, error) {
|
func NewBbxtTools(ossClient *oss.Client) (*BbxtTools, error) {
|
||||||
cache, err := pkg.GetCacheDir()
|
cache, err := pkg.GetCacheDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -31,6 +34,7 @@ func NewBbxtTools() (*BbxtTools, error) {
|
||||||
return &BbxtTools{
|
return &BbxtTools{
|
||||||
cacheDir: cache,
|
cacheDir: cache,
|
||||||
excelTempDir: fmt.Sprintf("%s/excel_temp", tempDir),
|
excelTempDir: fmt.Sprintf("%s/excel_temp", tempDir),
|
||||||
|
ossClient: ossClient,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,13 +121,14 @@ func (b *BbxtTools) StatisOursProductLossSum(ct []string) (err error) {
|
||||||
}
|
}
|
||||||
//总量生成excel
|
//总量生成excel
|
||||||
if len(total) > 0 {
|
if len(total) > 0 {
|
||||||
filePath := b.cacheDir + "/kshj_total" + fmt.Sprintf("%d", time.Now().Unix()) + ".xlsx"
|
filePath := b.cacheDir + "/kshj_total" + fmt.Sprintf("%d%d", time.Now().Unix(), rand.Intn(1000)) + ".xlsx"
|
||||||
err = b.SimpleFillExcel(b.excelTempDir+"/"+"kshj_total.xlsx", filePath, total)
|
err = b.SimpleFillExcel(b.excelTempDir+"/"+"kshj_total.xlsx", filePath, total)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(gt) > 0 {
|
if len(gt) > 0 {
|
||||||
filePath := b.cacheDir + "/kshj_gt" + fmt.Sprintf("%d", time.Now().Unix()) + ".xlsx"
|
filePath := b.cacheDir + "/kshj_gt" + fmt.Sprintf("%d%d", time.Now().Unix(), rand.Intn(1000)) + ".xlsx"
|
||||||
err = b.resellerDetailFillExcel(b.excelTempDir+"/"+"kshj_gt.xlsx", filePath, gt)
|
// err = b.resellerDetailFillExcel(b.excelTempDir+"/"+"kshj_gt.xlsx", filePath, gt)
|
||||||
|
err = b.resellerDetailFillExcelV2(b.excelTempDir+"/"+"kshj_gt.xlsx", filePath, gt)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,25 @@
|
||||||
package bbxt
|
package bbxt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/pkg/oss"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_StatisOursProductLossSumApiTotal(t *testing.T) {
|
func Test_StatisOursProductLossSumApiTotal(t *testing.T) {
|
||||||
o, err := NewBbxtTools()
|
ossClient, err := oss.NewClient(config.Oss{
|
||||||
|
AccessKey: "LTAI5tGGZzjf3tvqWk8SQj2G",
|
||||||
|
SecretKey: "S0NKOAUaYWoK4EGSxrMFmYDzllhvpq",
|
||||||
|
Bucket: "attachment-public",
|
||||||
|
Domain: "https://attachment-public.oss-cn-hangzhou.aliyuncs.com",
|
||||||
|
Endpoint: "https://oss-cn-hangzhou.aliyuncs.com",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
o, err := NewBbxtTools(ossClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,19 @@
|
||||||
package bbxt
|
package bbxt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/log"
|
"github.com/go-kratos/kratos/v2/log"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
"github.com/xuri/excelize/v2"
|
"github.com/xuri/excelize/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -80,6 +88,22 @@ func (b *BbxtTools) SimpleFillExcel(templatePath, outputPath string, dataSlice i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
excelBytes, err := f.WriteToBuffer()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("write to bytes failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
picBytes, err := b.excel2picPy(templatePath, excelBytes.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("excel2picPy failed: %v", err)
|
||||||
|
}
|
||||||
|
// b.savePic("temp.png", picBytes) // 本地生成图片,仅测试
|
||||||
|
// outputPath 提取文件名(不包含扩展名)
|
||||||
|
filename := filepath.Base(outputPath)
|
||||||
|
filename = strings.TrimSuffix(filename, filepath.Ext(filename))
|
||||||
|
imgUrl := b.uploadToOSS(filename, picBytes)
|
||||||
|
log.Infof("imgUrl: %s", imgUrl)
|
||||||
|
|
||||||
// 6. 保存
|
// 6. 保存
|
||||||
return f.SaveAs(outputPath)
|
return f.SaveAs(outputPath)
|
||||||
}
|
}
|
||||||
|
|
@ -170,12 +194,154 @@ func (b *BbxtTools) resellerDetailFillExcel(templatePath, outputPath string, dat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// buffer, err := f.WriteToBuffer()
|
// 6. 保存
|
||||||
// if err != nil {
|
return f.SaveAs(outputPath)
|
||||||
// return err
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// return buffer.Bytes(), nil
|
// 分销商负利润详情填充excel-V2
|
||||||
|
// 1.使用模板文件作为输出文件,从第二行开始填充
|
||||||
|
// 2.整体为3列:1.分销商名称(以ResellerName为分组,分销商名称列使用的样式为) 2.商品名称(p.ProductName) 3.亏损金额(p.Loss)
|
||||||
|
// 3.分销商名称列使用的样式为 A2;商品名称、亏损金额使用的样式为 B2、C2;样式包括宽高、背景、颜色等
|
||||||
|
// 4.以ResellerName分组,合并单元格
|
||||||
|
// 5.在文件末尾使用“合计”,合计行样式为模板第四行
|
||||||
|
// 6.保存为新文件
|
||||||
|
func (b *BbxtTools) resellerDetailFillExcelV2(templatePath, outputPath string, dataSlice []*ResellerLoss) error {
|
||||||
|
// 1. 读取模板
|
||||||
|
f, err := excelize.OpenFile(templatePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
sheet := f.GetSheetName(0)
|
||||||
|
|
||||||
|
// ---------------- 样式获取 ----------------
|
||||||
|
// 模板第2行:数据行样式
|
||||||
|
tplRowData := 2
|
||||||
|
styleA2, err := f.GetCellStyle(sheet, fmt.Sprintf("A%d", tplRowData))
|
||||||
|
if err != nil {
|
||||||
|
styleA2 = 0
|
||||||
|
}
|
||||||
|
// B2和C2通常样式一致,这里取B2作为明细列样式
|
||||||
|
styleB2, err := f.GetCellStyle(sheet, fmt.Sprintf("B%d", tplRowData))
|
||||||
|
if err != nil {
|
||||||
|
styleB2 = 0
|
||||||
|
}
|
||||||
|
styleC2, err := f.GetCellStyle(sheet, fmt.Sprintf("C%d", tplRowData))
|
||||||
|
if err != nil {
|
||||||
|
styleC2 = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
rowHeightData, err := f.GetRowHeight(sheet, tplRowData)
|
||||||
|
if err != nil {
|
||||||
|
rowHeightData = 20
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模板第4行:合计行样式
|
||||||
|
tplRowTotal := 4
|
||||||
|
styleTotalA, err := f.GetCellStyle(sheet, fmt.Sprintf("A%d", tplRowTotal))
|
||||||
|
if err != nil {
|
||||||
|
styleTotalA = 0
|
||||||
|
}
|
||||||
|
styleTotalB, err := f.GetCellStyle(sheet, fmt.Sprintf("B%d", tplRowTotal))
|
||||||
|
if err != nil {
|
||||||
|
styleTotalB = 0
|
||||||
|
}
|
||||||
|
styleTotalC, err := f.GetCellStyle(sheet, fmt.Sprintf("C%d", tplRowTotal))
|
||||||
|
if err != nil {
|
||||||
|
styleTotalC = 0
|
||||||
|
}
|
||||||
|
rowHeightTotal, err := f.GetRowHeight(sheet, tplRowTotal)
|
||||||
|
if err != nil {
|
||||||
|
rowHeightTotal = 30
|
||||||
|
}
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
currentRow := 2
|
||||||
|
totalLoss := 0.0
|
||||||
|
|
||||||
|
for _, reseller := range dataSlice {
|
||||||
|
// 排序 ProductLoss
|
||||||
|
var products []ProductLoss
|
||||||
|
for _, p := range reseller.ProductLoss {
|
||||||
|
products = append(products, p)
|
||||||
|
}
|
||||||
|
sort.Slice(products, func(i, j int) bool {
|
||||||
|
return products[i].Loss < products[j].Loss
|
||||||
|
})
|
||||||
|
|
||||||
|
startRow := currentRow
|
||||||
|
|
||||||
|
// 填充该经销商的所有产品
|
||||||
|
for _, p := range products {
|
||||||
|
// 设置行高
|
||||||
|
f.SetRowHeight(sheet, currentRow, rowHeightData)
|
||||||
|
|
||||||
|
// 设置值
|
||||||
|
f.SetCellValue(sheet, fmt.Sprintf("A%d", currentRow), reseller.ResellerName)
|
||||||
|
f.SetCellValue(sheet, fmt.Sprintf("B%d", currentRow), p.ProductName)
|
||||||
|
f.SetCellValue(sheet, fmt.Sprintf("C%d", currentRow), p.Loss)
|
||||||
|
|
||||||
|
// 设置样式
|
||||||
|
if styleA2 != 0 {
|
||||||
|
f.SetCellStyle(sheet, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("A%d", currentRow), styleA2)
|
||||||
|
}
|
||||||
|
if styleB2 != 0 {
|
||||||
|
f.SetCellStyle(sheet, fmt.Sprintf("B%d", currentRow), fmt.Sprintf("B%d", currentRow), styleB2)
|
||||||
|
}
|
||||||
|
if styleC2 != 0 {
|
||||||
|
f.SetCellStyle(sheet, fmt.Sprintf("C%d", currentRow), fmt.Sprintf("C%d", currentRow), styleC2)
|
||||||
|
}
|
||||||
|
|
||||||
|
totalLoss += p.Loss
|
||||||
|
currentRow++
|
||||||
|
}
|
||||||
|
|
||||||
|
endRow := currentRow - 1
|
||||||
|
// 合并单元格 (如果多于1行)
|
||||||
|
if endRow > startRow {
|
||||||
|
f.MergeCell(sheet, fmt.Sprintf("A%d", startRow), fmt.Sprintf("A%d", endRow))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- 填充合计行 ----------------
|
||||||
|
// 四舍五入保留四位小数
|
||||||
|
totalLoss, _ = decimal.NewFromFloat(totalLoss).Round(4).Float64()
|
||||||
|
// 设置行高
|
||||||
|
f.SetRowHeight(sheet, currentRow, rowHeightTotal)
|
||||||
|
|
||||||
|
f.SetCellValue(sheet, fmt.Sprintf("A%d", currentRow), "合计")
|
||||||
|
// B列留空,C列填充总亏损
|
||||||
|
f.SetCellValue(sheet, fmt.Sprintf("C%d", currentRow), totalLoss)
|
||||||
|
|
||||||
|
// 设置合计行样式
|
||||||
|
if styleTotalA != 0 {
|
||||||
|
f.SetCellStyle(sheet, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("A%d", currentRow), styleTotalA)
|
||||||
|
}
|
||||||
|
if styleTotalB != 0 {
|
||||||
|
f.SetCellStyle(sheet, fmt.Sprintf("B%d", currentRow), fmt.Sprintf("B%d", currentRow), styleTotalB)
|
||||||
|
}
|
||||||
|
if styleTotalC != 0 {
|
||||||
|
f.SetCellStyle(sheet, fmt.Sprintf("C%d", currentRow), fmt.Sprintf("C%d", currentRow), styleTotalC)
|
||||||
|
}
|
||||||
|
// 取消合并合计行的A、B列
|
||||||
|
// f.MergeCell(sheet, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("B%d", currentRow))
|
||||||
|
|
||||||
|
excelBytes, err := f.WriteToBuffer()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("write to bytes failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
picBytes, err := b.excel2picPy(templatePath, excelBytes.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("excel2picPy failed: %v", err)
|
||||||
|
}
|
||||||
|
// b.savePic("temp.png", picBytes) // 本地生成图片,仅测试
|
||||||
|
// outputPath 提取文件名(不包含扩展名)
|
||||||
|
filename := filepath.Base(outputPath)
|
||||||
|
filename = strings.TrimSuffix(filename, filepath.Ext(filename))
|
||||||
|
imgUrl := b.uploadToOSS(filename, picBytes)
|
||||||
|
log.Infof("imgUrl: %s", imgUrl)
|
||||||
|
|
||||||
// 6. 保存
|
// 6. 保存
|
||||||
return f.SaveAs(outputPath)
|
return f.SaveAs(outputPath)
|
||||||
|
|
@ -188,7 +354,91 @@ func (b *BbxtTools) resellerDetailFillExcel(templatePath, outputPath string, dat
|
||||||
// --form 'file=@"C:\\Users\\Administrator\\Downloads\\销售同比分析2025-12-29 0-12点.xlsx"' \
|
// --form 'file=@"C:\\Users\\Administrator\\Downloads\\销售同比分析2025-12-29 0-12点.xlsx"' \
|
||||||
// --form 'sheet_name="销售同比分析"'
|
// --form 'sheet_name="销售同比分析"'
|
||||||
func (b *BbxtTools) excel2picPy(templatePath string, excelBytes []byte) ([]byte, error) {
|
func (b *BbxtTools) excel2picPy(templatePath string, excelBytes []byte) ([]byte, error) {
|
||||||
|
// 1. 获取 Sheet Name
|
||||||
return nil, nil
|
// 尝试从 excelBytes 解析,如果失败则使用默认值 "Sheet1"
|
||||||
// return picBytes, nil
|
sheetName := "Sheet1"
|
||||||
|
f, err := excelize.OpenReader(bytes.NewReader(excelBytes))
|
||||||
|
if err == nil {
|
||||||
|
sheetName = f.GetSheetName(0)
|
||||||
|
if sheetName == "" {
|
||||||
|
sheetName = "Sheet1"
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 构造 Multipart 请求
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
|
||||||
|
// 添加文件字段
|
||||||
|
// 使用 templatePath 的文件名作为上传文件名,如果没有则用 default.xlsx
|
||||||
|
filename := "default.xlsx"
|
||||||
|
if templatePath != "" {
|
||||||
|
filename = filepath.Base(templatePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
part, err := writer.CreateFormFile("file", filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create form file failed: %v", err)
|
||||||
|
}
|
||||||
|
if _, err = part.Write(excelBytes); err != nil {
|
||||||
|
return nil, fmt.Errorf("write file part failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加 sheet_name 字段
|
||||||
|
if err = writer.WriteField("sheet_name", sheetName); err != nil {
|
||||||
|
return nil, fmt.Errorf("write field sheet_name failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = writer.Close(); err != nil {
|
||||||
|
return nil, fmt.Errorf("close writer failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 发送 HTTP POST 请求
|
||||||
|
url := "http://192.168.6.109:8010/api/v1/convert"
|
||||||
|
req, err := http.NewRequest("POST", url, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create request failed: %v", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("send request failed: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
respBody, _ := io.ReadAll(resp.Body)
|
||||||
|
return nil, fmt.Errorf("api request failed with status: %d, body: %s", resp.StatusCode, string(respBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 读取响应 Body (图片内容)
|
||||||
|
picBytes, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read response body failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return picBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// savePic 保存图片到本地
|
||||||
|
func (b *BbxtTools) savePic(outputPath string, picBytes []byte) error {
|
||||||
|
dir := filepath.Dir(outputPath)
|
||||||
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("create directory failed: %v", err)
|
||||||
|
}
|
||||||
|
return os.WriteFile(outputPath, picBytes, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// uploadToOSS 上传至 oss 返回图片url
|
||||||
|
func (b *BbxtTools) uploadToOSS(fileName string, fileBytes []byte) string {
|
||||||
|
objectKey := fmt.Sprintf("ai-scheduler/data-analytics/images/%s.png", fileName)
|
||||||
|
url, err := b.ossClient.UploadBytes(objectKey, fileBytes)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("oss upload failed: %v", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return url
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ func (z ZltxOrderStatisticsTool) Definition() entitys.ToolDefinition {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ZltxOrderStatisticsRequest struct {
|
type ZltxOrderStatisticsRequest struct {
|
||||||
Number string `json:"number"`
|
Number interface{} `json:"number"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z ZltxOrderStatisticsTool) Execute(ctx context.Context, rec *entitys.Recognize) error {
|
func (z ZltxOrderStatisticsTool) Execute(ctx context.Context, rec *entitys.Recognize) error {
|
||||||
|
|
@ -53,7 +53,7 @@ func (z ZltxOrderStatisticsTool) Execute(ctx context.Context, rec *entitys.Recog
|
||||||
if err := json.Unmarshal([]byte(rec.Match.Parameters), &req); err != nil {
|
if err := json.Unmarshal([]byte(rec.Match.Parameters), &req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if req.Number == "" {
|
if req.Number == nil {
|
||||||
return fmt.Errorf("number is required")
|
return fmt.Errorf("number is required")
|
||||||
}
|
}
|
||||||
return z.getZltxOrderStatistics(req.Number, rec)
|
return z.getZltxOrderStatistics(req.Number, rec)
|
||||||
|
|
@ -76,14 +76,13 @@ type ZltxOrderStatisticsData struct {
|
||||||
Total int `json:"total"`
|
Total int `json:"total"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z ZltxOrderStatisticsTool) getZltxOrderStatistics(number string, rec *entitys.Recognize) error {
|
func (z ZltxOrderStatisticsTool) getZltxOrderStatistics(number interface{}, rec *entitys.Recognize) error {
|
||||||
ext, err := rec_extra.GetTaskRecExt(rec)
|
ext, err := rec_extra.GetTaskRecExt(rec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
//查询订单详情
|
//查询订单详情
|
||||||
|
url := fmt.Sprintf("%s%s", z.config.BaseURL, fmt.Sprintf("%v", number))
|
||||||
url := fmt.Sprintf("%s%s", z.config.BaseURL, number)
|
|
||||||
req := l_request.Request{
|
req := l_request.Request{
|
||||||
Url: url,
|
Url: url,
|
||||||
Headers: map[string]string{
|
Headers: map[string]string{
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue