feat: 1.增加oss上传方法 2.报表系统截图上传oss

This commit is contained in:
fuzhongyun 2025-12-30 16:17:35 +08:00
parent 8576778bff
commit 87b9599ef8
11 changed files with 153 additions and 15 deletions

View File

@ -43,6 +43,12 @@ redis:
db:
driver: mysql
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:
zltxOrderDetail:

View File

@ -43,6 +43,12 @@ redis:
db:
driver: mysql
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:
zltxOrderDetail:

3
go.mod
View File

@ -46,6 +46,7 @@ require (
github.com/alibabacloud-go/gateway-dingtalk v1.0.2 // indirect
github.com/alibabacloud-go/openapi-util v0.1.1 // 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/andybalholm/brotli v1.1.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/slog-shim v0.1.0 // 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/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f // 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/sys v0.37.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/protobuf v1.34.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect

6
go.sum
View File

@ -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-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/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.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
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/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
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.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
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-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.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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=

View File

@ -3,8 +3,9 @@ package config
import (
"ai_scheduler/pkg"
"fmt"
"github.com/spf13/viper"
"time"
"github.com/spf13/viper"
)
// Config 应用配置
@ -19,6 +20,7 @@ type Config struct {
Logging LoggingConfig `mapstructure:"logging"`
Redis Redis `mapstructure:"redis"`
DB DB `mapstructure:"db"`
Oss Oss `mapstructure:"oss"`
DefaultPrompt SysPrompt `mapstructure:"default_prompt"`
PermissionConfig PermissionConfig `mapstructure:"permissionConfig"`
LLM LLM `mapstructure:"llm"`
@ -136,6 +138,15 @@ type DB struct {
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 工具配置
type ToolsConfig struct {
Weather ToolConfig `mapstructure:"weather"`

View File

@ -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
}

View File

@ -2,6 +2,7 @@ package pkg
import (
"ai_scheduler/internal/pkg/dingtalk"
"ai_scheduler/internal/pkg/oss"
"ai_scheduler/internal/pkg/utils_langchain"
"ai_scheduler/internal/pkg/utils_ollama"
"ai_scheduler/internal/pkg/utils_vllm"
@ -19,4 +20,6 @@ var ProviderSetClient = wire.NewSet(
dingtalk.NewOldClient,
dingtalk.NewContactClient,
dingtalk.NewNotableClient,
oss.NewClient,
)

View File

@ -1,8 +1,10 @@
package bbxt
import (
"ai_scheduler/internal/pkg/oss"
"ai_scheduler/pkg"
"fmt"
"math/rand"
"sort"
"time"
)
@ -10,9 +12,10 @@ import (
type BbxtTools struct {
cacheDir string
excelTempDir string
ossClient *oss.Client
}
func NewBbxtTools() (*BbxtTools, error) {
func NewBbxtTools(ossClient *oss.Client) (*BbxtTools, error) {
cache, err := pkg.GetCacheDir()
if err != nil {
return nil, err
@ -25,6 +28,7 @@ func NewBbxtTools() (*BbxtTools, error) {
return &BbxtTools{
cacheDir: cache,
excelTempDir: fmt.Sprintf("%s/excel_temp", tempDir),
ossClient: ossClient,
}, nil
}
@ -111,12 +115,12 @@ func (b *BbxtTools) StatisOursProductLossSumTotal(ct []string) (err error) {
}
//总量生成excel
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)
}
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.resellerDetailFillExcelV2(b.excelTempDir+"/"+"kshj_gt.xlsx", filePath, gt)
}

View File

@ -1,12 +1,25 @@
package bbxt
import (
"ai_scheduler/internal/config"
"ai_scheduler/internal/pkg/oss"
"testing"
"time"
)
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 {
panic(err)
}

View File

@ -10,8 +10,10 @@ import (
"path/filepath"
"reflect"
"sort"
"strings"
"github.com/go-kratos/kratos/v2/log"
"github.com/shopspring/decimal"
"github.com/xuri/excelize/v2"
)
@ -86,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. 保存
return f.SaveAs(outputPath)
}
@ -176,13 +194,6 @@ func (b *BbxtTools) resellerDetailFillExcel(templatePath, outputPath string, dat
}
}
// buffer, err := f.WriteToBuffer()
// if err != nil {
// return err
// }
// return buffer.Bytes(), nil
// 6. 保存
return f.SaveAs(outputPath)
}
@ -294,6 +305,8 @@ func (b *BbxtTools) resellerDetailFillExcelV2(templatePath, outputPath string, d
}
// ---------------- 填充合计行 ----------------
// 四舍五入保留四位小数
totalLoss, _ = decimal.NewFromFloat(totalLoss).Round(4).Float64()
// 设置行高
f.SetRowHeight(sheet, currentRow, rowHeightTotal)
@ -323,7 +336,12 @@ func (b *BbxtTools) resellerDetailFillExcelV2(templatePath, outputPath string, d
if err != nil {
return fmt.Errorf("excel2picPy failed: %v", err)
}
b.SavePic("temp.png", picBytes)
// 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. 保存
return f.SaveAs(outputPath)
@ -405,11 +423,22 @@ func (b *BbxtTools) excel2picPy(templatePath string, excelBytes []byte) ([]byte,
return picBytes, nil
}
// SavePic 保存图片到本地
func (b *BbxtTools) SavePic(outputPath string, picBytes []byte) error {
// 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
}

Binary file not shown.