fix(integration): 更新Authorization令牌并移除代理设置

- 替换camel_oil_api中CreateOrder方法的Authorization令牌
- 注释掉基于订单ID获取和设置代理IP的逻辑

feat(merchant): 使用SSE实现订单查询总结的流式返回

- 修改merchant_v1_order_query_summary中queryAppleResult为流式SSE返回
- 通过OpenAI流式API逐块发送总结内容,实时推送客户端
- 设置响应头支持SSE并在响应中刷新缓冲区

fix(api): 修正OrderQuerySummaryRes返回的内容类型

- 将OrderQuerySummaryRes结构的mime类型改为text/event-stream以支持流式响应
This commit is contained in:
danial
2025-11-29 12:51:31 +08:00
parent c23e1d7edc
commit a1fd8baa63
3 changed files with 54 additions and 22 deletions

View File

@@ -34,6 +34,5 @@ type OrderQuerySummaryReq struct {
}
type OrderQuerySummaryRes struct {
g.Meta `mime:"application/json"`
Message string `json:"message" dc:"结论"`
g.Meta `mime:"text/event-stream"`
}

View File

@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"github.com/duke-git/lancet/v2/pointer"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/option"
"kami/internal/service"
@@ -50,29 +51,63 @@ func (c *ControllerV1) OrderQuerySummary(ctx context.Context, req *v1.OrderQuery
redeemRecord := queryRedeemCard(ctx, req.MerchantOrderNo)
record = append(record, redeemRecord...)
if len(record) > 0 {
msg, err2 := queryAppleResult(ctx, record)
if err2 != nil {
msg = "总结失败,请稍候重试"
r := ghttp.RequestFromCtx(ctx)
if r != nil {
queryAppleResult(ctx, r, record)
return nil, nil
}
res.Message = msg
}
return
}
func queryAppleResult(ctx context.Context, record []*v1.OrderQueryRecord) (string, error) {
recordStr, _ := json.Marshal(record)
// queryAppleResult 通过 SSE 流式返回 AI 总结结果
func queryAppleResult(ctx context.Context, r *ghttp.Request, record []*v1.OrderQueryRecord) {
var copyRecord []*struct {
Operation string
Remark string
}
for _, queryRecord := range record {
copyRecord = append(copyRecord, &struct {
Operation string
Remark string
}{
Operation: queryRecord.Operation,
Remark: queryRecord.Remark,
})
}
recordStr, _ := json.Marshal(copyRecord)
// 设置 SSE 响应头
r.Response.Header().Set("Content-Type", "text/event-stream")
r.Response.Header().Set("Cache-Control", "no-cache")
r.Response.Header().Set("Connection", "keep-alive")
client := openai.NewClient(
option.WithAPIKey("sk-jYRlGtXAJ3Q82TS_FxWLFX6wn_VubNnyCtfxYZYP32ccqfQaGwQJHMq5I7k"),
option.WithBaseURL("https://hk.uniapi.io/v1"),
)
chatCompletion, err := client.Chat.Completions.New(context.TODO(), openai.ChatCompletionNewParams{
// 使用流式 API 获取结果
stream := client.Chat.Completions.NewStreaming(ctx, openai.ChatCompletionNewParams{
Messages: []openai.ChatCompletionMessageParamUnion{
openai.UserMessage(fmt.Sprintf("%s\n只生成简洁结论不需要建议使用中文", string(recordStr))),
openai.UserMessage(fmt.Sprintf("%s\n只生成简洁结论不需要建议中文回复", string(recordStr))),
},
Model: "deepseek-v3.2-exp",
})
if err != nil {
return "", err
// 逐块发送流式数据
for stream.Next() {
chunk := stream.Current()
if len(chunk.Choices) > 0 && chunk.Choices[0].Delta.Content != "" {
content := chunk.Choices[0].Delta.Content
// 创建 SSE 格式的数据
eventData := map[string]interface{}{
"content": content,
}
dataBytes, _ := json.Marshal(eventData)
r.Response.Write([]byte(fmt.Sprintf("data: %s\n\n", string(dataBytes))))
// 使用 GoFrame 内置的 Flush 方法立即刷新缓冲区
r.Response.Flush()
}
}
return chatCompletion.Choices[0].Message.Content, nil
}

View File

@@ -7,7 +7,6 @@ import (
"github.com/gogf/gf/v2/encoding/gbase64"
"github.com/gogf/gf/v2/net/gclient"
"github.com/gogf/gf/v2/os/glog"
"kami/internal/service"
"strings"
"sync"
"sync/atomic"
@@ -33,7 +32,6 @@ func NewClient() *Client {
"channel": "app",
"content-type": "application/json",
"host": "recharge3.bac365.com",
"priority": "u=3, i",
"user-agent": "Mozilla/5.0 (iPad; CPU OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Html5Plus/1.0 (Immersed/20) uni-app",
})
client.SetBrowserMode(true)
@@ -179,7 +177,7 @@ func (c *Client) QueryAvailableDenominations(ctx context.Context, token string)
func (c *Client) CreateOrder(ctx context.Context, phone, token string, amount float64) (orderId string, payUrl string, err error) {
//c.Client.SetHeader("Authorization", "Bearer "+c.getAuth(ctx, token))
c.Client.SetHeader("Authorization", "Bearer lIHCzNDVEWP9RVZcTAWRTGsIEm+GxSN0g4PYU6ydsIfSLH/v+6tIOy/wPhgLGIDpoUdSQUtUW8KV2OABmz2shbE4otvjn1EH0Nf7W9NHm+BuTAAaeMKqW9otAZ2K2clltxefiAXl3PhaRgsqybfsq+ELkqPoYVdyksQLS8Y6kA8=")
c.Client.SetHeader("Authorization", "Bearer 2TL5/6FN+qWSZzsLeobeFnHORq3ZBbhxoyvKcrXeO7vtLe/sSKhaYe8rfhJT7OmcgSbI5BIH7EVCSYoiyIqESzOP9vP8Hwel5a446ba4/qGByJDT6JQau60ik5z4j3tvZC3pLjGQ5FxbaftlNe7l6FJz4GE5MObkKgWNOYwMP8w=")
//
goods, err := c.QueryAvailableDenominations(ctx, token)
@@ -282,15 +280,15 @@ func (c *Client) CreateOrder(ctx context.Context, phone, token string, amount fl
Channel string `json:"channel"`
Yanqian bool `json:"yanqian"`
}{
BodyStr: "AzturcN0yAHCHi/k+xbcLwT+ZwbP7MFoEXeTt89fyx+wSTcjm+A8u7CBpieoET4WUde4aau04pbczjTd/grr0Cm0tZJEdK89eAMXQVU8u/NVImAKlyRdZ2/klP6A3B89Ff6YohGS2DhrRwE4i62E+v+HA2fskO1JRIuXvTN9vySPyD2bTopW1ZxoSU4Pg20BWdAdqaQ7UevggIdUyOu8htQH3j10tTy9XaNW6lrGDOut1FuCCvf5V3JLJQUDy8xWiDnqx7gM476RDwuuEdYtgsGopJCGxmMjWrVvAgrcErHoLHYzRb421ztXc+sZao1GmajpISO+Wa+EBp2Qv2r1jCTS0dcSudMMnb6ElGD3zw2Hw6CiROwSPO2S/BhhgyZbZzd6iFXM35GvxPNfFoKp0YqEe9uVXx0p2jwDtLXG3waOuc+8XC+XtNdzlhOSDop9zG94OfA86+v/ENK2M2xMzUKihUo1+Mkek2ABLZsCiUlBxkzA0nPecqCiiX8/qfjUBZgB5dphDY/PBAgvIDivlOQUBiErTMiNYZJ5ZYMcIOI4Z61TWadmxq/3yABP/cSugj5sSro7415au85T6l//8vADxa41bsJ77JGMNcM1qTT8o3Y1a7fekiHk4ZagKEDHMPVLMwrphzeTUyaRe9VPcO0zl3VTbz7xtBD44bWWz/FI9yxe9BTrnqjn12bpxB5xZHP+FAYLhMfrNdVh5+cloJ+gHBGnAfIH+6ejloMiGSJcJl1/Tdv4Bv06cV+6xuo1H/KVZ8GWtLUVGRxtk7WfVFNqcic5LGnOkOyn1MDwq6Gl0Gmy+xbvp23ziRDKXdJRK57Klk/zSdb5cL6fKIHTpI+4igf69nv6x2aQwML2/QAZ3+D4mkd+t0V04U4YRwBBIKHL+VijVp1fUaftpCty2byCR1aMsMGg50bSH4SU+bq3UCSDHMmyaU11/WBoCIgKq5kdOtCinMBiDCqcQJG8C1jixNJO4TUODi+SgS6s7F2fRg+9ugz5s5tbpxLFW1XlIWvXYSB4/HCUB5jVBPvIzAesrhF4sHK8GxNWEoH5hsT/pym0dBeFOvx8fX3yQdGvau70/hW11qUMpinc8dH3mFgF0aXZQwbmL5ipL7Vx/KAAsQeUQlDa2N4usa3VrAwyD68/aloIw2rsFLcseQ5i2FxCtReBZipt91+eYjITebY=",
BodyStr: "gusr/ro/aXwDOxQTbct9/koDxFSzX1R5tRiHtg9YdOx563WvtpU14Yw5Q9plLegeWaJSiyY3vgNB79tyIZXZ0AdsRS/qb4JLSZssQKuNVWG19pdX449uMQuI7f/YqbfEwxht4auuwSKYyg9G5OzLS0NSqZLR/y00w4zSuPGMfdKBk9UTlIiBdK9/RF2e+pUWT4Xds+kG6zWxUFvNdgheojlYTYFK7EMZvROaw25sRjuI9tzujZxW0+eszDDgn9DQ1uNVYImuaRe2C3gJwRcbsf1Av7j0SZmWnviyAsSutEyrsuYp9YUaMHtU/hjW+OouwaIXVf/INdXwySoY1kbcF3jvwke56lLBuXn7iSQh24V7AsAna4Uh8Xf6pBGf871IOyYhGBgjeT+iDj0FxcY/ivJb0B8CzeHfz/Bu1Rjbw0wpFu8J9v9Uf1TgcCinj2bQgnjbJ6a2N+av1fKx0xC2rpqPsnelISO50cLsIEAyy8vz2cKabyq5/IMGBK2EyPE4BB32v2a+e8MKF2dQ0Gy9Z8seXN7moPwB4V4JkmbYMHA6+Rdf87s8Z9LfRJzZSMtlMUouFV0oVlc4aKDs3wcZgrXBdb5lLmA5U0RzipaT0feoq0Q9TYN1uFHRKCmgysK0fxRVFvl4rYaiMQFaTtuHZ9jgcYSPTca2KvjiRLXpQ+8ZLkehiHBd89I+RIt5aGYOp+e/XW4xX/NUWLbimXRs8F5mSYhoko6qidr90psRo9JezNKc1wG1HVMJ4Su7MYcB+UoZI9t9EiPIJ+3avnuW8qQDgqqq7Bi+Vn+PjmvkeujejuwJL5J3WFfSjPP74HwoXg73jgmX2KLI5mU8FDqnojj/KMJmQa3eloOvSZu+uhwFWQdXabaM+o0vNJdSeJjQ7XJNQE9PWag9IxdkHmpZgsAzuu5Ctk41IGPqA/vstekhXRWyTAkl5qj8VI/duVlWWGDzQNaOYi1qnrabBwvffop450xGoCs8ahI7JLOcprIEY5icpmylIh3dvmMGnGOtYbcwQBKNsiA8xbslEdtmdytYobEYwX1UC7ByR9VPvA+3q2MZtxFOa3nKz+meuCAbaHNzEaer1TyjGloQ95sLQKL5xQMacf+0VbkcKJY7mIHaziRkQ9DOTHYgL4IAzsL9UT0bgmmrpP/zzCyzMm/cuZO7YOr4RLICjU3AXhLnPjo=",
Channel: "app",
Yanqian: true,
}
proxy, err2 := service.ProxyPool().GetProxyByOrderId(ctx, phone)
if err2 == nil && proxy.Host != "" {
glog.Info(ctx, "代理 ip", proxy.String())
c.Client.SetProxy(proxy.String())
}
//proxy, err2 := service.ProxyPool().GetProxyByOrderId(ctx, phone)
//if err2 == nil && proxy.Host != "" {
// glog.Info(ctx, "代理 ip", proxy.String())
// c.Client.SetProxy(proxy.String())
//}
resp, err1 := c.Client.ContentJson().Post(ctx, "https://recharge3.bac365.com/camel_wechat_mini_oil_server/eCardMall/createShoppingOrder", req)
if err1 != nil {
resultMutex.Lock()