Compare commits
14 Commits
ebc3f20e04
...
d17e488dd7
| Author | SHA1 | Date |
|---|---|---|
|
|
d17e488dd7 | |
|
|
2a567dde86 | |
|
|
f71018dd96 | |
|
|
46ceb77b79 | |
|
|
1f67330274 | |
|
|
9663fa07da | |
|
|
16c113398a | |
|
|
e695029238 | |
|
|
57692a8da3 | |
|
|
a72ec8cfc2 | |
|
|
e5973c3a3f | |
|
|
52ced45543 | |
|
|
3cac52dba2 | |
|
|
bb33489c45 |
|
|
@ -22,12 +22,6 @@ vllm:
|
||||||
coze:
|
coze:
|
||||||
base_url: "https://api.coze.cn"
|
base_url: "https://api.coze.cn"
|
||||||
|
|
||||||
redis:
|
|
||||||
host: 47.97.27.195:6379
|
|
||||||
type: node
|
|
||||||
pass:
|
|
||||||
lansexiongdi@666i_secret: "sat_AqvFcdNgesP8megy1ItTscWFXRcsHRzmM4NJ1KNavfcdT0EPwYuCPkDqGhItpx13"
|
|
||||||
|
|
||||||
lsxd:
|
lsxd:
|
||||||
# 统一登录
|
# 统一登录
|
||||||
login_url: "https://api.user.1688sup.com/v1/login/phone"
|
login_url: "https://api.user.1688sup.com/v1/login/phone"
|
||||||
|
|
@ -48,6 +42,16 @@ sys:
|
||||||
maxIdleTime: 30 #每个连接最大空闲时间,如果超过了这个时间会被关闭
|
maxIdleTime: 30 #每个连接最大空闲时间,如果超过了这个时间会被关闭
|
||||||
tls: 30
|
tls: 30
|
||||||
db:
|
db:
|
||||||
|
redis:
|
||||||
|
host: 47.97.27.195:6379
|
||||||
|
type: node
|
||||||
|
pass: lansexiongdi@666
|
||||||
|
key: report-api-test
|
||||||
|
pollSize: 5 #连接池大小,不配置,或配置为0表示不启用连接池
|
||||||
|
minIdleConns: 2 #最小空闲连接数
|
||||||
|
maxIdleTime: 30 #每个连接最大空闲时间,如果超过了这个时间会被关闭
|
||||||
|
tls: 30
|
||||||
|
db:
|
||||||
db:
|
db:
|
||||||
driver: mysql
|
driver: mysql
|
||||||
source: root:SD###sdf323r343@tcp(121.199.38.107:3306)/sys_ai?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
|
source: root:SD###sdf323r343@tcp(121.199.38.107:3306)/sys_ai?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
|
||||||
|
|
@ -150,14 +154,14 @@ dingtalk:
|
||||||
sheet_id_or_name: "数据表"
|
sheet_id_or_name: "数据表"
|
||||||
# 机器人群组
|
# 机器人群组
|
||||||
bot_group_id:
|
bot_group_id:
|
||||||
bbxt: 28
|
bbxt: 29
|
||||||
|
|
||||||
qywx:
|
qywx:
|
||||||
corp_id: "ww48151f694fb8ec67"
|
corp_id: "ww48151f694fb8ec67"
|
||||||
app_secret: "uYqtdwdtdH4Uv_P4is2AChuGzBCoB6cQDyRvpbW0Vmk"
|
app_secret: "uYqtdwdtdH4Uv_P4is2AChuGzBCoB6cQDyRvpbW0Vmk"
|
||||||
token: "Jdukry6"
|
token: "zJdukry6"
|
||||||
aes_key: "4VLH47qRGUogc2d3QLWuUhvJlk8Y0YuRjXzeBquBq8B"
|
aes_key: "4VLH47qRGUogc2d3QLWuUhvJlk8Y0YuRjXzeBquBq8B"
|
||||||
init_account: "les."
|
init_account: "les.,FuZhongYun"
|
||||||
chat_id_len: 16
|
chat_id_len: 16
|
||||||
default_config_id: 1
|
default_config_id: 1
|
||||||
bot_group_id:
|
bot_group_id:
|
||||||
|
|
|
||||||
|
|
@ -152,20 +152,15 @@ dingtalk:
|
||||||
bbxt: 23
|
bbxt: 23
|
||||||
|
|
||||||
qywx:
|
qywx:
|
||||||
# corp_id: "ww48151f694fb8ec67"
|
corp_id: "ww48151f694fb8ec67"
|
||||||
# app_secret: "uYqtdwdtdH4Uv_P4is2AChuGzBCoB6cQDyRvpbW0Vmk"
|
|
||||||
# token: "uYqtdwdtdH4Uv_P4is2AChuGzBCoB6cQDyRvpbW0Vmk"
|
|
||||||
# aes_key: "4VLH47qRGUogc2d3QLWuUhvJlk8Y0YuRjXzeBquBq8B"
|
|
||||||
|
|
||||||
corp_id: "wwabfd0cec7171e769"
|
|
||||||
app_secret: "uYqtdwdtdH4Uv_P4is2AChuGzBCoB6cQDyRvpbW0Vmk"
|
app_secret: "uYqtdwdtdH4Uv_P4is2AChuGzBCoB6cQDyRvpbW0Vmk"
|
||||||
token: "gY1AGR3mjBhzy"
|
token: "zJdukry6"
|
||||||
aes_key: "g8VGfQEqluUhoKOlyjmmll8Q9C5tVFUTX5T2qkmI9Sv"
|
aes_key: "4VLH47qRGUogc2d3QLWuUhvJlk8Y0YuRjXzeBquBq8B"
|
||||||
init_account: "les."
|
init_account: "les.,FuZhongYun"
|
||||||
chat_id_len: 16
|
chat_id_len: 16
|
||||||
default_config_id: 1
|
default_config_id: 1
|
||||||
bot_group_id:
|
bot_group_id:
|
||||||
bbxt: 23
|
bbxt: 35
|
||||||
|
|
||||||
default_prompt:
|
default_prompt:
|
||||||
img_recognize:
|
img_recognize:
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ func (g *GroupConfigBiz) GetReportLists(ctx context.Context, groupConfig *model.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//product = []string{"优酷周卡", "优酷季卡", "优酷年卡", "爱奇艺黄金会员天卡"}
|
|
||||||
//追加电商充值系统统计 - 返回统一使用[]*bbxt.ReportRes
|
//追加电商充值系统统计 - 返回统一使用[]*bbxt.ReportRes
|
||||||
rechargeReports, err := g.rechargeDailyReport(ctx, time.Now(), nil, g.ossClient)
|
rechargeReports, err := g.rechargeDailyReport(ctx, time.Now(), nil, g.ossClient)
|
||||||
if err != nil || len(rechargeReports) == 0 {
|
if err != nil || len(rechargeReports) == 0 {
|
||||||
|
|
@ -118,11 +118,10 @@ func (g *GroupConfigBiz) rechargeDailyReport(ctx context.Context, now time.Time,
|
||||||
reports = []*bbxt.ReportRes{
|
reports = []*bbxt.ReportRes{
|
||||||
{
|
{
|
||||||
ReportName: "我们的商品统计(电商充值系统)",
|
ReportName: "我们的商品统计(电商充值系统)",
|
||||||
Title: fmt.Sprintf("%s 电商充值系统我们的商品统计", now.Format("2006-01-02")),
|
Title: res["title"].(string),
|
||||||
Path: res["path"].(string),
|
Path: res["path"].(string),
|
||||||
Url: res["url"].(string),
|
Url: res["url"].(string),
|
||||||
Data: res["data"].([][]string),
|
Data: res["data"].([][]string),
|
||||||
Desc: res["desc"].(string),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,6 +179,11 @@ func (g *GroupConfigBiz) handleReport(ctx context.Context, rec *entitys.Recogniz
|
||||||
return _err
|
return _err
|
||||||
}
|
}
|
||||||
reports = append(reports, repo...)
|
reports = append(reports, repo...)
|
||||||
|
rechargeReport, _err := g.rechargeDailyReport(ctx, t, product, nil)
|
||||||
|
if _err != nil || len(repo) == 0 {
|
||||||
|
return _err
|
||||||
|
}
|
||||||
|
reports = append(reports, rechargeReport...)
|
||||||
case "report_daily_recharge":
|
case "report_daily_recharge":
|
||||||
product := strings.Split(groupConfig.ProductName, ",")
|
product := strings.Split(groupConfig.ProductName, ",")
|
||||||
repo, _err := g.rechargeDailyReport(ctx, t, product, nil)
|
repo, _err := g.rechargeDailyReport(ctx, t, product, nil)
|
||||||
|
|
@ -199,6 +203,9 @@ func (g *GroupConfigBiz) handleReport(ctx context.Context, rec *entitys.Recogniz
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, report := range reports {
|
for _, report := range reports {
|
||||||
|
if report == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
err = uploader.Run(report)
|
err = uploader.Run(report)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
|
|
|
||||||
|
|
@ -80,3 +80,11 @@ func (a *Auth) getNewAccessToken(ctx context.Context, corpid string, corpsecret
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UploadMediaRes struct {
|
||||||
|
Errcode int `json:"errcode"`
|
||||||
|
Errmsg string `json:"errmsg"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
MediaId string `json:"media_id"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,19 +33,17 @@ func NewGroup(groupImpl *impl.BotGroupQywxImpl, auth *Auth) *Group {
|
||||||
// - GroupCreateResp: 创建群聊的响应结果
|
// - GroupCreateResp: 创建群聊的响应结果
|
||||||
// - error: 错误信息,如果请求失败则返回错误
|
// - error: 错误信息,如果请求失败则返回错误
|
||||||
func (g *Group) Create(ctx context.Context, req GroupCreateReq, corpid string, corpsecret string) (GroupCreateResp, error) {
|
func (g *Group) Create(ctx context.Context, req GroupCreateReq, corpid string, corpsecret string) (GroupCreateResp, error) {
|
||||||
// 声明一个GroupCreateResp结构体变量res,用于存储响应结果
|
|
||||||
var res GroupCreateResp
|
var res GroupCreateResp
|
||||||
// 将请求结构体req转换为map类型的参数param
|
|
||||||
// 如果转换失败,忽略错误
|
|
||||||
param, _ := util.StructToMap(req)
|
param, _ := util.StructToMap(req)
|
||||||
// 发送HTTP请求到企业微信API创建群聊
|
|
||||||
// 参数依次为:上下文、请求参数、请求URL、响应结果存储指针、企业ID、应用密钥
|
|
||||||
_, err := g.request(ctx, param, "https://qyapi.weixin.qq.com/cgi-bin/appchat/create", &res, corpid, corpsecret)
|
_, err := g.request(ctx, param, "https://qyapi.weixin.qq.com/cgi-bin/appchat/create", &res, corpid, corpsecret)
|
||||||
// 如果请求过程中发生错误,返回响应结果和错误
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
// 请求成功,返回响应结果和nil错误
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,18 +58,76 @@ func (g *Group) Create(ctx context.Context, req GroupCreateReq, corpid string, c
|
||||||
// - error: 操作过程中发生的错误,如果成功则为nil
|
// - error: 操作过程中发生的错误,如果成功则为nil
|
||||||
func (g *Group) SendMarkDown(ctx context.Context, req GroupSendMarkDownReq, corpid string, corpsecret string) error {
|
func (g *Group) SendMarkDown(ctx context.Context, req GroupSendMarkDownReq, corpid string, corpsecret string) error {
|
||||||
|
|
||||||
// 设置消息类型为Markdown
|
req.Msgtype = "markdown_v2"
|
||||||
req.Msgtype = "markdown"
|
|
||||||
// 将请求结构体转换为map类型,便于后续的HTTP请求参数处理
|
|
||||||
param, _ := util.StructToMap(req)
|
param, _ := util.StructToMap(req)
|
||||||
// 调用request方法发送HTTP请求到企业微信API
|
|
||||||
// 参数依次为:上下文、请求参数、API URL、额外请求头、corpid、corpsecret
|
|
||||||
_, err := g.request(ctx, param, "https://qyapi.weixin.qq.com/cgi-bin/appchat/send", nil, corpid, corpsecret)
|
_, err := g.request(ctx, param, "https://qyapi.weixin.qq.com/cgi-bin/appchat/send", nil, corpid, corpsecret)
|
||||||
// 如果请求过程中发生错误,直接返回错误
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// 请求成功,返回nil
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) SendNews(ctx context.Context, req GroupSendNewsReq, corpid string, corpsecret string) error {
|
||||||
|
|
||||||
|
req.Msgtype = "news"
|
||||||
|
|
||||||
|
param, _ := util.StructToMap(req)
|
||||||
|
|
||||||
|
_, err := g.request(ctx, param, "https://qyapi.weixin.qq.com/cgi-bin/appchat/send", nil, corpid, corpsecret)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) SendImg(ctx context.Context, req GroupSendImgReq, corpid string, corpsecret string) error {
|
||||||
|
|
||||||
|
req.Msgtype = "image"
|
||||||
|
|
||||||
|
param, _ := util.StructToMap(req)
|
||||||
|
|
||||||
|
_, err := g.request(ctx, param, "https://qyapi.weixin.qq.com/cgi-bin/appchat/send", nil, corpid, corpsecret)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) SendMpNews(ctx context.Context, req GroupSendMpNewsReq, corpid string, corpsecret string) error {
|
||||||
|
|
||||||
|
req.Msgtype = "mpnews"
|
||||||
|
|
||||||
|
param, _ := util.StructToMap(req)
|
||||||
|
|
||||||
|
_, err := g.request(ctx, param, "https://qyapi.weixin.qq.com/cgi-bin/appchat/send", nil, corpid, corpsecret)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) SendText(ctx context.Context, req GroupSendTextReq, corpid string, corpsecret string) error {
|
||||||
|
|
||||||
|
req.Msgtype = "text"
|
||||||
|
|
||||||
|
param, _ := util.StructToMap(req)
|
||||||
|
|
||||||
|
_, err := g.request(ctx, param, "https://qyapi.weixin.qq.com/cgi-bin/appchat/send", nil, corpid, corpsecret)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
package qywx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Other struct {
|
||||||
|
auth *Auth
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOther(auth *Auth) *Other {
|
||||||
|
return &Other{
|
||||||
|
|
||||||
|
auth: auth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Other) UploadMediaWithUrl(ctx context.Context, fileUrl, mediaType, corpid, corpsecret string) (uploadRes *UploadMediaRes, err error) {
|
||||||
|
// 1. 获取AccessToken
|
||||||
|
auth, err := g.auth.GetAccessToken(ctx, corpid, corpsecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取AccessToken失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 下载文件
|
||||||
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
resp, err := client.Get(fileUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("下载文件失败: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// 检查响应状态
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("下载文件状态码错误: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 准备上传请求(修正点:使用正确的上传API)
|
||||||
|
uploadUrl := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s",
|
||||||
|
auth.AccessToken, mediaType)
|
||||||
|
|
||||||
|
// 4. 创建multipart表单
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
|
||||||
|
// 从URL提取文件名(或使用默认名)
|
||||||
|
filename := path.Base(fileUrl)
|
||||||
|
if filename == "/" || filename == "." {
|
||||||
|
filename = fmt.Sprintf("file_%d.dat", time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
part, err := writer.CreateFormFile("media", filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("创建表单文件失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 流式传输文件内容
|
||||||
|
if _, err = io.Copy(part, resp.Body); err != nil {
|
||||||
|
return nil, fmt.Errorf("写入文件内容失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 关闭writer
|
||||||
|
if err = writer.Close(); err != nil {
|
||||||
|
return nil, fmt.Errorf("关闭writer失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 发送上传请求
|
||||||
|
req, err := http.NewRequest("POST", uploadUrl, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("创建请求失败: %v", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||||
|
|
||||||
|
uploadResp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("上传请求失败: %v", err)
|
||||||
|
}
|
||||||
|
defer uploadResp.Body.Close()
|
||||||
|
|
||||||
|
// 8. 解析响应
|
||||||
|
respBody, err := io.ReadAll(uploadResp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("读取响应失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.Unmarshal(respBody, &uploadRes); err != nil {
|
||||||
|
return nil, fmt.Errorf("解析响应失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if uploadRes.Errcode != 0 {
|
||||||
|
return nil, fmt.Errorf("上传失败 [code=%d]: %s", uploadRes.Errcode, uploadRes.Errmsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -37,9 +37,66 @@ type commonResp struct {
|
||||||
type GroupSendMarkDownReq struct {
|
type GroupSendMarkDownReq struct {
|
||||||
Chatid string `json:"chatid"`
|
Chatid string `json:"chatid"`
|
||||||
Msgtype string `json:"msgtype"`
|
Msgtype string `json:"msgtype"`
|
||||||
Markdown MarkDown `json:"markdown"`
|
Markdown MarkDown `json:"markdown_v2"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MarkDown struct {
|
type MarkDown struct {
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GroupSendNewsReq struct {
|
||||||
|
Chatid string `json:"chatid"`
|
||||||
|
Msgtype string `json:"msgtype"`
|
||||||
|
News News `json:"news"`
|
||||||
|
Safe int `json:"safe"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type News struct {
|
||||||
|
Articles []Articles `json:"articles"`
|
||||||
|
}
|
||||||
|
type Articles struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
Picurl string `json:"picurl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GroupSendImgReq struct {
|
||||||
|
Chatid string `json:"chatid"`
|
||||||
|
Msgtype string `json:"msgtype"`
|
||||||
|
Image Image `json:"image"`
|
||||||
|
Safe int `json:"safe"`
|
||||||
|
}
|
||||||
|
type Image struct {
|
||||||
|
MediaId string `json:"media_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GroupSendMpNewsReq struct {
|
||||||
|
Chatid string `json:"chatid"`
|
||||||
|
Msgtype string `json:"msgtype"`
|
||||||
|
Mpnews Mpnews `json:"mpnews"`
|
||||||
|
Safe int `json:"safe"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mpnews struct {
|
||||||
|
Articles []ArticlesMpnews `json:"articles"`
|
||||||
|
}
|
||||||
|
type ArticlesMpnews struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
ThumbMediaId string `json:"thumb_media_id"`
|
||||||
|
Author string `json:"author"`
|
||||||
|
ContentSourceUrl string `json:"content_source_url"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Digest string `json:"digest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GroupSendTextReq struct {
|
||||||
|
Chatid string `json:"chatid"`
|
||||||
|
Msgtype string `json:"msgtype"`
|
||||||
|
Text Text `json:"text"`
|
||||||
|
Safe int `json:"safe"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Text struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"ai_scheduler/internal/pkg"
|
"ai_scheduler/internal/pkg"
|
||||||
"ai_scheduler/internal/tools/bbxt"
|
"ai_scheduler/internal/tools/bbxt"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"ai_scheduler/internal/config"
|
"ai_scheduler/internal/config"
|
||||||
|
|
@ -21,6 +21,7 @@ type QywxAppBiz struct {
|
||||||
conf *config.Config
|
conf *config.Config
|
||||||
botGroupQywxImpl *impl.BotGroupQywxImpl
|
botGroupQywxImpl *impl.BotGroupQywxImpl
|
||||||
qywxGroupHandle *qywx.Group
|
qywxGroupHandle *qywx.Group
|
||||||
|
qywxOtherHandle *qywx.Other
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDingTalkBotBiz
|
// NewDingTalkBotBiz
|
||||||
|
|
@ -28,11 +29,13 @@ func NewQywxAppBiz(
|
||||||
conf *config.Config,
|
conf *config.Config,
|
||||||
botGroupQywxImpl *impl.BotGroupQywxImpl,
|
botGroupQywxImpl *impl.BotGroupQywxImpl,
|
||||||
qywxGroupHandle *qywx.Group,
|
qywxGroupHandle *qywx.Group,
|
||||||
|
qywxOtherHandle *qywx.Other,
|
||||||
) *QywxAppBiz {
|
) *QywxAppBiz {
|
||||||
return &QywxAppBiz{
|
return &QywxAppBiz{
|
||||||
conf: conf,
|
conf: conf,
|
||||||
botGroupQywxImpl: botGroupQywxImpl,
|
botGroupQywxImpl: botGroupQywxImpl,
|
||||||
qywxGroupHandle: qywxGroupHandle,
|
qywxGroupHandle: qywxGroupHandle,
|
||||||
|
qywxOtherHandle: qywxOtherHandle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,9 +56,7 @@ func (q *QywxAppBiz) InitGroup(ctx context.Context) (string, error) {
|
||||||
qywx.GroupCreateReq{
|
qywx.GroupCreateReq{
|
||||||
Name: GroupInfo.Title,
|
Name: GroupInfo.Title,
|
||||||
Chatid: GroupInfo.ChatID,
|
Chatid: GroupInfo.ChatID,
|
||||||
Userlist: []string{
|
Userlist: strings.Split(q.conf.Qywx.InitAccount, ","),
|
||||||
q.conf.Qywx.InitAccount,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
q.conf.Qywx.CorpId,
|
q.conf.Qywx.CorpId,
|
||||||
GroupInfo.AppSecret,
|
GroupInfo.AppSecret,
|
||||||
|
|
@ -77,12 +78,30 @@ func (q *QywxAppBiz) GetGroupInfo(ctx context.Context, groupId int) (group model
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *QywxAppBiz) SendReport(ctx context.Context, groupInfo *model.AiBotGroupQywx, report *bbxt.ReportRes) (err error) {
|
func (q *QywxAppBiz) SendReport(ctx context.Context, groupInfo *model.AiBotGroupQywx, report *bbxt.ReportRes) (err error) {
|
||||||
confitent := fmt.Sprintf("%s\n%s", report.Title, fmt.Sprintf("", report.Url))
|
//confitent := fmt.Sprintf("%s\n%s", report.Title, fmt.Sprintf("", report.Url))
|
||||||
err = q.qywxGroupHandle.SendMarkDown(ctx, qywx.GroupSendMarkDownReq{
|
|
||||||
Chatid: groupInfo.ChatID,
|
upload, err := q.qywxOtherHandle.UploadMediaWithUrl(ctx, report.Url, "image", q.conf.Qywx.CorpId, groupInfo.AppSecret)
|
||||||
Markdown: qywx.MarkDown{
|
if err != nil {
|
||||||
Content: confitent,
|
return
|
||||||
},
|
}
|
||||||
}, q.conf.Qywx.CorpId, groupInfo.AppSecret)
|
|
||||||
|
err = q.qywxGroupHandle.SendText(ctx, qywx.GroupSendTextReq{
|
||||||
|
Chatid: groupInfo.ChatID,
|
||||||
|
Text: qywx.Text{
|
||||||
|
Content: report.ReportName + "\n" + report.Title,
|
||||||
|
},
|
||||||
|
}, q.conf.Qywx.CorpId, groupInfo.AppSecret)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = q.qywxGroupHandle.SendImg(ctx, qywx.GroupSendImgReq{
|
||||||
|
Chatid: groupInfo.ChatID,
|
||||||
|
Image: qywx.Image{
|
||||||
|
MediaId: upload.MediaId,
|
||||||
|
},
|
||||||
|
}, q.conf.Qywx.CorpId, groupInfo.AppSecret)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -91,6 +92,7 @@ type StatisticsOursProductContext struct {
|
||||||
ProductData []statistics_ours_product.StatisticsOursProductItem
|
ProductData []statistics_ours_product.StatisticsOursProductItem
|
||||||
ImgUrl string
|
ImgUrl string
|
||||||
ExcelData [][]string
|
ExcelData [][]string
|
||||||
|
TotalLoss float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *statisticsOursProduct) buildWorkflow(ctx context.Context) (compose.Runnable[*StatisticsOursProductWorkflowInput, map[string]any], error) {
|
func (w *statisticsOursProduct) buildWorkflow(ctx context.Context) (compose.Runnable[*StatisticsOursProductWorkflowInput, map[string]any], error) {
|
||||||
|
|
@ -122,7 +124,7 @@ func (w *statisticsOursProduct) formatContext(ctx context.Context, input *Statis
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
StartTime: startTime,
|
StartTime: startTime,
|
||||||
EndTime: endTime,
|
EndTime: endTime,
|
||||||
Title: fmt.Sprintf("截止 %s 我们的商品统计", endTimeStr),
|
Title: fmt.Sprintf("截止 %s 亏损100以上我们的商品统计", endTimeStr),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -162,7 +164,7 @@ func (w *statisticsOursProduct) generateExcelAndUpload(ctx context.Context, stat
|
||||||
fileName := fmt.Sprintf("statistics_ours_product_%d%d", time.Now().Unix(), rand.Intn(1000))
|
fileName := fmt.Sprintf("statistics_ours_product_%d%d", time.Now().Unix(), rand.Intn(1000))
|
||||||
|
|
||||||
// 2. 转换数据为 [][]string
|
// 2. 转换数据为 [][]string
|
||||||
excelData := w.convertDataToExcelFormat(state.ProductData)
|
excelData, totalLoss := w.convertDataToExcelFormat(state.ProductData)
|
||||||
|
|
||||||
// 3. 生成 Excel
|
// 3. 生成 Excel
|
||||||
req := &excel_generator.ExcelGeneratorRequest{
|
req := &excel_generator.ExcelGeneratorRequest{
|
||||||
|
|
@ -191,13 +193,20 @@ func (w *statisticsOursProduct) generateExcelAndUpload(ctx context.Context, stat
|
||||||
|
|
||||||
state.ImgUrl = url
|
state.ImgUrl = url
|
||||||
state.ExcelData = excelData
|
state.ExcelData = excelData
|
||||||
|
state.TotalLoss = totalLoss
|
||||||
|
|
||||||
return state, nil
|
return state, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// convertDataToExcelFormat 将业务数据转换为 Excel 生成器需要的二维字符串数组
|
// convertDataToExcelFormat 将业务数据转换为 Excel 生成器需要的二维字符串数组
|
||||||
func (w *statisticsOursProduct) convertDataToExcelFormat(data []statistics_ours_product.StatisticsOursProductItem) [][]string {
|
func (w *statisticsOursProduct) convertDataToExcelFormat(data []statistics_ours_product.StatisticsOursProductItem) ([][]string, float64) {
|
||||||
var result [][]string
|
type sortType struct {
|
||||||
|
Profit float64
|
||||||
|
cells []string
|
||||||
|
}
|
||||||
|
|
||||||
|
var sortList []sortType
|
||||||
|
var totalLoss float64
|
||||||
for _, item := range data {
|
for _, item := range data {
|
||||||
|
|
||||||
var profitVal float64
|
var profitVal float64
|
||||||
|
|
@ -219,6 +228,11 @@ func (w *statisticsOursProduct) convertDataToExcelFormat(data []statistics_ours_
|
||||||
profitVal = 0
|
profitVal = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 累加总亏损
|
||||||
|
if profitVal < 0 {
|
||||||
|
totalLoss += profitVal
|
||||||
|
}
|
||||||
|
|
||||||
// 过滤利润小于 -100 的记录
|
// 过滤利润小于 -100 的记录
|
||||||
if profitVal > -100 {
|
if profitVal > -100 {
|
||||||
continue
|
continue
|
||||||
|
|
@ -236,9 +250,24 @@ func (w *statisticsOursProduct) convertDataToExcelFormat(data []statistics_ours_
|
||||||
fmt.Sprintf("%v", item.Profit),
|
fmt.Sprintf("%v", item.Profit),
|
||||||
}
|
}
|
||||||
|
|
||||||
result = append(result, row)
|
sortList = append(sortList, sortType{
|
||||||
|
Profit: profitVal,
|
||||||
|
cells: row,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return result
|
|
||||||
|
// 排序
|
||||||
|
sort.Slice(sortList, func(i, j int) bool {
|
||||||
|
return sortList[i].Profit < sortList[j].Profit
|
||||||
|
})
|
||||||
|
|
||||||
|
// 转换为 [][]string
|
||||||
|
result := make([][]string, 0, len(sortList))
|
||||||
|
for _, item := range sortList {
|
||||||
|
result = append(result, item.cells)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, totalLoss
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *statisticsOursProduct) convertToMap(ctx context.Context, state *StatisticsOursProductContext) (map[string]any, error) {
|
func (w *statisticsOursProduct) convertToMap(ctx context.Context, state *StatisticsOursProductContext) (map[string]any, error) {
|
||||||
|
|
@ -246,6 +275,6 @@ func (w *statisticsOursProduct) convertToMap(ctx context.Context, state *Statist
|
||||||
"path": "",
|
"path": "",
|
||||||
"url": state.ImgUrl,
|
"url": state.ImgUrl,
|
||||||
"data": state.ExcelData,
|
"data": state.ExcelData,
|
||||||
"desc": state.Title,
|
"title": state.Title + fmt.Sprintf("(总亏损 %.2f)", state.TotalLoss),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-kratos/kratos/v2/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 请求结构体
|
// 请求结构体
|
||||||
|
|
@ -108,7 +110,11 @@ func (r *Request) prepare() *http.Request {
|
||||||
Method := r.getMethod()
|
Method := r.getMethod()
|
||||||
Url := r.getUrl()
|
Url := r.getUrl()
|
||||||
Data := r.getData()
|
Data := r.getData()
|
||||||
req, _ := http.NewRequest(Method, Url, Data)
|
req, err := http.NewRequest(Method, Url, Data)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
r.addHeaders(req)
|
r.addHeaders(req)
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,11 @@ func (c *CronServer) InitJobs(ctx context.Context) {
|
||||||
Name: "直连天下报表推送",
|
Name: "直连天下报表推送",
|
||||||
Schedule: "0 12,18,23 * * *",
|
Schedule: "0 12,18,23 * * *",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Func: c.cronService.CronReportSendQywx,
|
||||||
|
Name: "直连天下报表推送",
|
||||||
|
Schedule: "0 12,18,23 * * *",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,9 @@ func registerResponse(router fiber.Router) {
|
||||||
|
|
||||||
func registerCommon(c *fiber.Ctx, err error) error {
|
func registerCommon(c *fiber.Ctx, err error) error {
|
||||||
// 调用下一个中间件或路由处理函数
|
// 调用下一个中间件或路由处理函数
|
||||||
|
if c.Path() == "/api/v1/qywx/callback" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
bsErr, ok := err.(*errors.BusinessErr)
|
bsErr, ok := err.(*errors.BusinessErr)
|
||||||
if !ok {
|
if !ok {
|
||||||
bsErr = errors.SystemError
|
bsErr = errors.SystemError
|
||||||
|
|
|
||||||
|
|
@ -332,14 +332,21 @@ func (s *CallbackService) handleBugOptimizationSubmitDone(ctx context.Context, t
|
||||||
return msg, nil
|
return msg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CallbackService) QywxCallback(c *fiber.Ctx) error {
|
func (s *CallbackService) QywxCallback(c *fiber.Ctx) (err error) {
|
||||||
// 读取头
|
// 读取头
|
||||||
msgSignature := c.Query("msg_signature")
|
httpstr := string(c.Request().URI().QueryString())
|
||||||
timestamp := c.Query("timestamp")
|
|
||||||
nonce := c.Query("nonce")
|
start := strings.Index(httpstr, "msg_signature=")
|
||||||
echostr := c.Query("echostr")
|
start += len("msg_signature=")
|
||||||
|
var msgSignature string
|
||||||
|
next := getString(httpstr, "×tamp=", start, &msgSignature)
|
||||||
|
var timestamp string
|
||||||
|
next = getString(httpstr, "&nonce=", next, ×tamp)
|
||||||
|
var nonce string
|
||||||
|
next = getString(httpstr, "&echostr=", next, &nonce)
|
||||||
|
echostr := httpstr[next:len(httpstr)]
|
||||||
echostr, _ = url.QueryUnescape(echostr)
|
echostr, _ = url.QueryUnescape(echostr)
|
||||||
fmt.Println(msgSignature, timestamp, nonce, echostr)
|
fmt.Println(httpstr, msgSignature, timestamp, nonce, echostr)
|
||||||
wxcpt := wxbizjsonmsgcrypt.NewWXBizMsgCrypt(s.cfg.Qywx.Token, s.cfg.Qywx.AES_KEY, s.cfg.Qywx.CorpId, wxbizjsonmsgcrypt.JsonType)
|
wxcpt := wxbizjsonmsgcrypt.NewWXBizMsgCrypt(s.cfg.Qywx.Token, s.cfg.Qywx.AES_KEY, s.cfg.Qywx.CorpId, wxbizjsonmsgcrypt.JsonType)
|
||||||
echoStr, cryptErr := wxcpt.VerifyURL(msgSignature, timestamp, nonce, echostr)
|
echoStr, cryptErr := wxcpt.VerifyURL(msgSignature, timestamp, nonce, echostr)
|
||||||
if nil != cryptErr {
|
if nil != cryptErr {
|
||||||
|
|
@ -347,7 +354,13 @@ func (s *CallbackService) QywxCallback(c *fiber.Ctx) error {
|
||||||
return fmt.Errorf("%v", cryptErr)
|
return fmt.Errorf("%v", cryptErr)
|
||||||
}
|
}
|
||||||
fmt.Println("verifyUrl success echoStr", string(echoStr))
|
fmt.Println("verifyUrl success echoStr", string(echoStr))
|
||||||
err := c.Send(echoStr)
|
err = c.Send(echoStr)
|
||||||
return err
|
return err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getString(str, endstr string, start int, msg *string) int {
|
||||||
|
end := strings.Index(str, endstr)
|
||||||
|
*msg = str[start:end]
|
||||||
|
return end + len(endstr)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ func (h *ChatService) Chat(c *websocket.Conn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开启心跳检测
|
// 开启心跳检测
|
||||||
//go client.InitHeartbeat(time.Duration(h.cfg.Sys.HeartbeatInterval))
|
go client.InitHeartbeat(time.Duration(h.cfg.Sys.HeartbeatInterval))
|
||||||
|
|
||||||
// 循环读取客户端消息
|
// 循环读取客户端消息
|
||||||
for {
|
for {
|
||||||
|
|
|
||||||
|
|
@ -35,10 +35,16 @@ func (d *CronService) CronReportSendDingTalk(ctx context.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if groupInfo.GroupID == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
groupConfig, err := d.groupConfigBiz.GetGroupConfig(ctx, groupInfo.ConfigID)
|
groupConfig, err := d.groupConfigBiz.GetGroupConfig(ctx, groupInfo.ConfigID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if groupConfig.ConfigID == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
reports, err := d.groupConfigBiz.GetReportLists(ctx, groupConfig)
|
reports, err := d.groupConfigBiz.GetReportLists(ctx, groupConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -60,10 +66,16 @@ func (d *CronService) CronReportSendQywx(ctx context.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if groupInfo.GroupID == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
groupConfig, err := d.groupConfigBiz.GetGroupConfig(ctx, groupInfo.ConfigID)
|
groupConfig, err := d.groupConfigBiz.GetGroupConfig(ctx, groupInfo.ConfigID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if groupConfig.ConfigID == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
reports, err := d.groupConfigBiz.GetReportLists(ctx, groupConfig)
|
reports, err := d.groupConfigBiz.GetReportLists(ctx, groupConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,8 @@ func run() {
|
||||||
botGroupQywxImpl := impl.NewBotGroupQywxImpl(db)
|
botGroupQywxImpl := impl.NewBotGroupQywxImpl(db)
|
||||||
qywxAuth := qywx.NewAuth(configConfig, rdb)
|
qywxAuth := qywx.NewAuth(configConfig, rdb)
|
||||||
group := qywx.NewGroup(botGroupQywxImpl, qywxAuth)
|
group := qywx.NewGroup(botGroupQywxImpl, qywxAuth)
|
||||||
qywxAppBiz := biz.NewQywxAppBiz(configConfig, botGroupQywxImpl, group)
|
other := qywx.NewOther(qywxAuth)
|
||||||
|
qywxAppBiz := biz.NewQywxAppBiz(configConfig, botGroupQywxImpl, group, other)
|
||||||
groupConfigBiz := biz.NewGroupConfigBiz(toolRegis, utils_ossClient, botGroupConfigImpl, registry, configConfig)
|
groupConfigBiz := biz.NewGroupConfigBiz(toolRegis, utils_ossClient, botGroupConfigImpl, registry, configConfig)
|
||||||
dingTalkBotBiz := biz.NewDingTalkBotBiz(doDo, handle, botConfigImpl, botGroupImpl, user, botChatHisImpl, manager, configConfig, sendCardClient, groupConfigBiz)
|
dingTalkBotBiz := biz.NewDingTalkBotBiz(doDo, handle, botConfigImpl, botGroupImpl, user, botChatHisImpl, manager, configConfig, sendCardClient, groupConfigBiz)
|
||||||
// 初始化钉钉机器人服务
|
// 初始化钉钉机器人服务
|
||||||
|
|
|
||||||
|
|
@ -355,19 +355,20 @@ func (b *BbxtTools) GetStatisOfficialProductSumDecline(now time.Time, downWardVa
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
productSumMap = make(map[int32]ProductSumDecline)
|
productSumMap = make(map[string]ProductSumDecline)
|
||||||
)
|
)
|
||||||
for _, v := range data.OfficialProductSumDecline {
|
for _, v := range data.OfficialProductSumDecline {
|
||||||
if _, ex := productSumMap[v.OfficialProductId]; !ex {
|
if _, ex := productSumMap[v.OfficialProductName]; !ex {
|
||||||
productSumMap[v.OfficialProductId] = ProductSumDecline{
|
productSumMap[v.OfficialProductName] = ProductSumDecline{
|
||||||
OfficialProductName: v.OfficialProductName,
|
OfficialProductName: v.OfficialProductName,
|
||||||
OfficialProductId: v.OfficialProductId,
|
OfficialProductId: v.OfficialProductId,
|
||||||
ProductSumReseller: make(map[int32]ProductSumReseller),
|
ProductSumReseller: make(map[int32]ProductSumReseller),
|
||||||
|
Index: productMap[v.OfficialProductName],
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if v.HistoryOneDiff <= sumFilter || v.HistoryTwoDiff <= sumFilter {
|
if v.HistoryOneDiff <= sumFilter || v.HistoryTwoDiff <= sumFilter {
|
||||||
productSumMap[v.OfficialProductId].ProductSumReseller[v.ResellerId] = ProductSumReseller{
|
productSumMap[v.OfficialProductName].ProductSumReseller[v.ResellerId] = ProductSumReseller{
|
||||||
ResellerName: v.ResellerName,
|
ResellerName: v.ResellerName,
|
||||||
CurrentNum: v.CurrentNum,
|
CurrentNum: v.CurrentNum,
|
||||||
HistoryOneNum: v.HistoryOneNum,
|
HistoryOneNum: v.HistoryOneNum,
|
||||||
|
|
@ -376,8 +377,14 @@ func (b *BbxtTools) GetStatisOfficialProductSumDecline(now time.Time, downWardVa
|
||||||
HistoryTwoDiff: v.HistoryTwoDiff,
|
HistoryTwoDiff: v.HistoryTwoDiff,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
var total = make([]ProductSumDecline, 0, len(productSumMap))
|
||||||
|
for k, _ := range productSumMap {
|
||||||
|
total = append(total, productSumMap[k])
|
||||||
|
}
|
||||||
|
sort.Slice(total, func(i, j int) bool {
|
||||||
|
return total[i].Index > total[j].Index
|
||||||
|
})
|
||||||
timeCh := now.Format("1月2日15点")
|
timeCh := now.Format("1月2日15点")
|
||||||
//title := "截至" + timeCh + "销量下滑大于" + fmt.Sprintf("%d", downWardValue) + "明细,分销商仅展示差额大于" + fmt.Sprintf("%d", -sumFilter)
|
//title := "截至" + timeCh + "销量下滑大于" + fmt.Sprintf("%d", downWardValue) + "明细,分销商仅展示差额大于" + fmt.Sprintf("%d", -sumFilter)
|
||||||
title := "截至" + timeCh + "销量下滑较大商品"
|
title := "截至" + timeCh + "销量下滑较大商品"
|
||||||
|
|
@ -386,7 +393,7 @@ func (b *BbxtTools) GetStatisOfficialProductSumDecline(now time.Time, downWardVa
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
filePath := b.cacheDir + "/xlxhmx" + fmt.Sprintf("%d%d", time.Now().Unix(), rand.Intn(1000)) + ".xlsx"
|
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)
|
err = b.OfficialProductSumDeclineExcel(b.excelTempDir+"/"+"/xlxhmx.xlsx", filePath, total, title)
|
||||||
return &ReportRes{
|
return &ReportRes{
|
||||||
ReportName: "销售下滑明细",
|
ReportName: "销售下滑明细",
|
||||||
Title: title,
|
Title: title,
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ func Test_GetStatisOfficialProductSumDecline(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
s := "官方--美团外卖红包5元,官方--美团外卖红包10元,官方--饿了么超级会员月卡,官方--网易云黑胶vip月卡,官方--喜马拉雅巅峰会员月卡,官方--芒果-PC季卡,官方--芒果-PC月卡,官方--芒果-PC周卡,官方--腾讯-周卡,官方--优酷周卡,官方--QQ音乐-绿钻月卡,官方--爱奇艺-周卡,官方--腾讯-月卡,官方--腾讯-季卡,官方--腾讯-年卡,官方--优酷月卡,官方--优酷季卡,官方--优酷年卡,官方--爱奇艺-月卡,官方--爱奇艺-季卡,官方--爱奇艺-年卡"
|
s := "官方--腾讯-周卡,官方--腾讯-月卡,官方--腾讯-季卡,官方--腾讯-年卡,官方--优酷周卡,官方--优酷月卡,官方--优酷季卡,官方--优酷年卡,官方--爱奇艺-周卡,官方--爱奇艺-月卡,官方--爱奇艺-季卡,官方--爱奇艺-年卡,官方--芒果-PC周卡,官方--芒果-PC月卡,官方--芒果-PC季卡,官方--美团外卖红包5元,官方--美团外卖红包10元,官方--QQ音乐-绿钻月卡,官方--饿了么超级会员月卡,官方--网易云黑胶vip月卡,官方--喜马拉雅巅峰会员月卡"
|
||||||
//s := "官方--QQ音乐-绿钻月卡"
|
//s := "官方--QQ音乐-绿钻月卡"
|
||||||
report, err := o.GetStatisOfficialProductSumDecline(time.Now(), 1000, strings.Split(s, ","), -150)
|
report, err := o.GetStatisOfficialProductSumDecline(time.Now(), 1000, strings.Split(s, ","), -150)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ type ProductSumDecline struct {
|
||||||
OfficialProductId int32
|
OfficialProductId int32
|
||||||
OfficialProductName string
|
OfficialProductName string
|
||||||
ProductSumReseller map[int32]ProductSumReseller
|
ProductSumReseller map[int32]ProductSumReseller
|
||||||
|
Index int
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductSumReseller struct {
|
type ProductSumReseller struct {
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ func (u *Uploader) uploadToOSS(fileName string, fileBytes []byte) string {
|
||||||
// return url
|
// return url
|
||||||
//}
|
//}
|
||||||
|
|
||||||
func (b *BbxtTools) OfficialProductSumDeclineExcel(templatePath, outputPath string, sumMap map[int32]ProductSumDecline, title string) error {
|
func (b *BbxtTools) OfficialProductSumDeclineExcel(templatePath, outputPath string, sumSlice []ProductSumDecline, title string) error {
|
||||||
// 1. 读取模板
|
// 1. 读取模板
|
||||||
f, err := excelize.OpenFile(templatePath)
|
f, err := excelize.OpenFile(templatePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -230,7 +230,7 @@ func (b *BbxtTools) OfficialProductSumDeclineExcel(templatePath, outputPath stri
|
||||||
currentRow := 3
|
currentRow := 3
|
||||||
pattern := `\$\{(.*?)\}`
|
pattern := `\$\{(.*?)\}`
|
||||||
re := regexp.MustCompile(pattern)
|
re := regexp.MustCompile(pattern)
|
||||||
for _, product := range sumMap {
|
for _, product := range sumSlice {
|
||||||
// 排序 ProductLoss
|
// 排序 ProductLoss
|
||||||
var reseller []ProductSumReseller
|
var reseller []ProductSumReseller
|
||||||
for _, p := range product.ProductSumReseller {
|
for _, p := range product.ProductSumReseller {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue