diff --git a/cmd/server/main.go b/cmd/server/main.go index cf658c3..806c43d 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -12,6 +12,7 @@ import ( func main() { configPath := flag.String("config", "./config/config_test.yaml", "Path to configuration file") onBot := flag.String("bot", "", "bot start") + cron := flag.String("cron", "", "close") flag.Parse() ctx := context.Background() bc, err := config.LoadConfig(*configPath) @@ -29,7 +30,7 @@ func main() { //钉钉机器人 app.DingBotServer.Run(ctx, *onBot) //定时任务 - 测试环境不启用 - if configPath != nil && *configPath == "./config/config.yaml" { + if *cron == "start" { app.Cron.Run(ctx) } diff --git a/config/config.yaml b/config/config.yaml index 3b4fab8..6586e20 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -108,6 +108,8 @@ tools: base_url: "https://api.coze.cn" api_key: "7583905168607100978" api_secret: "pat_eEN0BdLNDughEtABjJJRYTW71olvDU0qUbfQUeaPc2NnYWO8HeyNoui5aR9z0sSZ" + zltxResellerAuthProductToManagerAndDefaultLossReason: + base_url: "https://revcl.1688sup.com/api/admin/resellerAuthProductToManagerAndDefaultLossReason" # eino tool 配置 eino_tools: diff --git a/config/config_env.yaml b/config/config_env.yaml index d99d144..1b1a8bd 100644 --- a/config/config_env.yaml +++ b/config/config_env.yaml @@ -29,7 +29,7 @@ lsxd: phone: "ORlviZN7N06W2+WKLe76xg==" password: "V5Uh8C4bamEM6UQZh4TCeQ==" check_token_url: "https://api.user.1688sup.com/v1/user/welcome" - + code: "456789" sys: session_len: 6 diff --git a/config/config_test.yaml b/config/config_test.yaml index 4c8d69e..d7d519d 100644 --- a/config/config_test.yaml +++ b/config/config_test.yaml @@ -26,14 +26,12 @@ coze: lsxd: # 统一登录 - login_url: "http://api.test.user.1688sup.com/v1/login/phone" - phone: "OFJ8UpqOlI7+w3Qklf36ZA==" - password: "tEbFegH/DRRh6LutFb7o3g==" - code: "123456" - check_token_url: "http://api.test.user.1688sup.com/v1/user/welcome" + login_url: "https://api.user.1688sup.com/v1/login/phone" + phone: "ORlviZN7N06W2+WKLe76xg==" + password: "V5Uh8C4bamEM6UQZh4TCeQ==" + code: "456789" + check_token_url: "https://api.user.1688sup.com/v1/user/welcome" -zltx: - req_url: "https://gateway.dev.cdlsxd.cn/zltx_api" sys: session_len: 6 @@ -106,6 +104,8 @@ tools: base_url: "https://api.coze.cn" api_key: "7583905168607100978" api_secret: "pat_eEN0BdLNDughEtABjJJRYTW71olvDU0qUbfQUeaPc2NnYWO8HeyNoui5aR9z0sSZ" + zltxResellerAuthProductToManagerAndDefaultLossReason: + base_url: "https://revcl.1688sup.com/api/admin/reseller/resellerAuthProduct/getManagerAndDefaultLossReason" # eino tool 配置 eino_tools: diff --git a/deploy.sh b/deploy.sh index 1ad338a..a9f6bd0 100644 --- a/deploy.sh +++ b/deploy.sh @@ -16,10 +16,12 @@ fi CONFIG_FILE="config/config.yaml" BRANCH="master" BOT="All" +CRON="start" if [ "$MODE" = "dev" ]; then CONFIG_FILE="config/config_test.yaml" BOT="zltx" BRANCH="test" + CRON="close" fi git fetch origin @@ -47,6 +49,8 @@ docker run -itd \ -v ./cache:/app/cache \ -v ./tmpl:/app/tmpl \ -v ./go.mod:/app/go.mod \ - "${CONTAINER_NAME}" ./server --config "./${CONFIG_FILE}" --bot "${BOT}" + "${CONTAINER_NAME}" ./server \ + --config "./${CONFIG_FILE}" --bot "${BOT}" --cron "${CRON}" + docker logs -f ${CONTAINER_NAME} \ No newline at end of file diff --git a/internal/biz/ding_talk_bot.go b/internal/biz/ding_talk_bot.go index a04fd99..d2fe01a 100644 --- a/internal/biz/ding_talk_bot.go +++ b/internal/biz/ding_talk_bot.go @@ -19,7 +19,6 @@ import ( "fmt" "strings" "time" - "unicode" "gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go/chatbot" @@ -45,6 +44,7 @@ type DingTalkBotBiz struct { qywxGroupHandle *qywx.Group groupConfigBiz *GroupConfigBiz reportDailyCacheImpl *impl.ReportDailyCacheImpl + macro *do.Macro } // NewDingTalkBotBiz @@ -60,6 +60,7 @@ func NewDingTalkBotBiz( conf *config.Config, cardSend *dingtalk.SendCardClient, groupConfigBiz *GroupConfigBiz, + macro *do.Macro, ) *DingTalkBotBiz { return &DingTalkBotBiz{ do: do, @@ -74,6 +75,7 @@ func NewDingTalkBotBiz( conf: conf, cardSend: cardSend, reportDailyCacheImpl: reportDailyCacheImpl, + macro: macro, } } @@ -148,10 +150,14 @@ func (d *DingTalkBotBiz) handleGroupChat(ctx context.Context, requireData *entit return } //宏 - err, isFinal := d.Macro(ctx, requireData, groupConfig) + sucMsg, err, isFinal := d.macro.Router(ctx, requireData.Req.Text.Content, groupConfig) if err != nil { + entitys.ResText(requireData.Ch, "", err.Error()) return } + if len(sucMsg) > 0 { + entitys.ResText(requireData.Ch, "", sucMsg) + } if isFinal { return } @@ -168,102 +174,6 @@ func (d *DingTalkBotBiz) handleGroupChat(ctx context.Context, requireData *entit return d.groupConfigBiz.handleMatch(ctx, rec, groupConfig) } -func (d *DingTalkBotBiz) Macro(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, groupConfig *model.AiBotGroupConfig) (err error, isFinish bool) { - content := processString(requireData.Req.Text.Content) - - if strings.Contains(content, "[利润同比报表]商品修改:") { - // 提取冒号后的内容 - if parts := strings.SplitN(content, ":", 2); len(parts) == 2 { - itemInfo := strings.TrimSpace(parts[1]) - log.Infof("商品修改信息: %s", itemInfo) - groupConfig.ProductName = itemInfo - cond := builder.NewCond() - cond = cond.And(builder.Eq{"config_id": groupConfig.ConfigID}) - err = d.botGroupImpl.UpdateByCond(&cond, groupConfig) - if err != nil { - entitys.ResText(requireData.Ch, "", fmt.Sprintf("修改失败:%v", err)) - } - entitys.ResText(requireData.Ch, "", "修改成功") - isFinish = true - return - } - } - - if strings.Contains(content, "[利润同比报表]商品列表") { - // 提取冒号后的内容 - if len(groupConfig.ProductName) == 0 { - entitys.ResText(requireData.Ch, "", "暂未设置") - } else { - entitys.ResText(requireData.Ch, "", groupConfig.ProductName) - isFinish = true - } - return - } - - if strings.Contains(content, "[负利润分析]获取") { - var ( - data model.AiReportDailyCache - value map[int32]*bbxt.ResellerLossSumProductRelation - ) - cond := builder.NewCond() - cond = cond.And(builder.Eq{"`index`": bbxt.IndexLossSumDetail}) - cond = cond.And(builder.Eq{"`key`": time.Now().Format(time.DateOnly)}) - err = d.reportDailyCacheImpl.GetOneBySearchToStrut(&cond, &data) - if err != nil { - entitys.ResText(requireData.Ch, "", "获取失败") - return - } - err = json.Unmarshal([]byte(data.Value), &value) - if err != nil { - entitys.ResText(requireData.Ch, "", "获取失败,格式解析错误") - return - } - - return - } - - if strings.Contains(content, "[负利润分析]更新") { - // 提取冒号后的内容 - if len(groupConfig.ProductName) == 0 { - entitys.ResText(requireData.Ch, "", "暂未设置") - } else { - entitys.ResText(requireData.Ch, "", groupConfig.ProductName) - isFinish = true - } - return - } - - if strings.Contains(content, "[负利润分析]同步") { - // 提取冒号后的内容 - if len(groupConfig.ProductName) == 0 { - entitys.ResText(requireData.Ch, "", "暂未设置") - } else { - entitys.ResText(requireData.Ch, "", groupConfig.ProductName) - isFinish = true - } - return - } - return -} - -func processString(s string) string { - // 1. 替换中文逗号为英文逗号 - s = strings.ReplaceAll(s, ",", ",") - - // 2. 过滤控制字符(如 \n, \t, \r 等) - var result []rune - for _, char := range s { - // 判断是否是控制字符(ASCII < 32 或 = 127) - if !unicode.IsControl(char) { - // 如果需要完全移除 \n 和 \t,可以改成: - // if !unicode.IsControl(char) - result = append(result, char) - } - } - - return string(result) -} - func (d *DingTalkBotBiz) initGroup(ctx context.Context, conversationId string, conversationTitle string, robotCode string) (group *model.AiBotGroup, err error) { group, err = d.botGroupImpl.GetByConversationIdAndRobotCode(conversationId, robotCode) if err != nil { diff --git a/internal/biz/do/macro.go b/internal/biz/do/macro.go new file mode 100644 index 0000000..ae6b9fe --- /dev/null +++ b/internal/biz/do/macro.go @@ -0,0 +1,288 @@ +package do + +import ( + "ai_scheduler/internal/data/impl" + "ai_scheduler/internal/data/model" + "ai_scheduler/internal/pkg" + + "ai_scheduler/internal/tools/bbxt" + + "context" + "encoding/json" + "fmt" + + "strconv" + "strings" + "time" + "unicode" + + "github.com/gofiber/fiber/v2/log" + "xorm.io/builder" +) + +type Macro struct { + botGroupImpl *impl.BotGroupImpl + reportDailyCacheImpl *impl.ReportDailyCacheImpl +} + +func NewMacro( + botGroupImpl *impl.BotGroupImpl, + reportDailyCacheImpl *impl.ReportDailyCacheImpl, +) *Macro { + return &Macro{ + botGroupImpl: botGroupImpl, + reportDailyCacheImpl: reportDailyCacheImpl, + } +} + +type MacroFunc func(ctx context.Context, content string, groupConfig *model.AiBotGroupConfig) (successMsg string, err error, isFinish bool) + +func (m *Macro) Router(ctx context.Context, reqContent string, groupConfig *model.AiBotGroupConfig) (successMsg string, err error, isFinish bool) { + reqContent = strings.TrimSpace(reqContent) + switch { + case strings.HasPrefix(reqContent, "[利润同比报表]商品修改"): + return m.ProductModify(ctx, reqContent, groupConfig) + case strings.HasPrefix(reqContent, "[利润同比报表]商品列表"): + return m.ProductList(ctx, groupConfig) + case strings.HasPrefix(reqContent, "[负利润分析]导出"): + return m.NegativeProfitGet(ctx) + case strings.HasPrefix(reqContent, "[负利润分析]更新"): + return m.NegativeProfitUpdate(ctx, reqContent, groupConfig) + default: + + } + return +} + +func (m *Macro) NegativeProfitUpdate(ctx context.Context, content string, groupConfig *model.AiBotGroupConfig) (successMsg string, err error, isFinish bool) { + //newContent := strings.ReplaceAll(strings.TrimSpace(content), "[负利润分析]更新:", "") + jsonData, err := ParseLossData(content) + if err != nil { + err = fmt.Errorf("解析失败:%v", err) + return + } + dayDate := time.Now().Format(time.DateOnly) + cond := builder.NewCond() + cond = cond.And(builder.Eq{"cache_index": bbxt.IndexLossSumDetail}) + cond = cond.And(builder.Eq{"cache_key": dayDate}) + err = m.reportDailyCacheImpl.UpdateByCond(&cond, &model.AiReportDailyCache{ + Value: pkg.JsonStringIgonErr(jsonData), + }) + if err != nil { + err = fmt.Errorf("解析失败:%v", err) + return + } + isFinish = true + successMsg = "更新成功" + return +} + +func (m *Macro) NegativeProfitGet(ctx context.Context) (successMsg string, err error, isFinish bool) { + var ( + data model.AiReportDailyCache + value map[int32]*bbxt.ResellerLossSumProductRelation + ) + isFinish = true + cond := builder.NewCond() + cond = cond.And(builder.Eq{"cache_index": bbxt.IndexLossSumDetail}) + cond = cond.And(builder.Eq{"cache_key": time.Now().Format(time.DateOnly)}) + err = m.reportDailyCacheImpl.GetOneBySearchToStrut(&cond, &data) + if err != nil { + err = fmt.Errorf("获取失败:%v", err) + return + } + if data.ID == 0 { + successMsg = "暂未获取今日负利润分析数据,请先呼出报表数据" + return + } + err = json.Unmarshal([]byte(data.Value), &value) + if err != nil { + err = fmt.Errorf("获取失败,格式解析错误:%v", err) + return + } + + //将value转为string + //**[供应商id]供应商名称->商务名称**\n + //└──商品名称:亏损原因\n + var valueString strings.Builder + valueString.WriteString("[负利润分析]更新:\n") + for k, v := range value { + if len(v.AfterSaleName) == 0 { + v.AfterSaleName = "未查找到对应商务" + } + valueString.WriteString(fmt.Sprintf("**[%d]%s->%s**\n", k, v.ResellerName, v.AfterSaleName)) + for kk, vv := range v.Products { + valueString.WriteString(fmt.Sprintf("└── [%d]%s:%s\n", kk, vv.ProductName, vv.LossReason)) + } + } + successMsg = valueString.String() + return +} + +func (m *Macro) ProductModify(ctx context.Context, content string, groupConfig *model.AiBotGroupConfig) (successMsg string, err error, isFinish bool) { + content = processString(content) + if parts := strings.SplitN(content, ":", 2); len(parts) == 2 { + itemInfo := strings.TrimSpace(parts[1]) + log.Infof("商品修改信息: %s", itemInfo) + groupConfig.ProductName = itemInfo + cond := builder.NewCond() + cond = cond.And(builder.Eq{"config_id": groupConfig.ConfigID}) + err = m.botGroupImpl.UpdateByCond(&cond, groupConfig) + if err != nil { + err = fmt.Errorf("修改失败:%v", err) + return + } + successMsg = "修改成功" + isFinish = true + return + } + return +} + +func (m *Macro) ProductList(ctx context.Context, groupConfig *model.AiBotGroupConfig) (successMsg string, err error, isFinish bool) { + if len(groupConfig.ProductName) == 0 { + successMsg = "暂未设置" + } else { + successMsg = groupConfig.ProductName + isFinish = true + } + return +} + +func processString(s string) string { + // 替换中文逗号为英文逗号 + s = strings.ReplaceAll(s, ",", ",") + + return string(clearSpacialFormat(s)) +} + +func clearSpacialFormat(s string) (result []rune) { + //过滤控制字符(如 \n, \t, \r 等) + for _, char := range s { + // 判断是否是控制字符(ASCII < 32 或 = 127) + if !unicode.IsControl(char) { + // 如果需要完全移除 \n 和 \t,可以改成: + // if !unicode.IsControl(char) + result = append(result, char) + } + } + return +} + +func ParseLossData(input string) (map[int32]*bbxt.ResellerLossSumProductRelation, error) { + result := make(map[int32]*bbxt.ResellerLossSumProductRelation) + + // 按行分割 + lines := strings.Split(input, "\n") + + // 跳过第一行的标题 + startIdx := 0 + for i, line := range lines { + if strings.HasPrefix(line, "[") && strings.Contains(line, "]") && strings.Contains(line, "->") { + startIdx = i + break + } + } + + var currentResellerID int32 + var currentReseller *bbxt.ResellerLossSumProductRelation + + for i := startIdx; i < len(lines); i++ { + line := strings.TrimSpace(lines[i]) + if line == "" { + continue + } + + // 检查是否是供应商行(以 [ 开头) + if strings.HasPrefix(line, "[") && strings.Contains(line, "]") { + // 解析供应商行:[25131]兴业银行-营销系统->未查找到对应商务 + + // 找到第一个 ] 的位置 + bracketEnd := strings.Index(line, "]") + if bracketEnd == -1 { + continue + } + + // 提取供应商ID + idStr := line[1:bracketEnd] + id, err := strconv.ParseInt(idStr, 10, 32) + if err != nil { + continue + } + currentResellerID = int32(id) + + // 提取供应商名称和商务名称 + remaining := strings.TrimSpace(line[bracketEnd+1:]) + + // 分割供应商名称和商务名称 + var resellerName, afterSaleName string + arrowIdx := strings.Index(remaining, "->") + if arrowIdx != -1 { + resellerName = strings.TrimSpace(remaining[:arrowIdx]) + afterSaleName = strings.TrimSpace(remaining[arrowIdx+2:]) + } else { + resellerName = remaining + afterSaleName = "" + } + + // 创建新的供应商对象 + currentReseller = &bbxt.ResellerLossSumProductRelation{ + AfterSaleName: afterSaleName, + ResellerName: resellerName, + Products: make(map[int32]*bbxt.LossReason), + } + + result[currentResellerID] = currentReseller + + } else if strings.Contains(line, "└──") { + // 解析商品行:└── [460]全国话费30元:未填写 + if currentReseller == nil { + continue + } + + // 移除└──前缀 + productLine := strings.TrimPrefix(line, "└──") + productLine = strings.TrimSpace(productLine) + + if !strings.HasPrefix(productLine, "[") { + continue + } + + // 找到商品ID的结束位置 + prodBracketEnd := strings.Index(productLine, "]") + if prodBracketEnd == -1 { + continue + } + + // 提取商品ID + prodIDStr := productLine[1:prodBracketEnd] + prodID, err := strconv.ParseInt(prodIDStr, 10, 32) + if err != nil { + continue + } + + // 提取商品名称和亏损原因 + prodRemaining := strings.TrimSpace(productLine[prodBracketEnd+1:]) + + // 找到冒号分隔符 + colonIdx := strings.Index(prodRemaining, ":") + var productName, lossReason string + + if colonIdx != -1 { + productName = strings.TrimSpace(prodRemaining[:colonIdx]) + lossReason = strings.TrimSpace(prodRemaining[colonIdx+1:]) + } else { + productName = prodRemaining + lossReason = "" + } + + // 添加到当前供应商的商品列表中 + currentReseller.Products[int32(prodID)] = &bbxt.LossReason{ + ProductName: productName, + LossReason: lossReason, + } + } + } + + return result, nil +} diff --git a/internal/biz/group_config.go b/internal/biz/group_config.go index 5d33e7e..b68a29d 100644 --- a/internal/biz/group_config.go +++ b/internal/biz/group_config.go @@ -17,7 +17,6 @@ import ( "ai_scheduler/internal/tools/bbxt" "ai_scheduler/utils" "context" - "database/sql" "encoding/json" "errors" "fmt" @@ -189,12 +188,12 @@ func (g *GroupConfigBiz) handleReport(ctx context.Context, rec *entitys.Recogniz if _err != nil { return _err } + rechargeReport, _err := g.rechargeDailyReport(ctx, t, product, nil) + if _err != nil { + return _err + } reports = append(reports, repo...) - // rechargeReport, _err := g.rechargeDailyReport(ctx, t, product, nil) - // if _err != nil || len(repo) == 0 { - // return _err - // } - // reports = append(reports, rechargeReport...) + reports = append(reports, rechargeReport...) case "report_daily_recharge": product := strings.Split(groupConfig.ProductName, ",") repo, _err := g.rechargeDailyReport(ctx, t, product, nil) @@ -224,7 +223,6 @@ func (g *GroupConfigBiz) handleReport(ctx context.Context, rec *entitys.Recogniz } entitys.ResText(rec.Ch, "", fmt.Sprintf("%s![图片](%s)", report.Title, report.Url)) - } return nil } @@ -421,26 +419,28 @@ func (g *GroupConfigBiz) GetReportCache(ctx context.Context, day time.Time, tota dayDate := day.Format(time.DateOnly) cond := builder.NewCond() - cond = cond.And(builder.Eq{"index": bbxt.IndexLossSumDetail}) - cond = cond.And(builder.Eq{"key": dayDate}) + cond = cond.And(builder.Eq{"cache_index": bbxt.IndexLossSumDetail}) + cond = cond.And(builder.Eq{"cache_key": dayDate}) var cache model.AiReportDailyCache err := g.reportDailyCacheImpl.GetOneBySearchToStrut(&cond, &cache) if err != nil { - if errors.Is(sql.ErrNoRows, err) { - ResellerProductRelation, err = bbxtObj.GetResellerLossMannagerAndLossReasonFromApi(ctx, totalDetail) - if err != nil { - return err - } - cache = model.AiReportDailyCache{ - Key: dayDate, - Index: bbxt.IndexLossSumDetail, - Value: pkg.JsonStringIgonErr(ResellerProductRelation), - } - _, err = g.reportDailyCacheImpl.Add(&cache) + return err + } + if cache.ID == 0 { + ResellerProductRelation, err = bbxtObj.GetResellerLossMannagerAndLossReasonFromApi(ctx, totalDetail) + if err != nil { + return err } + cache = model.AiReportDailyCache{ + CacheKey: dayDate, + CacheIndex: bbxt.IndexLossSumDetail, + Value: pkg.JsonStringIgonErr(ResellerProductRelation), + } + _, err = g.reportDailyCacheImpl.Add(&cache) } else { err = json.Unmarshal([]byte(cache.Value), &ResellerProductRelation) } + if err != nil { return err } diff --git a/internal/biz/handle/dingtalk/send_card.go b/internal/biz/handle/dingtalk/send_card.go index c2063da..d2e5cb7 100644 --- a/internal/biz/handle/dingtalk/send_card.go +++ b/internal/biz/handle/dingtalk/send_card.go @@ -20,7 +20,7 @@ import ( ) const DefaultInterval = 100 * time.Millisecond -const HeardBeatX = 50 +const HeardBeatX = 100 type SendCardClient struct { Auth *Auth diff --git a/internal/biz/provider_set.go b/internal/biz/provider_set.go index 1f8595f..6f95898 100644 --- a/internal/biz/provider_set.go +++ b/internal/biz/provider_set.go @@ -20,4 +20,5 @@ var ProviderSetBiz = wire.NewSet( NewDingTalkBotBiz, NewQywxAppBiz, NewGroupConfigBiz, + do.NewMacro, ) diff --git a/internal/config/config.go b/internal/config/config.go index b4f05d9..64857a9 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -27,7 +27,6 @@ type Config struct { LLM LLM `mapstructure:"llm"` Dingtalk DingtalkConfig `mapstructure:"dingtalk"` Qywx QywxConfig `mapstructure:"qywx"` - ZLTX ZLTX `mapstructure:"zltx"` } type ZLTX struct { @@ -196,7 +195,8 @@ type ToolsConfig struct { // Coze 快递查询工具 CozeExpress ToolConfig `mapstructure:"cozeExpress"` // Coze 公司查询工具 - CozeCompany ToolConfig `mapstructure:"cozeCompany"` + CozeCompany ToolConfig `mapstructure:"cozeCompany"` + ZltxResellerAuthProductToManagerAndDefaultLossReason ToolConfig `mapstructure:"zltxResellerAuthProductToManagerAndDefaultLossReason"` } // ToolConfig 单个工具配置 diff --git a/internal/data/model/ai_report_daily_cache.gen.go b/internal/data/model/ai_report_daily_cache.gen.go index 0fa1f57..bf7057c 100644 --- a/internal/data/model/ai_report_daily_cache.gen.go +++ b/internal/data/model/ai_report_daily_cache.gen.go @@ -8,10 +8,10 @@ const TableNameAiReportDailyCache = "ai_report_daily_cache" // AiReportDailyCache mapped from table type AiReportDailyCache struct { - ID int32 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"` - Key string `gorm:"column:key;not null;default:1;comment:索引方式,可以是任意类型" json:"key"` // 索引方式,可以是任意类型 - Value string `gorm:"column:value;comment:类型下所需路由以及参数" json:"value"` // 类型下所需路由以及参数 - Index string `gorm:"column:index;not null;comment:类型" json:"index"` // 类型 + ID int32 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"` + CacheKey string `gorm:"column:cache_key;not null;default:1;comment:索引方式,可以是任意类型" json:"cache_key"` // 索引方式,可以是任意类型 + Value string `gorm:"column:value;comment:类型下所需路由以及参数" json:"value"` // 类型下所需路由以及参数 + CacheIndex string `gorm:"column:cache_index;not null;comment:类型" json:"cache_index"` // 类型 } // TableName AiReportDailyCache's table name diff --git a/internal/services/dtalk_bot_test.go b/internal/services/dtalk_bot_test.go index cd4c245..37c206e 100644 --- a/internal/services/dtalk_bot_test.go +++ b/internal/services/dtalk_bot_test.go @@ -120,8 +120,8 @@ func run() { group := qywx.NewGroup(botGroupQywxImpl, qywxAuth) other := qywx.NewOther(qywxAuth) qywxAppBiz := biz.NewQywxAppBiz(configConfig, botGroupQywxImpl, group, other) - groupConfigBiz := biz.NewGroupConfigBiz(toolRegis, utils_ossClient, botGroupConfigImpl, registry, configConfig) - dingTalkBotBiz := biz.NewDingTalkBotBiz(doDo, handle, botConfigImpl, botGroupImpl, user, botChatHisImpl, manager, configConfig, sendCardClient, groupConfigBiz) + groupConfigBiz := biz.NewGroupConfigBiz(toolRegis, utils_ossClient, botGroupConfigImpl, registry, configConfig, impl.NewReportDailyCacheImpl(db), rdb) + dingTalkBotBiz := biz.NewDingTalkBotBiz(doDo, handle, botConfigImpl, botGroupImpl, user, botChatHisImpl, impl.NewReportDailyCacheImpl(db), manager, configConfig, sendCardClient, groupConfigBiz) // 初始化钉钉机器人服务 cronService = NewCronService(configConfig, dingTalkBotBiz, qywxAppBiz, groupConfigBiz) } diff --git a/internal/tools/bbxt/api.go b/internal/tools/bbxt/api.go index 4dd645b..2ef4640 100644 --- a/internal/tools/bbxt/api.go +++ b/internal/tools/bbxt/api.go @@ -195,48 +195,49 @@ type GetManagerAndDefaultLossReasonResponse struct { } type GetManagerAndDefaultLossReasonResponseList struct { - ResellerInfo *GetManagerAndDefaultLossReasonResponse_ResellerInfo `json:"GetManagerAndDefaultLossReasonResponse_ResellerInfo,omitempty"` - ProductList []*GetManagerAndDefaultLossReasonResponse_ProductList `json:"GetManagerAndDefaultLossReasonResponse_ProductList,omitempty"` + ResellerInfo *GetManagerAndDefaultLossReasonResponse_ResellerInfo `json:"resellerInfo,omitempty"` + ProductList []*GetManagerAndDefaultLossReasonResponse_ProductList `json:"productList,omitempty"` } type GetManagerAndDefaultLossReasonResponse_ResellerInfo struct { - ResellerId int32 `json:"reseller_id,omitempty"` - AfterSaleName string `json:"after_sale_name,omitempty"` + ResellerId int32 `json:"resellerId,omitempty"` + AfterSaleName string `json:"AfterSaleName,omitempty"` } type GetManagerAndDefaultLossReasonResponse_ProductList struct { - ProductId int32 `json:"product_id,omitempty"` - LossReason string `json:"loss_reason,omitempty"` + ProductId int32 `json:"productId,omitempty"` + LossReason string `json:"lossReason,omitempty"` } +//return []*GetManagerAndDefaultLossReasonResponseList{ +//{ +//ResellerInfo: &GetManagerAndDefaultLossReasonResponse_ResellerInfo{ +//ResellerId: 25009, +//AfterSaleName: "张三", +//}, +//ProductList: []*GetManagerAndDefaultLossReasonResponse_ProductList{ +//{ +//ProductId: 129, +//LossReason: "小米钱包h5-QQ音乐绿钻季卡原因", +//}, +//{ +//ProductId: 2218, +//LossReason: "小米钱包h5-百度网盘新vip会员月卡原因", +//}, +//{ +//ProductId: 3226, +//LossReason: "小米钱包h5-腾讯视频月卡-小米钱包2024原因", +//}, +//{ +//ProductId: 3364, +//LossReason: "小米钱包h5-腾讯视频月卡-0元直充原因", +//}, +//}, +//}, +//}, nil + // GetStatisFilterOfficialProductApi 官方商品列表 func GetManagerAndDefaultLossReasonApi(param *GetManagerAndDefaultLossReasonRequest, token string, reqUrl string) ([]*GetManagerAndDefaultLossReasonResponseList, error) { - return []*GetManagerAndDefaultLossReasonResponseList{ - { - ResellerInfo: &GetManagerAndDefaultLossReasonResponse_ResellerInfo{ - ResellerId: 25009, - AfterSaleName: "张三", - }, - ProductList: []*GetManagerAndDefaultLossReasonResponse_ProductList{ - { - ProductId: 129, - LossReason: "小米钱包h5-QQ音乐绿钻季卡原因", - }, - { - ProductId: 2218, - LossReason: "小米钱包h5-百度网盘新vip会员月卡原因", - }, - { - ProductId: 3226, - LossReason: "小米钱包h5-腾讯视频月卡-小米钱包2024原因", - }, - { - ProductId: 3364, - LossReason: "小米钱包h5-腾讯视频月卡-0元直充原因", - }, - }, - }, - }, nil reqParam, err := util.StructToMap(param) if err != nil { @@ -244,7 +245,7 @@ func GetManagerAndDefaultLossReasonApi(param *GetManagerAndDefaultLossReasonRequ } req := &l_request.Request{ - Url: reqUrl + "/admin/reseller/resellerAuthProduct/getManagerAndDefaultLossReason", + Url: reqUrl, Method: http.MethodPost, Json: reqParam, Headers: map[string]string{ diff --git a/internal/tools/bbxt/bbxt.go b/internal/tools/bbxt/bbxt.go index 56b87d3..de1e638 100644 --- a/internal/tools/bbxt/bbxt.go +++ b/internal/tools/bbxt/bbxt.go @@ -218,24 +218,24 @@ func (b *BbxtTools) StatisOursProductLossSum(ctx context.Context, now time.Time, } } - //if len(totalDetail) > 0 { - // err = initFunc(ctx, now, totalDetail, b) - // if err != nil { - // return - // } - // filePath := b.cacheDir + "/kshj_total_ana" + fmt.Sprintf("%d%d", time.Now().Unix(), rand.Intn(1000)) + ".xlsx" - // title := "截至" + timeCh + "亏损100以上的分销商&产品负利润原因" - // err = b.resellerDetailFillExcelAna(b.excelTempDir+"/"+"kshj_total_ana.xlsx", filePath, totalDetail, title) - // if err != nil { - // return - // } - // report[2] = &ReportRes{ - // ReportName: "负利润分析(亏损100以上)", - // Title: "截至" + timeCh + "亏损100以上利润原因", - // Path: filePath, - // Data: total, - // } - //} + if len(totalDetail) > 0 { + err = initFunc(ctx, now, totalDetail, b) + if err != nil { + return + } + filePath := b.cacheDir + "/kshj_total_ana" + fmt.Sprintf("%d%d", time.Now().Unix(), rand.Intn(1000)) + ".xlsx" + title := "截至" + timeCh + "亏损100以上的分销商&产品负利润原因" + err = b.resellerDetailFillExcelAna(b.excelTempDir+"/"+"kshj_total_ana.xlsx", filePath, totalDetail, title) + if err != nil { + return + } + report[2] = &ReportRes{ + ReportName: "负利润分析(亏损100以上)", + Title: "截至" + timeCh + "亏损100以上利润原因", + Path: filePath, + Data: total, + } + } return report, nil } @@ -269,7 +269,7 @@ func (b *BbxtTools) GetResellerLossMannagerAndLossReasonFromApi(ctx context.Cont } res, err := GetManagerAndDefaultLossReasonApi(&GetManagerAndDefaultLossReasonRequest{ Param: resellerMap, - }, b.login.GetToken(ctx), b.config.ZLTX.ReqUrl) + }, b.login.GetToken(ctx), b.config.Tools.ZltxResellerAuthProductToManagerAndDefaultLossReason.BaseURL) if err != nil { return nil, err } @@ -283,6 +283,12 @@ func (b *BbxtTools) GetResellerLossMannagerAndLossReasonFromApi(ctx context.Cont relationMap[v.ResellerInfo.ResellerId].AfterSaleName = v.ResellerInfo.AfterSaleName for _, vv := range v.ProductList { + if _, ex := relationMap[v.ResellerInfo.ResellerId].Products[vv.ProductId]; !ex { + continue + } + if len(vv.LossReason) == 0 { + continue + } relationMap[v.ResellerInfo.ResellerId].Products[vv.ProductId].LossReason = vv.LossReason } } diff --git a/internal/tools/bbxt/bbxt_test.go b/internal/tools/bbxt/bbxt_test.go index 1ea8f43..c586eda 100644 --- a/internal/tools/bbxt/bbxt_test.go +++ b/internal/tools/bbxt/bbxt_test.go @@ -110,8 +110,8 @@ func GetReportCache(ctx context.Context, day time.Time, totalDetail []*ResellerL var ResellerProductRelation map[int32]*ResellerLossSumProductRelation dayDate := day.Format(time.DateOnly) cond := builder.NewCond() - cond = cond.And(builder.Eq{"`index`": IndexLossSumDetail}) - cond = cond.And(builder.Eq{"`key`": dayDate}) + cond = cond.And(builder.Eq{"cache_index": IndexLossSumDetail}) + cond = cond.And(builder.Eq{"cache_key": dayDate}) var cache model.AiReportDailyCache err := reportDailyCacheImpl.GetOneBySearchToStrut(&cond, &cache) @@ -124,9 +124,9 @@ func GetReportCache(ctx context.Context, day time.Time, totalDetail []*ResellerL return err } cache = model.AiReportDailyCache{ - Key: dayDate, - Index: IndexLossSumDetail, - Value: pkg.JsonStringIgonErr(ResellerProductRelation), + CacheKey: dayDate, + CacheIndex: IndexLossSumDetail, + Value: pkg.JsonStringIgonErr(ResellerProductRelation), } _, err = reportDailyCacheImpl.Add(&cache) if err != nil { diff --git a/tmpl/excel_temp/kshj_gt.xlsx b/tmpl/excel_temp/kshj_gt.xlsx index 459460b..ef1033d 100644 Binary files a/tmpl/excel_temp/kshj_gt.xlsx and b/tmpl/excel_temp/kshj_gt.xlsx differ diff --git a/tmpl/excel_temp/kshj_total_ana.xlsx b/tmpl/excel_temp/kshj_total_ana.xlsx index d2774f3..c4824cd 100644 Binary files a/tmpl/excel_temp/kshj_total_ana.xlsx and b/tmpl/excel_temp/kshj_total_ana.xlsx differ