Compare commits
24 Commits
08c2094940
...
5831eefd4f
| Author | SHA1 | Date |
|---|---|---|
|
|
5831eefd4f | |
|
|
aa3151baa5 | |
|
|
04c0077e16 | |
|
|
2e9fa92734 | |
|
|
c69345bcaf | |
|
|
370090ad15 | |
|
|
b6725f75e8 | |
|
|
bff31bc81e | |
|
|
b61bbe4ede | |
|
|
3045790dd5 | |
|
|
d4a79bb915 | |
|
|
f85bbc1fad | |
|
|
3d8678566e | |
|
|
1ca305b3a0 | |
|
|
2df24a5ff3 | |
|
|
bfbaee2028 | |
|
|
8271e7941d | |
|
|
8fb5d1b038 | |
|
|
8919a296e6 | |
|
|
63ddd84a43 | |
|
|
c6fda17b87 | |
|
|
31b131f1db | |
|
|
a3ec04a3ce | |
|
|
62d22c1405 |
|
|
@ -21,7 +21,19 @@ vllm:
|
|||
|
||||
coze:
|
||||
base_url: "https://api.coze.cn"
|
||||
api_secret: "sat_AqvFcdNgesP8megy1ItTscWFXRcsHRzmM4NJ1KNavfcdT0EPwYuCPkDqGhItpx13"
|
||||
|
||||
redis:
|
||||
host: 47.97.27.195:6379
|
||||
type: node
|
||||
pass:
|
||||
lansexiongdi@666i_secret: "sat_AqvFcdNgesP8megy1ItTscWFXRcsHRzmM4NJ1KNavfcdT0EPwYuCPkDqGhItpx13"
|
||||
|
||||
lsxd:
|
||||
# 统一登录
|
||||
login_url: "http://api.test.user.1688sup.com/v1/login/phone"
|
||||
phone: "0zmINhJBwsDJYTmeaxXK4A=="
|
||||
password: "LSSGoWhc63NpZc1rq3LT8g=="
|
||||
check_token_url: "http://api.test.user.1688sup.com/v1/user/welcome"
|
||||
|
||||
|
||||
sys:
|
||||
|
|
@ -30,10 +42,6 @@ sys:
|
|||
channel_pool_size: 32
|
||||
llm_pool_len: 5
|
||||
heartbeat_interval: 30
|
||||
redis:
|
||||
host: 47.97.27.195:6379
|
||||
type: node
|
||||
pass: lansexiongdi@666
|
||||
key: report-api
|
||||
pollSize: 5 #连接池大小,不配置,或配置为0表示不启用连接池
|
||||
minIdleConns: 2 #最小空闲连接数
|
||||
|
|
@ -128,7 +136,7 @@ eino_tools:
|
|||
# 我们的商品统计
|
||||
rechargeStatisticsOursProduct:
|
||||
base_url: "http://admin.lanseds.cn/admin/statistics/oursProduct"
|
||||
api_key: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyQ2VudGVyIiwiZXhwIjoxNzY3MTc5ODgzLCJuYmYiOjE3NjcxNjkwODMsImp0aSI6IjEiLCJQaG9uZSI6IjE4MjAwMTYwMTQzIiwiVXNlck5hbWUiOiJsc3hkIiwiUmVhbE5hbWUiOiLotoXnuqfnrqHnkIblkZgiLCJBY2NvdW50VHlwZSI6MiwiR3JvdXBDb2RlcyI6IkxTWEREU19TWVNURU0sU1RBVElTVElDQUxTWVNURU1fQURNSU4sUEhZU0lDQUxHT09EU19BRE1JTiIsIkRpbmdVc2VySWQiOiIifQ.ELNF1Iv6yEwA12nCbXGKwXCw-F5Gq4GI2t2nqo1PlSkFdQ5Oz5s5NwV0RUXA66LxCggI-9IjBtFI1MvBHpvTHq9QRlm-HKzVTMcOBkEtKEfCCI6SPKVTAZyntTJlWPKG3u-CJUotT5YW0j2rU1VcpA7uGEiY7gs5VPUOZ80R1uGJ7HBSqVI2DRqar6STa1xryygdCjK7qamUM2d6aJ6r9VPTBt-JO6dkDdw3KHs3wl-PGM3wcbXHZ2aC18WFd_PxLmtjqErpxTEkdUBCnUHOSKDePG0henDJq71Nh3yRdRmY9VvszHMyIxJA2BVGPIPUT_Y5aewaaEMQVEjhiBnn-Q"
|
||||
|
||||
excel2pic:
|
||||
base_url: "http://192.168.6.109:8010/api/v1/convert"
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,13 @@ coze:
|
|||
base_url: "https://api.coze.cn"
|
||||
api_secret: "sat_AqvFcdNgesP8megy1ItTscWFXRcsHRzmM4NJ1KNavfcdT0EPwYuCPkDqGhItpx13"
|
||||
|
||||
lsxd:
|
||||
# 统一登录
|
||||
login_url: "http://api.test.user.1688sup.com/v1/login/phone"
|
||||
phone: "0zmINhJBwsDJYTmeaxXK4A=="
|
||||
password: "LSSGoWhc63NpZc1rq3LT8g=="
|
||||
check_token_url: "http://api.test.user.1688sup.com/v1/user/welcome"
|
||||
|
||||
|
||||
sys:
|
||||
session_len: 6
|
||||
|
|
@ -125,7 +132,6 @@ eino_tools:
|
|||
# 我们的商品统计
|
||||
rechargeStatisticsOursProduct:
|
||||
base_url: "http://admin.1688sup.cn:8001/admin/statistics/oursProduct"
|
||||
api_key: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyQ2VudGVyIiwiZXhwIjoxNzY3MDc3NzcwLCJuYmYiOjE3NjcwNzU5NzAsImp0aSI6IjEiLCJQaG9uZSI6IjE4MDAwMDAwMDAwIiwiVXNlck5hbWUiOiJsc3hkIiwiUmVhbE5hbWUiOiLotoXnuqfnrqHnkIblkZgiLCJBY2NvdW50VHlwZSI6MSwiR3JvdXBDb2RlcyI6IlZDTF9DQVNISUVSLFZDTF9PUEVSQVRFLFZDTF9BRE1JTixWQ0xfQUFBLFZDTF9WQ0xfT1BFUkFULFZDTF9JTlZPSUNFLENSTV9BRE1JTixMSUFOTElBTl9BRE1JTixNQVJLRVRNQUcyX0FETUlOLFBIT05FQklMTF9BRE1JTixRSUFOWkhVX1NVUFBFUl9BRE0sTUFSS0VUSU5HU0FBU19TVVBFUkFETUlOLENBUkRfQ09ERSxDQVJEX1BST0NVUkVNRU5ULE1BUktFVElOR1NZU1RFTV9TVVBFUixTVEFUSVNUSUNBTFNZU1RFTV9BRE1JTixaTFRYX0FETUlOLFpMVFhfT1BFUkFURSIsIkRpbmdVc2VySWQiOiIxNjIwMjYxMjMwMjg5MzM4MzQifQ.Nuw_aR6iSPmhhh9E5rhyTxHBsgWtaTZvbnc7SFTnUBJXTQvYahnk0LyZaVpsVw6FB3cU0F5xOdX3rmGyWyaiszWO6yi-o1oxGMXwhf39fMiWT2xUI6pAn9Ync8DmZ4tOMCNUTdEk4CaQFzrTwJs0c-VR4yW6LgoPmNPvUVZ-KwmusUpnPz5j9RsJItzIWE3bpGGsfB54e2UERcZdbo9BXxCZIBbpAYKBKdl73KuI8SNaXgKvGTrJ6hEN4ESpnbisJVwT5pp_kuChJlcfjHTHFsEf4RJDjN9gTrtDbBWZyY3OmO2ukqYAM7tZPs6TXJwvQGJQsFRVZUBGxS1nD_6DzQ"
|
||||
excel2pic:
|
||||
base_url: "http://192.168.6.109:8010/api/v1/convert"
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,13 @@ coze:
|
|||
api_secret: "sat_AqvFcdNgesP8megy1ItTscWFXRcsHRzmM4NJ1KNavfcdT0EPwYuCPkDqGhItpx13"
|
||||
|
||||
|
||||
lsxd:
|
||||
# 统一登录
|
||||
login_url: "http://api.test.user.1688sup.com/v1/login/phone"
|
||||
phone: "0zmINhJBwsDJYTmeaxXK4A=="
|
||||
password: "LSSGoWhc63NpZc1rq3LT8g=="
|
||||
check_token_url: "http://api.test.user.1688sup.com/v1/user/welcome"
|
||||
|
||||
sys:
|
||||
session_len: 6
|
||||
channel_pool_len: 100
|
||||
|
|
@ -127,8 +134,7 @@ eino_tools:
|
|||
# == 电商充值系统 ==
|
||||
# 我们的商品统计
|
||||
rechargeStatisticsOursProduct:
|
||||
base_url: "http://admin.lanseds.cn/admin/statistics/oursProduct"
|
||||
api_key: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyQ2VudGVyIiwiZXhwIjoxNzY3MTc5ODgzLCJuYmYiOjE3NjcxNjkwODMsImp0aSI6IjEiLCJQaG9uZSI6IjE4MjAwMTYwMTQzIiwiVXNlck5hbWUiOiJsc3hkIiwiUmVhbE5hbWUiOiLotoXnuqfnrqHnkIblkZgiLCJBY2NvdW50VHlwZSI6MiwiR3JvdXBDb2RlcyI6IkxTWEREU19TWVNURU0sU1RBVElTVElDQUxTWVNURU1fQURNSU4sUEhZU0lDQUxHT09EU19BRE1JTiIsIkRpbmdVc2VySWQiOiIifQ.ELNF1Iv6yEwA12nCbXGKwXCw-F5Gq4GI2t2nqo1PlSkFdQ5Oz5s5NwV0RUXA66LxCggI-9IjBtFI1MvBHpvTHq9QRlm-HKzVTMcOBkEtKEfCCI6SPKVTAZyntTJlWPKG3u-CJUotT5YW0j2rU1VcpA7uGEiY7gs5VPUOZ80R1uGJ7HBSqVI2DRqar6STa1xryygdCjK7qamUM2d6aJ6r9VPTBt-JO6dkDdw3KHs3wl-PGM3wcbXHZ2aC18WFd_PxLmtjqErpxTEkdUBCnUHOSKDePG0henDJq71Nh3yRdRmY9VvszHMyIxJA2BVGPIPUT_Y5aewaaEMQVEjhiBnn-Q"
|
||||
base_url: "http://admin.1688sup.cn:8001/admin/statistics/oursProduct"
|
||||
excel2pic:
|
||||
base_url: "http://192.168.6.109:8010/api/v1/convert"
|
||||
|
||||
|
|
|
|||
|
|
@ -497,10 +497,15 @@ func (d *DingTalkBotBiz) handleReport(ctx context.Context, rec *entitys.Recogniz
|
|||
return err
|
||||
}
|
||||
t, err := time.Parse(time.DateTime, configData.Time)
|
||||
if err != nil {
|
||||
t, err = time.Parse("2006-01-02 15:04", configData.Time)
|
||||
if err != nil {
|
||||
t, err = time.Parse("2006-01-02", configData.Time)
|
||||
if err != nil {
|
||||
log.Infof("时间识别失败:%s", configData.Time)
|
||||
entitys.ResText(rec.Ch, "", "时间识别失败了!可以给我一份比较具体的时间吗,例如“2025-12-31 12:00,抱歉抱歉😀")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
rep, err := bbxt.NewBbxtTools()
|
||||
uploader := bbxt.NewUploader(d.ossClient)
|
||||
|
|
@ -531,7 +536,7 @@ func (d *DingTalkBotBiz) handleReport(ctx context.Context, rec *entitys.Recogniz
|
|||
reports = append(reports, repo)
|
||||
case "report_daily":
|
||||
product := strings.Split(group.ProductName, ",")
|
||||
repo, _err := rep.DailyReport(t, product, nil)
|
||||
repo, _err := rep.DailyReport(t, bbxt.DownWardValue, product, bbxt.SumFilter, nil)
|
||||
if _err != nil {
|
||||
return _err
|
||||
}
|
||||
|
|
@ -543,6 +548,13 @@ func (d *DingTalkBotBiz) handleReport(ctx context.Context, rec *entitys.Recogniz
|
|||
return _err
|
||||
}
|
||||
reports = append(reports, repo...)
|
||||
case "report_sale_down_analysis":
|
||||
product := strings.Split(group.ProductName, ",")
|
||||
repo, _err := rep.GetStatisOfficialProductSumDecline(t, bbxt.DownWardValue, product, bbxt.SumFilter)
|
||||
if _err != nil {
|
||||
return _err
|
||||
}
|
||||
reports = append(reports, repo)
|
||||
default:
|
||||
return fmt.Errorf("未找到的报表:%s", rec.Match.Index)
|
||||
}
|
||||
|
|
@ -621,22 +633,23 @@ func (d *DingTalkBotBiz) HandleStreamRes(ctx context.Context, data *chatbot.BotC
|
|||
|
||||
func (d *DingTalkBotBiz) GetReportLists(ctx context.Context, group *model.AiBotGroup) (reports []*bbxt.ReportRes, err error) {
|
||||
|
||||
reportList, err := bbxt.NewBbxtTools()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var product []string
|
||||
if group.ProductName != "" {
|
||||
product = strings.Split(group.ProductName, ",")
|
||||
}
|
||||
//[]string{"官方-爱奇艺-星钻季卡", "官方-爱奇艺-星钻半年卡", "官方--腾讯-年卡", "官方--爱奇艺-月卡"}
|
||||
reports, err = reportList.DailyReport(time.Now(), product, d.ossClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
//var product []string
|
||||
//if group.ProductName != "" {
|
||||
// product = strings.Split(group.ProductName, ",")
|
||||
//}
|
||||
|
||||
// 追加电商充值系统统计 - 返回统一使用 []*bbxt.ReportRes
|
||||
rechargeReports, err := d.rechargeDailyReport(ctx, time.Now(), product, d.ossClient)
|
||||
//reportList, err := bbxt.NewBbxtTools()
|
||||
//if err != nil {
|
||||
// return
|
||||
//}
|
||||
|
||||
//reports, err = reportList.DailyReport(time.Now(), bbxt.DownWardValue, product, bbxt.SumFilter, d.ossClient)
|
||||
//if err != nil {
|
||||
// return
|
||||
//}
|
||||
//product = []string{"优酷周卡", "优酷季卡", "优酷年卡", "爱奇艺黄金会员天卡"}
|
||||
//追加电商充值系统统计 - 返回统一使用[]*bbxt.ReportRes
|
||||
rechargeReports, err := d.rechargeDailyReport(ctx, time.Now(), nil, d.ossClient)
|
||||
if err != nil || len(rechargeReports) == 0 {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -349,7 +349,7 @@ func (d *Do) LoadUserPermission(client *gateway.Client, requireData *entitys.Req
|
|||
|
||||
// 检查响应状态码
|
||||
if res.StatusCode != http.StatusOK {
|
||||
err = errors.SysErrf("获取用户权限失败")
|
||||
err = errors.SysErr(fmt.Sprintf("获取用户权限失败,状态码:%d %s %s %s", res.StatusCode, res.Text, request.Url, request.Headers["Authorization"]))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ type Config struct {
|
|||
Ollama OllamaConfig `mapstructure:"ollama"`
|
||||
Vllm VllmConfig `mapstructure:"vllm"`
|
||||
Coze CozeConfig `mapstructure:"coze"`
|
||||
LSXD LSXDConfig `mapstructure:"lsxd"`
|
||||
Sys SysConfig `mapstructure:"sys"`
|
||||
Tools ToolsConfig `mapstructure:"tools"`
|
||||
EinoTools EinoToolsConfig `mapstructure:"eino_tools"`
|
||||
|
|
@ -116,6 +117,14 @@ type CozeConfig struct {
|
|||
ApiSecret string `mapstructure:"api_secret"`
|
||||
}
|
||||
|
||||
// LSXDConfig 统一登录配置
|
||||
type LSXDConfig struct {
|
||||
LoginURL string `mapstructure:"login_url"`
|
||||
Phone string `mapstructure:"phone"`
|
||||
Password string `mapstructure:"password"`
|
||||
CheckTokenURL string `mapstructure:"check_token_url"`
|
||||
}
|
||||
|
||||
type Redis struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Type string `mapstructure:"type"`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package constants
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
CACHE_KEY_LSXD_TOKEN = "ai_scheduler:lsxd_token"
|
||||
EXPIRE_LSXD_TOKEN = time.Hour * 2 // 2小时
|
||||
)
|
||||
|
|
@ -4,18 +4,21 @@ import (
|
|||
"ai_scheduler/internal/config"
|
||||
"ai_scheduler/internal/data/impl"
|
||||
"ai_scheduler/internal/pkg/utils_oss"
|
||||
"ai_scheduler/utils"
|
||||
)
|
||||
|
||||
// Repos 聚合所有 Repository
|
||||
type Repos struct {
|
||||
Session SessionRepo
|
||||
OssClient *utils_oss.Client
|
||||
Rdb *utils.Rdb
|
||||
}
|
||||
|
||||
func NewRepos(sessionImpl *impl.SessionImpl, cfg *config.Config) *Repos {
|
||||
func NewRepos(sessionImpl *impl.SessionImpl, cfg *config.Config, rdb *utils.Rdb) *Repos {
|
||||
ossClient, _ := utils_oss.NewClient(cfg)
|
||||
return &Repos{
|
||||
Session: NewSessionAdapter(sessionImpl),
|
||||
OssClient: ossClient,
|
||||
Rdb: rdb,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package statistics_ours_product
|
|||
import (
|
||||
"ai_scheduler/internal/config"
|
||||
"ai_scheduler/internal/pkg/l_request"
|
||||
"ai_scheduler/internal/pkg/lsxd"
|
||||
"ai_scheduler/utils"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
|
@ -14,14 +16,16 @@ type Client struct {
|
|||
cfg config.ToolConfig
|
||||
}
|
||||
|
||||
// New 是一个客户端构造函数,接收一个 config.ToolConfig 类型的配置参数
|
||||
// 返回一个初始化后的 Client 指针
|
||||
func New(cfg config.ToolConfig) *Client {
|
||||
return &Client{
|
||||
cfg: cfg,
|
||||
cfg: cfg, // 将传入的配置参数赋值给 Client 结构体的 cfg 字段
|
||||
}
|
||||
}
|
||||
|
||||
// Call 调用我们的商品统计接口
|
||||
func (c *Client) Call(ctx context.Context, req StatisticsOursProductRequest) ([]StatisticsOursProductItem, error) {
|
||||
func (c *Client) Call(ctx context.Context, req StatisticsOursProductRequest, rdb *utils.Rdb, lsxdcfg *config.LSXDConfig) ([]StatisticsOursProductItem, error) {
|
||||
// 构建 URL 参数
|
||||
var queryParams []string
|
||||
|
||||
|
|
@ -44,9 +48,9 @@ func (c *Client) Call(ctx context.Context, req StatisticsOursProductRequest) ([]
|
|||
|
||||
queryString := strings.Join(queryParams, "&")
|
||||
fullURL := fmt.Sprintf("%s?%s", c.cfg.BaseURL, queryString)
|
||||
|
||||
token := lsxd.NewLogin(lsxdcfg, rdb).GetToken(ctx)
|
||||
headers := map[string]string{
|
||||
"Authorization": fmt.Sprintf("Bearer %s", c.cfg.APIKey),
|
||||
"Authorization": fmt.Sprintf("Bearer %s", token),
|
||||
}
|
||||
|
||||
reqObj := l_request.Request{
|
||||
|
|
|
|||
|
|
@ -2,17 +2,33 @@ package statistics_ours_product
|
|||
|
||||
import (
|
||||
"ai_scheduler/internal/config"
|
||||
"ai_scheduler/utils"
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClient_Call(t *testing.T) {
|
||||
cfg := config.ToolConfig{
|
||||
rdb := utils.NewRdb(&config.Config{
|
||||
Redis: config.Redis{
|
||||
Host: "47.97.27.195:6379",
|
||||
Pass: "lansexiongdi@666",
|
||||
Key: "report-api-test",
|
||||
},
|
||||
})
|
||||
|
||||
configEino := config.ToolConfig{
|
||||
BaseURL: "http://admin.1688sup.cn:8001/admin/statistics/oursProduct",
|
||||
APIKey: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyQ2VudGVyIiwiZXhwIjoxNzY3MDc3NzcwLCJuYmYiOjE3NjcwNzU5NzAsImp0aSI6IjEiLCJQaG9uZSI6IjE4MDAwMDAwMDAwIiwiVXNlck5hbWUiOiJsc3hkIiwiUmVhbE5hbWUiOiLotoXnuqfnrqHnkIblkZgiLCJBY2NvdW50VHlwZSI6MSwiR3JvdXBDb2RlcyI6IlZDTF9DQVNISUVSLFZDTF9PUEVSQVRFLFZDTF9BRE1JTixWQ0xfQUFBLFZDTF9WQ0xfT1BFUkFULFZDTF9JTlZPSUNFLENSTV9BRE1JTixMSUFOTElBTl9BRE1JTixNQVJLRVRNQUcyX0FETUlOLFBIT05FQklMTF9BRE1JTixRSUFOWkhVX1NVUFBFUl9BRE0sTUFSS0VUSU5HU0FBU19TVVBFUkFETUlOLENBUkRfQ09ERSxDQVJEX1BST0NVUkVNRU5ULE1BUktFVElOR1NZU1RFTV9TVVBFUixTVEFUSVNUSUNBTFNZU1RFTV9BRE1JTixaTFRYX0FETUlOLFpMVFhfT1BFUkFURSIsIkRpbmdVc2VySWQiOiIxNjIwMjYxMjMwMjg5MzM4MzQifQ.Nuw_aR6iSPmhhh9E5rhyTxHBsgWtaTZvbnc7SFTnUBJXTQvYahnk0LyZaVpsVw6FB3cU0F5xOdX3rmGyWyaiszWO6yi-o1oxGMXwhf39fMiWT2xUI6pAn9Ync8DmZ4tOMCNUTdEk4CaQFzrTwJs0c-VR4yW6LgoPmNPvUVZ-KwmusUpnPz5j9RsJItzIWE3bpGGsfB54e2UERcZdbo9BXxCZIBbpAYKBKdl73KuI8SNaXgKvGTrJ6hEN4ESpnbisJVwT5pp_kuChJlcfjHTHFsEf4RJDjN9gTrtDbBWZyY3OmO2ukqYAM7tZPs6TXJwvQGJQsFRVZUBGxS1nD_6DzQ",
|
||||
APIKey: "OFJ8UpqOlI7+w3Qklf36ZA==",
|
||||
APISecret: "tEbFegH/DRRh6LutFb7o3g==",
|
||||
}
|
||||
configLSXD := &config.LSXDConfig{
|
||||
LoginURL: "http://api.test.user.1688sup.com/v1/login/phone",
|
||||
Phone: "0zmINhJBwsDJYTmeaxXK4A==",
|
||||
Password: "LSSGoWhc63NpZc1rq3LT8g==",
|
||||
CheckTokenURL: "http://api.test.user.1688sup.com/v1/user/welcome",
|
||||
}
|
||||
|
||||
client := New(cfg)
|
||||
client := New(configEino)
|
||||
|
||||
req := StatisticsOursProductRequest{
|
||||
Page: 1,
|
||||
|
|
@ -24,7 +40,7 @@ func TestClient_Call(t *testing.T) {
|
|||
|
||||
// 由于没有真实的后端环境和 Token,这里注释掉实际调用
|
||||
// 在开发环境中,你可以取消注释并填入有效的 Token 进行测试
|
||||
res, err := client.Call(context.Background(), req)
|
||||
res, err := client.Call(context.Background(), req, rdb, configLSXD)
|
||||
if err != nil {
|
||||
t.Logf("Call failed: %v", err)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"ai_scheduler/internal/domain/tools/recharge/statistics_ours_product"
|
||||
"ai_scheduler/internal/domain/workflow/runtime"
|
||||
"ai_scheduler/internal/pkg/utils_oss"
|
||||
"ai_scheduler/utils"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
|
@ -24,7 +25,7 @@ const WorkflowIDStatisticsOursProduct = "recharge.statisticsOursProduct"
|
|||
|
||||
func init() {
|
||||
runtime.Register(WorkflowIDStatisticsOursProduct, func(d *runtime.Deps) (runtime.Workflow, error) {
|
||||
return &statisticsOursProduct{cfg: d.Conf, toolManager: d.ToolManager, ossClient: d.Repos.OssClient}, nil
|
||||
return &statisticsOursProduct{cfg: d.Conf, toolManager: d.ToolManager, ossClient: d.Repos.OssClient, rdb: d.Repos.Rdb}, nil
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -32,6 +33,7 @@ type statisticsOursProduct struct {
|
|||
cfg *config.Config
|
||||
toolManager *toolManager.Manager
|
||||
ossClient *utils_oss.Client
|
||||
rdb *utils.Rdb
|
||||
}
|
||||
|
||||
type StatisticsOursProductWorkflowInput struct {
|
||||
|
|
@ -103,7 +105,8 @@ func (w *statisticsOursProduct) buildWorkflow(ctx context.Context) (compose.Runn
|
|||
|
||||
// formatContext 整理上下文参数
|
||||
func (w *statisticsOursProduct) formatContext(ctx context.Context, input *StatisticsOursProductWorkflowInput) (*StatisticsOursProductContext, error) {
|
||||
startTime := input.Time.Format("2006010200")
|
||||
//startTime := input.Time.Format("2006010200")
|
||||
startTime := "2025010500"
|
||||
endTime := input.Time.Format("2006010215")
|
||||
endTimeStr := input.Time.Format("1.2号15点")
|
||||
|
||||
|
|
@ -122,7 +125,7 @@ func (w *statisticsOursProduct) callStatisticsTool(ctx context.Context, state *S
|
|||
Serial: []string{state.StartTime, state.EndTime},
|
||||
}
|
||||
|
||||
dataList, err := w.toolManager.Recharge.StatisticsOursProduct.Call(ctx, req)
|
||||
dataList, err := w.toolManager.Recharge.StatisticsOursProduct.Call(ctx, req, w.rdb, &w.cfg.LSXD)
|
||||
if err != nil {
|
||||
log.Errorf("调用统计我们的商品工具失败: %v", err)
|
||||
return nil, fmt.Errorf("获取统计我们的商品数据失败")
|
||||
|
|
@ -138,7 +141,7 @@ func (w *statisticsOursProduct) callStatisticsTool(ctx context.Context, state *S
|
|||
|
||||
func (w *statisticsOursProduct) generateExcelAndUpload(ctx context.Context, state *StatisticsOursProductContext) (*StatisticsOursProductContext, error) {
|
||||
// 1. 获取模板路径
|
||||
cwd, _ := filepath.Abs(".")
|
||||
cwd, _ := filepath.Abs("../../")
|
||||
templatePath := filepath.Join(cwd, "tmpl", "excel_temp", "recharge_statistics_ours_product.xlsx")
|
||||
fileName := fmt.Sprintf("statistics_ours_product_%d%d", time.Now().Unix(), rand.Intn(1000))
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,212 @@
|
|||
package lsxd
|
||||
|
||||
import (
|
||||
"ai_scheduler/internal/config"
|
||||
"ai_scheduler/internal/data/constants"
|
||||
"ai_scheduler/utils"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/log"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type Login struct {
|
||||
config *config.LSXDConfig
|
||||
redisCli *redis.Client
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewLogin(config *config.LSXDConfig, rdb *utils.Rdb) *Login {
|
||||
return &Login{
|
||||
config: config,
|
||||
redisCli: rdb.Rdb,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Login) GetToken(ctx context.Context) string {
|
||||
|
||||
token, err := l.getCachedToken(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("lsxd get token from redis failed, err: %v", err)
|
||||
}
|
||||
if token != "" && l.checkTokenValid(ctx, token) {
|
||||
return token
|
||||
}
|
||||
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
token, err = l.getCachedToken(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("lsxd get token from redis failed, err: %v", err)
|
||||
}
|
||||
if token != "" && l.checkTokenValid(ctx, token) {
|
||||
return token
|
||||
}
|
||||
|
||||
token, err = l.login(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("lsxd login failed, err: %v", err)
|
||||
return ""
|
||||
}
|
||||
if token == "" {
|
||||
log.Errorf("lsxd login failed, token is empty")
|
||||
return ""
|
||||
}
|
||||
|
||||
if err := l.cacheToken(ctx, token); err != nil {
|
||||
log.Errorf("lsxd cache token failed, err: %v", err)
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
// 校验token是否有效
|
||||
func (l *Login) checkTokenValid(ctx context.Context, token string) bool {
|
||||
// 欢迎页校验token有效
|
||||
checkTokenURL := l.config.CheckTokenURL
|
||||
if checkTokenURL == "" {
|
||||
return token != ""
|
||||
}
|
||||
|
||||
status, err := l.doRequest(ctx, http.MethodGet, checkTokenURL, token, nil)
|
||||
if err != nil {
|
||||
log.Errorf("lsxd check token valid failed, err: %v", err)
|
||||
return true
|
||||
}
|
||||
if status == http.StatusOK {
|
||||
return true
|
||||
}
|
||||
if status == http.StatusUnauthorized || status == http.StatusForbidden {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 调用登录接口获取token
|
||||
func (l *Login) login(ctx context.Context) (string, error) {
|
||||
// 1.获取配置
|
||||
loginURL := l.config.LoginURL
|
||||
phone := l.config.Phone
|
||||
password := l.config.Password
|
||||
|
||||
// 2.调用登录接口获取token
|
||||
if loginURL == "" {
|
||||
return "", errors.New("login url is empty")
|
||||
}
|
||||
if phone == "" || password == "" {
|
||||
return "", errors.New("phone or password is empty")
|
||||
}
|
||||
|
||||
reqBody := map[string]any{
|
||||
"phone": phone,
|
||||
"password": password,
|
||||
"code": "456789",
|
||||
}
|
||||
bodyBytes, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
status, respBody, err := l.doRequestWithBody(ctx, http.MethodPost, loginURL, "", "application/json", bodyBytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if status != http.StatusOK {
|
||||
return "", fmt.Errorf("login status code: %d", status)
|
||||
}
|
||||
|
||||
type loginResp struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Message string `json:"message"`
|
||||
AccessToken string `json:"accessToken"`
|
||||
Data struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
Token string `json:"token"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
var resp loginResp
|
||||
if err := json.Unmarshal(respBody, &resp); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
token := resp.AccessToken
|
||||
if token == "" {
|
||||
token = resp.Data.AccessToken
|
||||
}
|
||||
if token == "" {
|
||||
token = resp.Data.Token
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
return "", errors.New("token is empty")
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (l *Login) getCachedToken(ctx context.Context) (string, error) {
|
||||
token, err := l.redisCli.Get(ctx, constants.CACHE_KEY_LSXD_TOKEN).Result()
|
||||
if err == nil {
|
||||
return token, nil
|
||||
}
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
func (l *Login) cacheToken(ctx context.Context, token string) error {
|
||||
if token == "" {
|
||||
return errors.New("token is empty")
|
||||
}
|
||||
return l.redisCli.Set(ctx, constants.CACHE_KEY_LSXD_TOKEN, token, constants.EXPIRE_LSXD_TOKEN).Err()
|
||||
}
|
||||
|
||||
func (l *Login) doRequest(ctx context.Context, method string, url string, authorization string, body []byte) (int, error) {
|
||||
status, _, err := l.doRequestWithBody(ctx, method, url, authorization, "", body)
|
||||
return status, err
|
||||
}
|
||||
|
||||
func (l *Login) doRequestWithBody(ctx context.Context, method string, url string, authorization string, contentType string, body []byte) (int, []byte, error) {
|
||||
reqCtx, cancel := context.WithTimeout(ctx, 6*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var reader io.Reader
|
||||
if len(body) > 0 {
|
||||
reader = bytes.NewReader(body)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(reqCtx, method, url, reader)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if contentType != "" {
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
}
|
||||
if authorization != "" {
|
||||
req.Header.Set("Authorization", authorization)
|
||||
}
|
||||
|
||||
resp, err := (&http.Client{}).Do(req)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return resp.StatusCode, nil, err
|
||||
}
|
||||
return resp.StatusCode, respBody, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package lsxd
|
||||
|
||||
import (
|
||||
"ai_scheduler/internal/config"
|
||||
"ai_scheduler/utils"
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetToken(t *testing.T) {
|
||||
|
||||
rdb := utils.NewRdb(&config.Config{
|
||||
Redis: config.Redis{
|
||||
Host: "47.97.27.195:6379",
|
||||
Pass: "lansexiongdi@666",
|
||||
Key: "report-api-test",
|
||||
},
|
||||
})
|
||||
|
||||
login := NewLogin(&config.LSXDConfig{
|
||||
LoginURL: "http://api.test.user.1688sup.com/v1/login/phone",
|
||||
Phone: "OFJ8UpqOlI7+w3Qklf36ZA==",
|
||||
Password: "tEbFegH/DRRh6LutFb7o3g==",
|
||||
CheckTokenURL: "http://api.test.user.1688sup.com/v1/user/welcome",
|
||||
}, rdb)
|
||||
token := login.GetToken(context.Background())
|
||||
if token == "" {
|
||||
t.Errorf("token is empty")
|
||||
}
|
||||
|
||||
t.Logf("token: %s", token)
|
||||
}
|
||||
|
|
@ -66,13 +66,13 @@ func (c *CronServer) Run(ctx context.Context) {
|
|||
}
|
||||
c.log.Infof("任务[%d]:%s执行结束", jobID, job.Name)
|
||||
}()
|
||||
|
||||
c.log.Infof("任务[%d]:%s执ddd", jobID, job.Name)
|
||||
// 为每次执行创建新的上下文
|
||||
ctx := context.Background()
|
||||
err := job.Func(ctx)
|
||||
if err != nil {
|
||||
c.log.Errorf("任务[%d]:%s执行失败: %s", jobID, job.Name, err.Error())
|
||||
}
|
||||
//ctx := context.Background()
|
||||
//err := job.Func(ctx)
|
||||
//if err != nil {
|
||||
// c.log.Errorf("任务[%d]:%s执行失败: %s", jobID, job.Name, err.Error())
|
||||
//}
|
||||
})
|
||||
if err != nil {
|
||||
c.log.Errorf("添加任务失败:%s", err.Error())
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ func NewCronService(config *config.Config, dingTalkBotBiz *biz.DingTalkBotBiz) *
|
|||
|
||||
func (d *CronService) CronReportSend(ctx context.Context) error {
|
||||
|
||||
groupId := 29
|
||||
groupId := 28
|
||||
groupInfo, err := d.dingTalkBotBiz.GetGroupInfo(ctx, groupId)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -37,6 +37,11 @@ func (d *DingBotService) OnChatBotMessageReceived(ctx context.Context, data *cha
|
|||
|
||||
// 启动后台任务(独立生命周期,带超时控制)
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("稍等一下,问的人太多了,有点转不过来,请稍等下再来🚀🚀🚀")
|
||||
}
|
||||
}()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer cancel()
|
||||
if err := d.runBackgroundTasks(ctx, data, requireData); err != nil {
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ func run() {
|
|||
// 初始化Redis数据库连接
|
||||
rdb := utils.NewRdb(configConfig)
|
||||
// 初始化仓库层
|
||||
repos := repo.NewRepos(sessionImpl, configConfig)
|
||||
repos := repo.NewRepos(sessionImpl, configConfig, rdb)
|
||||
// 初始化包级别的Redis连接
|
||||
pkgRdb := pkg.NewRdb(configConfig)
|
||||
|
||||
|
|
|
|||
|
|
@ -113,20 +113,20 @@ func GetStatisOfficialProductSumApi(param *GetStatisOfficialProductSumRequest) (
|
|||
}
|
||||
|
||||
type GetStatisOfficialProductSumDeclineResponse struct {
|
||||
OfficialProductSumDecline []*GetStatisOfficialProductSumDecline `protobuf:"bytes,1,rep,name=official_product_sum_decline,json=officialProductSumDecline,proto3" json:"official_product_sum_decline,omitempty"`
|
||||
DataCount int32 `protobuf:"varint,2,opt,name=data_count,json=dataCount,proto3" json:"data_count,omitempty"`
|
||||
OfficialProductSumDecline []*GetStatisOfficialProductSumDecline `protobuf:"bytes,1,rep,name=official_product_sum_decline,json=officialProductSumDecline,proto3" json:"officialProductSumDecline,omitempty"`
|
||||
DataCount int32 `protobuf:"varint,2,opt,name=data_count,json=dataCount,proto3" json:"dataCount,omitempty"`
|
||||
}
|
||||
|
||||
type GetStatisOfficialProductSumDecline struct {
|
||||
ResellerId int32 `protobuf:"varint,1,opt,name=reseller_id,json=resellerId,proto3" json:"reseller_id,omitempty"`
|
||||
OfficialProductId int32 `protobuf:"varint,2,opt,name=official_product_id,json=officialProductId,proto3" json:"official_product_id,omitempty"`
|
||||
OfficialProductName string `protobuf:"bytes,3,opt,name=official_product_name,json=officialProductName,proto3" json:"official_product_name,omitempty"`
|
||||
ResellerName string `protobuf:"bytes,4,opt,name=reseller_name,json=resellerName,proto3" json:"reseller_name,omitempty"`
|
||||
CurrentNum int32 `protobuf:"varint,5,opt,name=current_num,json=currentNum,proto3" json:"current_num,omitempty"`
|
||||
HistoryOneNum int32 `protobuf:"varint,6,opt,name=history_one_num,json=historyOneNum,proto3" json:"history_one_num,omitempty"`
|
||||
HistoryTwoNum int32 `protobuf:"varint,7,opt,name=history_two_num,json=historyTwoNum,proto3" json:"history_two_num,omitempty"`
|
||||
HistoryOneDiff int32 `protobuf:"varint,8,opt,name=history_one_diff,json=historyOneDiff,proto3" json:"history_one_diff,omitempty"`
|
||||
HistoryTwoDiff int32 `protobuf:"varint,9,opt,name=history_two_diff,json=historyTwoDiff,proto3" json:"history_two_diff,omitempty"`
|
||||
ResellerId int32 `protobuf:"varint,1,opt,name=reseller_id,json=resellerId,proto3" json:"resellerId,omitempty"`
|
||||
OfficialProductId int32 `protobuf:"varint,2,opt,name=official_product_id,json=officialProductId,proto3" json:"officialProductId,omitempty"`
|
||||
OfficialProductName string `protobuf:"bytes,3,opt,name=official_product_name,json=officialProductName,proto3" json:"officialProductName,omitempty"`
|
||||
ResellerName string `protobuf:"bytes,4,opt,name=reseller_name,json=resellerName,proto3" json:"resellerName,omitempty"`
|
||||
CurrentNum int32 `protobuf:"varint,5,opt,name=current_num,json=currentNum,proto3" json:"currentNum,omitempty"`
|
||||
HistoryOneNum int32 `protobuf:"varint,6,opt,name=history_one_num,json=historyOneNum,proto3" json:"historyOneNum,omitempty"`
|
||||
HistoryTwoNum int32 `protobuf:"varint,7,opt,name=history_two_num,json=historyTwoNum,proto3" json:"historyTwoNum,omitempty"`
|
||||
HistoryOneDiff int32 `protobuf:"varint,8,opt,name=history_one_diff,json=historyOneDiff,proto3" json:"historyOneDiff,omitempty"`
|
||||
HistoryTwoDiff int32 `protobuf:"varint,9,opt,name=history_two_diff,json=historyTwoDiff,proto3" json:"historyTwoDiff,omitempty"`
|
||||
}
|
||||
|
||||
// GetStatisOfficialProductSumDeclineApi 销量同比分析
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
package bbxt
|
||||
|
||||
import (
|
||||
pkginner "ai_scheduler/internal/pkg"
|
||||
"ai_scheduler/internal/pkg/utils_oss"
|
||||
"ai_scheduler/pkg"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"slices"
|
||||
|
||||
"sort"
|
||||
|
||||
"time"
|
||||
|
|
@ -14,7 +14,12 @@ import (
|
|||
|
||||
const (
|
||||
RedStyle = "${color: FF0000;horizontal:center;vertical:center;borderColor:#000000}"
|
||||
GreenStyle = "${color: 00B050;horizontal:center;vertical:center;borderColor:#000000}"
|
||||
GreenStyle = "${color: 008000;horizontal:center;vertical:center;borderColor:#000000}"
|
||||
)
|
||||
|
||||
var (
|
||||
DownWardValue int32 = 1500
|
||||
SumFilter int32 = -150
|
||||
)
|
||||
|
||||
var resellerBlackList = []string{
|
||||
|
|
@ -46,7 +51,7 @@ func NewBbxtTools() (*BbxtTools, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (b *BbxtTools) DailyReport(now time.Time, productName []string, ossClient *utils_oss.Client) (reports []*ReportRes, err error) {
|
||||
func (b *BbxtTools) DailyReport(now time.Time, downWardValue int32, productName []string, sumFilter int32, ossClient *utils_oss.Client) (reports []*ReportRes, err error) {
|
||||
reports = make([]*ReportRes, 0, 4)
|
||||
productLossReport, err := b.StatisOursProductLossSum(now)
|
||||
if err != nil {
|
||||
|
|
@ -60,8 +65,12 @@ func (b *BbxtTools) DailyReport(now time.Time, productName []string, ossClient *
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
statisOfficialProductSumDecline, err := b.GetStatisOfficialProductSumDecline(now, downWardValue, productName, sumFilter)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
reports = append(reports, productLossReport...)
|
||||
reports = append(reports, statisOfficialProductSum, profitRankingSum)
|
||||
reports = append(reports, profitRankingSum, statisOfficialProductSum, statisOfficialProductSumDecline)
|
||||
|
||||
if ossClient != nil {
|
||||
uploader := NewUploader(ossClient)
|
||||
|
|
@ -145,12 +154,12 @@ func (b *BbxtTools) StatisOursProductLossSum(now time.Time) (report []*ReportRes
|
|||
fmt.Sprintf("%s", v.ResellerName),
|
||||
fmt.Sprintf("%.2f", v.Total),
|
||||
})
|
||||
totalSum += v.Total
|
||||
}
|
||||
if v.Total <= -500 && !slices.Contains(resellerBlackList, v.ResellerName) {
|
||||
gt = append(gt, v)
|
||||
totalSum500 += v.Total
|
||||
}
|
||||
totalSum += v.Total
|
||||
}
|
||||
report = make([]*ReportRes, 2)
|
||||
timeCh := now.Format("1月2日15点")
|
||||
|
|
@ -160,20 +169,22 @@ func (b *BbxtTools) StatisOursProductLossSum(now time.Time) (report []*ReportRes
|
|||
err = b.SimpleFillExcelWithTitle(b.excelTempDir+"/"+"kshj_total.xlsx", filePath, total, "")
|
||||
report[0] = &ReportRes{
|
||||
ReportName: "负利润分析(合计表)",
|
||||
Title: "截至今日" + timeCh + "利润累计亏损" + fmt.Sprintf("%.2f", totalSum),
|
||||
Title: "截至" + timeCh + "利润累计亏损" + fmt.Sprintf("%.2f", totalSum),
|
||||
Path: filePath,
|
||||
Data: total,
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(gt) > 0 {
|
||||
filePath := b.cacheDir + "/kshj_gt" + fmt.Sprintf("%d%d", time.Now().Unix(), rand.Intn(1000)) + ".xlsx"
|
||||
err = b.resellerDetailFillExcelV2(b.excelTempDir+"/"+"kshj_gt.xlsx", filePath, gt)
|
||||
title := "截至" + timeCh + "亏损500以上的分销商和产品"
|
||||
err = b.resellerDetailFillExcelV2(b.excelTempDir+"/"+"kshj_gt.xlsx", filePath, gt, title)
|
||||
report[1] = &ReportRes{
|
||||
ReportName: "负利润分析(亏损500以上)",
|
||||
Title: "截至今日" + timeCh + "亏顺500以上利润累计亏损" + fmt.Sprintf("%.2f", totalSum500),
|
||||
Title: "截至" + timeCh + "亏损500以上利润累计亏损" + fmt.Sprintf("%.2f", totalSum500),
|
||||
Path: filePath,
|
||||
Data: total,
|
||||
}
|
||||
|
|
@ -312,6 +323,78 @@ func (b *BbxtTools) GetStatisOfficialProductSum(now time.Time, productName []str
|
|||
}, err
|
||||
}
|
||||
|
||||
// GetStatisOfficialProductSumDecline 销量下滑明细
|
||||
func (b *BbxtTools) GetStatisOfficialProductSumDecline(now time.Time, downWardValue int32, productName []string, sumFilter int32) (report *ReportRes, err error) {
|
||||
|
||||
var productMap = make(map[string]int)
|
||||
for k, v := range productName {
|
||||
productMap[v] = k
|
||||
}
|
||||
ct := []string{
|
||||
time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).Format("2006-01-02 15:04:05"),
|
||||
adjustedTime(now),
|
||||
}
|
||||
var ids []int32
|
||||
if len(productName) > 0 {
|
||||
ids, err = b.getProductIdFromProductName(productName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
reqParam := &GetStatisOfficialProductSumRequest{
|
||||
Ct: ct,
|
||||
DownwardValue: downWardValue,
|
||||
OfficialProductId: ids,
|
||||
Page: 1,
|
||||
Limit: 1000,
|
||||
}
|
||||
|
||||
data, err := GetStatisOfficialProductSumDeclineApi(reqParam)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var (
|
||||
productSumMap = make(map[int32]ProductSumDecline)
|
||||
)
|
||||
for _, v := range data.OfficialProductSumDecline {
|
||||
if _, ex := productSumMap[v.OfficialProductId]; !ex {
|
||||
productSumMap[v.OfficialProductId] = ProductSumDecline{
|
||||
OfficialProductName: v.OfficialProductName,
|
||||
OfficialProductId: v.OfficialProductId,
|
||||
ProductSumReseller: make(map[int32]ProductSumReseller),
|
||||
}
|
||||
|
||||
}
|
||||
if v.HistoryOneDiff <= sumFilter || v.HistoryTwoDiff <= sumFilter {
|
||||
productSumMap[v.OfficialProductId].ProductSumReseller[v.ResellerId] = ProductSumReseller{
|
||||
ResellerName: v.ResellerName,
|
||||
CurrentNum: v.CurrentNum,
|
||||
HistoryOneNum: v.HistoryOneNum,
|
||||
HistoryOneDiff: v.HistoryOneDiff,
|
||||
HistoryTwoNum: v.HistoryTwoNum,
|
||||
HistoryTwoDiff: v.HistoryTwoDiff,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
timeCh := now.Format("1月2日15点")
|
||||
//title := "截至" + timeCh + "销量下滑大于" + fmt.Sprintf("%d", downWardValue) + "明细,分销商仅展示差额大于" + fmt.Sprintf("%d", -sumFilter)
|
||||
title := "截至" + timeCh + "销量下滑较大商品"
|
||||
//总量生成excel
|
||||
if len(productSumMap) == 0 {
|
||||
return
|
||||
}
|
||||
filePath := b.cacheDir + "/xlxhmx" + fmt.Sprintf("%d%d", time.Now().Unix(), rand.Intn(1000)) + ".xlsx"
|
||||
err = b.OfficialProductSumDeclineExcel(b.excelTempDir+"/"+"/xlxhmx.xlsx", filePath, productSumMap, title)
|
||||
return &ReportRes{
|
||||
ReportName: "销售下滑明细",
|
||||
Title: title,
|
||||
Path: filePath,
|
||||
Desc: pkginner.JsonStringIgonErr(productSumMap),
|
||||
}, err
|
||||
}
|
||||
|
||||
func (b *BbxtTools) getProductIdFromProductName(productNames []string) ([]int32, error) {
|
||||
data, err := GetStatisFilterOfficialProductApi(&GetStatisFilterOfficialProductRequest{})
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ func Test_StatisOursProductLossSumApiTotal(t *testing.T) {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
reports, err := o.DailyReport(time.Now(), []string{"官方-爱奇艺-星钻季卡", "官方-爱奇艺-星钻半年卡", "官方--腾讯-年卡", "官方--爱奇艺-月卡"}, ossClient)
|
||||
reports, err := o.DailyReport(time.Now(), DownWardValue, []string{"官方-爱奇艺-星钻季卡", "官方-爱奇艺-星钻半年卡", "官方--腾讯-年卡", "官方--爱奇艺-月卡"}, SumFilter, ossClient)
|
||||
|
||||
t.Log(reports, err)
|
||||
|
||||
|
|
@ -55,6 +55,19 @@ func Test_GetProfitRankingSum(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
func Test_GetStatisOfficialProductSumDecline(t *testing.T) {
|
||||
o, err := NewBbxtTools()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s := "官方--美团外卖红包5元,官方--美团外卖红包10元,官方--饿了么超级会员月卡,官方--网易云黑胶vip月卡,官方--喜马拉雅巅峰会员月卡,官方--芒果-PC季卡,官方--芒果-PC月卡,官方--芒果-PC周卡,官方--腾讯-周卡,官方--优酷周卡,官方--QQ音乐-绿钻月卡,官方--爱奇艺-周卡,官方--腾讯-月卡,官方--腾讯-季卡,官方--腾讯-年卡,官方--优酷月卡,官方--优酷季卡,官方--优酷年卡,官方--爱奇艺-月卡,官方--爱奇艺-季卡,官方--爱奇艺-年卡"
|
||||
//s := "官方--QQ音乐-绿钻月卡"
|
||||
report, err := o.GetStatisOfficialProductSumDecline(time.Now(), 1000, strings.Split(s, ","), -150)
|
||||
|
||||
t.Log(report, err)
|
||||
|
||||
}
|
||||
|
||||
func Test_GetStatisOfficialProductSum(t *testing.T) {
|
||||
o, err := NewBbxtTools()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -21,3 +21,18 @@ type ReportRes struct {
|
|||
Data [][]string
|
||||
Desc string
|
||||
}
|
||||
|
||||
type ProductSumDecline struct {
|
||||
OfficialProductId int32
|
||||
OfficialProductName string
|
||||
ProductSumReseller map[int32]ProductSumReseller
|
||||
}
|
||||
|
||||
type ProductSumReseller struct {
|
||||
ResellerName string
|
||||
CurrentNum int32 //今日成功数量
|
||||
HistoryOneNum int32 //昨日成功数量
|
||||
HistoryOneDiff int32 //同比昨日当前增减量
|
||||
HistoryTwoNum int32 //上周成功数量
|
||||
HistoryTwoDiff int32 //同比上周当前增减量
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ func (b *BbxtTools) SimpleFillExcelWithTitle(templatePath, outputPath string, da
|
|||
log.Errorf("获取模板行高失败: %v", err)
|
||||
rowHeight = 31 // 默认高度
|
||||
}
|
||||
|
||||
rowWidth := 25.00
|
||||
// 反射获取切片数据
|
||||
v := reflect.ValueOf(dataSlice)
|
||||
if v.Kind() != reflect.Slice {
|
||||
|
|
@ -101,7 +101,7 @@ func (b *BbxtTools) SimpleFillExcelWithTitle(templatePath, outputPath string, da
|
|||
// 填充数据到Excel
|
||||
for col, value := range rowData {
|
||||
cell := fmt.Sprintf("%c%d", 'A'+col, currentRow)
|
||||
|
||||
f.SetColWidth(sheet, cell, cell, rowWidth)
|
||||
switch value.(type) {
|
||||
case string:
|
||||
var style = value.(string)
|
||||
|
|
@ -144,6 +144,8 @@ func SetStyle(styleMap map[string]string, f *excelize.File) (int, error) {
|
|||
if colorHex, exists := styleMap["color"]; exists {
|
||||
style.Font = &excelize.Font{
|
||||
Color: colorHex,
|
||||
Family: "SimHei",
|
||||
Bold: true,
|
||||
}
|
||||
}
|
||||
// 设置水平对齐
|
||||
|
|
@ -180,7 +182,7 @@ func SetStyle(styleMap map[string]string, f *excelize.File) (int, error) {
|
|||
// 4.以ResellerName分组,合并单元格
|
||||
// 5.在文件末尾使用“合计”,合计行样式为模板第四行
|
||||
// 6.保存为新文件
|
||||
func (b *BbxtTools) resellerDetailFillExcelV2(templatePath, outputPath string, dataSlice []*ResellerLoss) error {
|
||||
func (b *BbxtTools) resellerDetailFillExcelV2(templatePath, outputPath string, dataSlice []*ResellerLoss, title string) error {
|
||||
// 1. 读取模板
|
||||
f, err := excelize.OpenFile(templatePath)
|
||||
if err != nil {
|
||||
|
|
@ -189,7 +191,10 @@ func (b *BbxtTools) resellerDetailFillExcelV2(templatePath, outputPath string, d
|
|||
defer f.Close()
|
||||
|
||||
sheet := f.GetSheetName(0)
|
||||
|
||||
if len(title) > 0 {
|
||||
// 写入标题
|
||||
f.SetCellValue(sheet, "A1", title)
|
||||
}
|
||||
// ---------------- 样式获取 ----------------
|
||||
// 模板第2行:数据行样式
|
||||
tplRowData := 2
|
||||
|
|
@ -305,3 +310,5 @@ func (b *BbxtTools) resellerDetailFillExcelV2(templatePath, outputPath string, d
|
|||
// 6. 保存
|
||||
return f.SaveAs(outputPath)
|
||||
}
|
||||
|
||||
// OfficialProductSumDeclineExcel
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
|
|
@ -170,3 +172,171 @@ func (u *Uploader) uploadToOSS(fileName string, fileBytes []byte) string {
|
|||
// }
|
||||
// return url
|
||||
//}
|
||||
|
||||
func (b *BbxtTools) OfficialProductSumDeclineExcel(templatePath, outputPath string, sumMap map[int32]ProductSumDecline, title string) error {
|
||||
// 1. 读取模板
|
||||
f, err := excelize.OpenFile(templatePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
sheet := f.GetSheetName(0)
|
||||
if len(title) > 0 {
|
||||
// 写入标题
|
||||
f.SetCellValue(sheet, "A1", title)
|
||||
}
|
||||
// ---------------- 样式获取 ----------------
|
||||
// 模板第2行:数据行样式
|
||||
tplRowData := 3
|
||||
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
|
||||
}
|
||||
styleD2, err := f.GetCellStyle(sheet, fmt.Sprintf("D%d", tplRowData))
|
||||
if err != nil {
|
||||
styleC2 = 0
|
||||
}
|
||||
//styleE2, err := f.GetCellStyle(sheet, fmt.Sprintf("E%d", tplRowData))
|
||||
//if err != nil {
|
||||
// styleC2 = 0
|
||||
//}
|
||||
styleF2, err := f.GetCellStyle(sheet, fmt.Sprintf("F%d", tplRowData))
|
||||
if err != nil {
|
||||
styleC2 = 0
|
||||
}
|
||||
//styleG2, err := f.GetCellStyle(sheet, fmt.Sprintf("G%d", tplRowData))
|
||||
//if err != nil {
|
||||
// styleC2 = 0
|
||||
//}
|
||||
|
||||
rowHeightData, err := f.GetRowHeight(sheet, tplRowData)
|
||||
if err != nil {
|
||||
rowHeightData = 20
|
||||
}
|
||||
rowWidth := 25.00
|
||||
currentRow := 3
|
||||
pattern := `\$\{(.*?)\}`
|
||||
re := regexp.MustCompile(pattern)
|
||||
for _, product := range sumMap {
|
||||
// 排序 ProductLoss
|
||||
var reseller []ProductSumReseller
|
||||
for _, p := range product.ProductSumReseller {
|
||||
reseller = append(reseller, p)
|
||||
}
|
||||
sort.Slice(reseller, func(i, j int) bool {
|
||||
return reseller[i].HistoryOneDiff < reseller[j].HistoryOneDiff
|
||||
})
|
||||
|
||||
startRow := currentRow
|
||||
|
||||
// 填充该经销商的所有产品
|
||||
for _, p := range reseller {
|
||||
// 设置行高
|
||||
var (
|
||||
oneDiff string
|
||||
twoDiff string
|
||||
)
|
||||
f.SetRowHeight(sheet, currentRow, rowHeightData)
|
||||
if p.HistoryOneDiff >= 0 {
|
||||
oneDiff = fmt.Sprintf("%s↑%d", RedStyle, p.HistoryOneDiff)
|
||||
} else {
|
||||
oneDiff = fmt.Sprintf("%s↓%d", GreenStyle, p.HistoryOneDiff)
|
||||
}
|
||||
if p.HistoryTwoDiff >= 0 {
|
||||
twoDiff = fmt.Sprintf("%s↑%d", RedStyle, p.HistoryTwoDiff)
|
||||
} else {
|
||||
twoDiff = fmt.Sprintf("%s↓%d", GreenStyle, p.HistoryTwoDiff)
|
||||
}
|
||||
matchesE := re.FindStringSubmatch(oneDiff)
|
||||
styleMap := make(map[string]string)
|
||||
|
||||
if len(matchesE) != 2 {
|
||||
continue
|
||||
}
|
||||
for _, kv := range strings.Split(matchesE[1], ";") {
|
||||
kvParts := strings.Split(kv, ":")
|
||||
if len(kvParts) == 2 {
|
||||
styleMap[strings.TrimSpace(kvParts[0])] = strings.TrimSpace(kvParts[1])
|
||||
}
|
||||
}
|
||||
fontStyleIDE, _err := SetStyle(styleMap, f)
|
||||
if _err != nil {
|
||||
log.Errorf("set style failed: %v", _err)
|
||||
}
|
||||
f.SetCellStyle(sheet, fmt.Sprintf("E%d", currentRow), fmt.Sprintf("E%d", currentRow), fontStyleIDE)
|
||||
oneDiffValue := re.ReplaceAllString(oneDiff, "")
|
||||
matchesG := re.FindStringSubmatch(twoDiff)
|
||||
styleMapG := make(map[string]string)
|
||||
if len(matchesG) != 2 {
|
||||
continue
|
||||
}
|
||||
for _, kv := range strings.Split(matchesG[1], ";") {
|
||||
kvParts := strings.Split(kv, ":")
|
||||
if len(kvParts) == 2 {
|
||||
styleMapG[strings.TrimSpace(kvParts[0])] = strings.TrimSpace(kvParts[1])
|
||||
}
|
||||
}
|
||||
fontStyleIDG, _err := SetStyle(styleMapG, f)
|
||||
if _err != nil {
|
||||
log.Errorf("set style failed: %v", _err)
|
||||
}
|
||||
twoDiffValue := re.ReplaceAllString(twoDiff, "")
|
||||
f.SetCellStyle(sheet, fmt.Sprintf("G%d", currentRow), fmt.Sprintf("G%d", currentRow), fontStyleIDG)
|
||||
// 设置值
|
||||
//f.SetColWidth(sheet, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("A%d", currentRow), product.OfficialProductName)
|
||||
//f.SetColWidth(sheet, fmt.Sprintf("B%d", currentRow), fmt.Sprintf("B%d", currentRow), p.ResellerName)
|
||||
f.SetColWidth(sheet, fmt.Sprintf("C%d", currentRow), fmt.Sprintf("C%d", currentRow), rowWidth)
|
||||
f.SetColWidth(sheet, fmt.Sprintf("D%d", currentRow), fmt.Sprintf("D%d", currentRow), rowWidth)
|
||||
f.SetColWidth(sheet, fmt.Sprintf("E%d", currentRow), fmt.Sprintf("E%d", currentRow), rowWidth)
|
||||
f.SetColWidth(sheet, fmt.Sprintf("F%d", currentRow), fmt.Sprintf("F%d", currentRow), rowWidth)
|
||||
f.SetColWidth(sheet, fmt.Sprintf("G%d", currentRow), fmt.Sprintf("G%d", currentRow), rowWidth)
|
||||
|
||||
f.SetCellValue(sheet, fmt.Sprintf("A%d", currentRow), product.OfficialProductName)
|
||||
f.SetCellValue(sheet, fmt.Sprintf("B%d", currentRow), p.ResellerName)
|
||||
f.SetCellValue(sheet, fmt.Sprintf("C%d", currentRow), p.CurrentNum)
|
||||
f.SetCellValue(sheet, fmt.Sprintf("D%d", currentRow), p.HistoryOneNum)
|
||||
f.SetCellValue(sheet, fmt.Sprintf("E%d", currentRow), oneDiffValue)
|
||||
f.SetCellValue(sheet, fmt.Sprintf("F%d", currentRow), p.HistoryTwoNum)
|
||||
f.SetCellValue(sheet, fmt.Sprintf("G%d", currentRow), twoDiffValue)
|
||||
|
||||
// 设置样式
|
||||
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)
|
||||
}
|
||||
if styleD2 != 0 {
|
||||
f.SetCellStyle(sheet, fmt.Sprintf("D%d", currentRow), fmt.Sprintf("D%d", currentRow), styleC2)
|
||||
}
|
||||
|
||||
if styleF2 != 0 {
|
||||
f.SetCellStyle(sheet, fmt.Sprintf("F%d", currentRow), fmt.Sprintf("F%d", currentRow), styleC2)
|
||||
}
|
||||
|
||||
currentRow++
|
||||
}
|
||||
|
||||
endRow := currentRow - 1
|
||||
// 合并单元格 (如果多于1行)
|
||||
if endRow > startRow {
|
||||
f.MergeCell(sheet, fmt.Sprintf("A%d", startRow), fmt.Sprintf("A%d", endRow))
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 保存
|
||||
return f.SaveAs(outputPath)
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue