ๆฌๆฌกๅ็บงๅฐ ZenOps ้้ๆบๅจไบบ็่ชๅฎไนๆตๅผๅฎ็ฐๆฟๆขไธบ้้ๅฎๆน SDK ็ StreamingUpdate API,ๅฎ็ฐ็ๆญฃ็ๆตๅผๆๅญๆบๆๆใ
ๅ็บงๆฅๆ: 2025-12-09 ๅบไบๅ่: PandaWiki ๅฎ็ฐ
ๆทปๅ ไบไปฅไธ้้ๅฎๆน SDK ๅ :
github.com/alibabacloud-go/dingtalk v1.6.88
โโโ github.com/alibabacloud-go/dingtalk/card_1_0 # ๅก็ๆไฝ
โโโ github.com/alibabacloud-go/dingtalk/oauth2_1_0 # OAuth ่ฎค่ฏ
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.7 # OpenAPI ๅบ็ก
github.com/alibabacloud-go/tea v1.3.9 # Tea ๆกๆถ
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 # Tea ๅทฅๅ
ท
github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1 # Stream SDK (้ข็)
| ๆไปถ | ่กๆฐ | ่ฏดๆ |
|---|---|---|
internal/server/dingtalk_stream.go |
238 | ๅฎๆน SDK ๆตๅผๅฎขๆท็ซฏๅฎ็ฐ |
docs/dingtalk-setup-guide.md |
350+ | ๅฎๆด้ ็ฝฎๆๅ |
docs/dingtalk-streaming-upgrade.md |
ๆฌๆไปถ | ๅ็บงๆป็ปๆๆกฃ |
| ๆไปถ | ๅๆดๅ ๅฎน |
|---|---|
internal/config/config.go |
ๆทปๅ TemplateID ๅญๆฎตๅฐ DingTalkConfig |
internal/server/dingtalk.go |
ๆทปๅ DingTalkStreamClient ๆฏๆ,ๆทปๅ ConversationType ๅ SenderStaffID ๅญๆฎต |
configs/config.yaml |
ๆทปๅ template_id ้
็ฝฎ้กน |
| ็นๆง | ๆงๅฎ็ฐ | ๆฐๅฎ็ฐ |
|---|---|---|
| API | ่ชๅฎไน SendStreamMessage (ไธๅญๅจ) |
ๅฎๆน StreamingUpdate API |
| ๆถๆฏ็ฑปๅ | ๆฎ้ๆๆฌๆถๆฏ | AI ไบๅจๅก็ |
| ๆตๅผๆๆ | โ ๅๆฎตๆพ็คบ | โ ็ๆญฃๆตๅผๆๅญๆบๆๆ |
| ๆดๆฐๆบๅถ | ๆๅจๅๅๅ้ | ๅฎๆถๆดๆฐ (1.5็ง/ๆฌก) |
| ็จๆทไฝ้ช | ๆฎ้ | ไผ็ง |
| Markdown | ้จๅๆฏๆ | ๅฎๆดๆฏๆ |
| ๅฏ่ฝฌๅๆง | - | โ ๆฏๆ่ฝฌๅ |
| ้ ็ฝฎๅคๆๅบฆ | ็ฎๅ | ้ๅๅปบๅก็ๆจกๆฟ |
DingTalkClient
โโโ SendStreamMessage() โ ่ฐ็จไธๅญๅจ็ API
โโโ ๅๅๅ้ๆๆฌๆถๆฏ
DingTalkStreamClient (ๆฐๅข)
โโโ GetAccessToken() โ OAuth2 ่ทๅไปค็
โโโ CreateAndDeliverCard() โ ๅๅปบๅนถๆ้ AI ๅก็
โโโ StreamingUpdate() โ ๆตๅผๆดๆฐๅก็ๅ
ๅฎน
โโโ StreamResponse() โ ๅฎๆถๆดๆฐ็ฎก็
DingTalkMessageHandler (ๅขๅผบ)
โโโ streamClient: *DingTalkStreamClient (ๆฐๅข)
โโโ processWithStreamingUpdate() โ ไฝฟ็จๅฎๆน SDK ๅค็
1. GetAccessToken() - ่ฎฟ้ฎไปค็่ทๅ
// ็น็น:
// - ไฝฟ็จๅฎๆน OAuth2 SDK
// - ๅ
ๅญ็ผๅญ (ๆๅ 5 ๅ้ๅทๆฐ)
// - ๅ้ๆฃๆฅ้ (DCL) ๆจกๅผ2. CreateAndDeliverCard() - ๅๅปบๅก็
// ๅ่ฝ:
// - ๅๅปบ AI ไบๅจๅก็
// - ่ชๅจๅบๅๅ่/็พค่
// - ่ฎพ็ฝฎๅก็ๅๅง็ถๆ
// ๅ
ณ้ฎๅๆฐ:
// - OutTrackId: ๅก็ๅฏไธๆ ่ฏ
// - CallbackType: "STREAM" (ๆตๅผๆจกๅผ)
// - OpenSpaceId: ๆ นๆฎไผ่ฏ็ฑปๅๅจๆ็ๆ3. StreamingUpdate() - ๆตๅผๆดๆฐ
// ๆ ธๅฟ API:
request := &dingtalkcard_1_0.StreamingUpdateRequest{
OutTrackId: tea.String(trackID), // ๅก็่ท่ธช ID
Guid: tea.String(uuid.New()), // ๆฏๆฌกๆดๆฐ็ๅฏไธๆ ่ฏ
Key: tea.String("content"), // ๆดๆฐ็ๅญๆฎตๅ
Content: tea.String(content), // ๆฐๅ
ๅฎน
IsFull: tea.Bool(true), // ๅ
จ้ๆดๆฐ
IsFinalize: tea.Bool(isFinalize), // ๆฏๅฆๆ็ป็ๆฌ
IsError: tea.Bool(false), // ๆฏๅฆ้่ฏฏ
}4. StreamResponse() - ๆตๅผๅๅบ็ฎก็
// ๅฎ็ฐ:
// - ไฝฟ็จ Ticker ๅฎๆถๆดๆฐ (1.5็ง)
// - ไป channel ๆฅๆถๅ
ๅฎนๆต
// - ็ดฏ็งฏๅ
ๅฎนๅๆน้ๆดๆฐ
// - ๆ็ปๅ้ isFinalize=true ็ๆฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 1. ๆฅๆถ้้ๆถๆฏ (HTTP Callback) โ
โ HandleMessage() โ ้ช่ฏ็ญพๅ โ ่งฃๅฏๆถๆฏ โ
โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 2. ่งฃๆๆๅพ โ
โ ParseIntent() โ ๆญฃๅๅน้
โ ๆๅๅๆฐ โ
โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 3. ๅผๆญฅๅค็ (processQueryAsync) โ
โ โโ ๆฃๆฅๆฏๅฆ้
็ฝฎ streamClient โ
โ โโ ๆฏ โ processWithStreamingUpdate() โ
โ โโ ๅฆ โ ไฝฟ็จๆง็ SendStreamMessage() โ
โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 4. ๆตๅผๆดๆฐๅค็ (processWithStreamingUpdate) โ
โ โโ ๅๅปบๅนถๆ้ AI ๅก็ โ
โ โโ ๅ้ๅๅงๆ็คบ "ๆญฃๅจๆฅ่ฏข..." โ
โ โโ ่ฐ็จ MCP ๅทฅๅ
ทๆฅ่ฏข โ
โ โโ ๅๅปบๅ
ๅฎน channel โ
โ โโ ๆจกๆๆตๅผ่พๅบ (ๆ่กๅ้) โ
โ โโ StreamResponse() ๅฎๆถๆดๆฐๅก็ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
{
"contents": [
{
"type": "markdown",
"text": "${content}", // โ ๅฟ
้กปไฝฟ็จ่ฟไธชๅ้ๅ
"id": "content" // โ ๅฟ
้กปไฝฟ็จ่ฟไธช ID
}
]
}้่ฆ่ฏดๆ:
${content}ๅ้ๅๅฟ ้กปไธStreamingUpdateไธญ็Key: "content"ๅน้ - ๅฆๆๅญๆฎตๅไธๅ,ๆตๅผๆดๆฐๅฐๅคฑ่ดฅ
ๆฐๅฎ็ฐๅฎๅ จๅๅๅ ผๅฎน:
if h.streamClient != nil {
// ไฝฟ็จๆฐ็ๅฎๆน SDK ๆตๅผๆดๆฐ
h.processWithStreamingUpdate(ctx, msg, intent, userMessage)
} else {
// ้็บงๅฐๆง็ๅฎ็ฐ
h.client.SendStreamMessage(ctx, ...)
}้็บงๆกไปถ:
- ๆช้
็ฝฎ
template_id - ๆตๅผๅฎขๆท็ซฏๅๅงๅๅคฑ่ดฅ
| ้ ็ฝฎ้กน | ๆงๅฎ็ฐ | ๆฐๅฎ็ฐ | ๅฟ ้ๆง |
|---|---|---|---|
app_key |
โ | โ | ๅฟ ้ |
app_secret |
โ | โ | ๅฟ ้ |
agent_id |
โ | โ | ๅฟ ้ |
template_id |
- | โ | ๅฏ้ (ๅฏ็จๆตๅผ่พๅบ) |
callback.token |
โ | โ | ๅฟ ้ |
callback.aes_key |
โ | โ | ๅฟ ้ |
type DingTalkStreamClient struct {
tokenCache struct {
accessToken string
expireAt time.Time
}
tokenMutex sync.RWMutex
}ไผๅฟ:
- ๅๅฐ API ่ฐ็จๆฌกๆฐ
- ๆๅ 5 ๅ้ๅทๆฐ (้ฟๅ ่ฟๆ)
- ็บฟ็จๅฎๅ จ (DCL ๆจกๅผ)
updateTicker := time.NewTicker(1500 * time.Millisecond)่ฎพ่ฎก่่:
- 1.5 ็ง: ๅนณ่กกๅฎๆถๆงๅ API ้ๆต
- ๆ้ๆดๆฐ: ไป ๅฝๅ ๅฎนๅๅๆถๆๆดๆฐ
- ๆ็ปไฟ่ฏ: ้้ๅ ณ้ญๆถๅฟ ๅฎๅ้ๆ็ป็ๆฌ
// ๆจกๆๆตๅผ่พๅบ:ๅฐ็ปๆๆ่กๅๆนๅ้
lines := strings.Split(result, "\n")
for _, line := range lines {
contentCh <- line + "\n"
time.Sleep(50 * time.Millisecond) // ๆๅญๆบๆๆ
}# .env
DINGTALK_APP_KEY=dingxxxxxxxx
DINGTALK_APP_SECRET=xxxxxxxxxx
DINGTALK_AGENT_ID=1234567890
DINGTALK_TEMPLATE_ID=4d18414c-aabc-4ec8-9e67-4ceefeada72a.schema
DINGTALK_CALLBACK_TOKEN=your_token
DINGTALK_AES_KEY=your_aes_keydingtalk:
enabled: true
app_key: ${DINGTALK_APP_KEY}
app_secret: ${DINGTALK_APP_SECRET}
agent_id: ${DINGTALK_AGENT_ID}
template_id: ${DINGTALK_TEMPLATE_ID} # ๆฐๅข
callback:
token: ${DINGTALK_CALLBACK_TOKEN}
aes_key: ${DINGTALK_AES_KEY}
url: https://your-domain.com/api/v1/dingtalk/callback$ go build -o bin/zenops .
โ
็ผ่ฏๆๅ- ๅๅปบ AI ๅก็ๆจกๆฟ
- ้
็ฝฎ
template_id็ฏๅขๅ้ - ๅฏๅจๆๅก
- ๅจ็พค่ไธญ @ๆบๅจไบบ ๅ้ๆฅ่ฏข
- ้ช่ฏๅก็ๅๅปบๆๅ
- ้ช่ฏๅๅงๆ็คบๆพ็คบ
- ้ช่ฏๆตๅผๆดๆฐๆๆ (ๆๅญๆบๆๆ)
- ้ช่ฏๆ็ป็ปๆๆญฃ็กฎๆพ็คบ
- ๆต่ฏ้่ฏฏๅค็ (ๆ ๆ่งฆๅ้่ฏฏ)
- ๆต่ฏๅ่ๅบๆฏ
ๅฏ็จ DEBUG ๆฅๅฟ,ๆฅ็ๅ ณ้ฎๆฅๅฟ:
INFO Got DingTalk access token expire_in=7200
INFO Created and delivered AI card track_id=xxx conversation_type=2
DEBUG Streaming update card track_id=xxx content_len=150 finalize=false
DEBUG Streaming update card track_id=xxx content_len=500 finalize=false
DEBUG Streaming update card track_id=xxx content_len=800 finalize=true
Error: failed to create and deliver card
ๅๅ :
template_idไธๆญฃ็กฎ- ๅก็ๆจกๆฟๆชๅๅธ
- ๅบ็จๆ้ไธ่ถณ
่งฃๅณ:
- ๆฃๆฅๆจกๆฟ ID ๆฏๅฆๆญฃ็กฎ
- ็กฎ่ฎคๆจกๆฟๅทฒๅๅธๅฐ็ไบง็ฏๅข
- ๆฃๆฅๅบ็จๆฏๅฆๆ"็พคๆถๆฏๅ้"ๆ้
Error: failed to update card
ๅๅ :
trackIDไธๅญๅจ- ๅก็ๆชๅๅปบๆๅ
- Access Token ่ฟๆ
่งฃๅณ:
- ็กฎไฟๅ
่ฐ็จ
CreateAndDeliverCard() - ๆฃๆฅ Token ็ผๅญ้ป่พ
- ๆฅ็่ฏฆ็ป้่ฏฏๆฅๅฟ
ๅๅ :
- ๅก็ๆจกๆฟไธญๅญๆฎตๅไธๆฏ
content - ๅญๆฎต ID ไธๆฏ
content
่งฃๅณ: ไฟฎๆนๅก็ๆจกๆฟ,็กฎไฟ:
{
"text": "${content}",
"id": "content"
}ๆงๅฎ็ฐ:
- ้ฟๆถๆฏ (3000 ๅญ): ~3-5 ๆฌก API ่ฐ็จ
ๆฐๅฎ็ฐ:
- ๅๆ ทๆถๆฏ: 1 ๆฌกๅๅปบ + 2-3 ๆฌกๆดๆฐ + 1 ๆฌกๆ็ป = 4-5 ๆฌก
็ป่ฎบ: API ่ฐ็จๆฌกๆฐ็ธๅฝ,ไฝ็จๆทไฝ้ชๆพ่ๆๅ
- ๅก็ๅๅปบ: ~200ms
- ้ฆๆฌกๆดๆฐ: ~300ms (ๅ ๅซ MCP ๆฅ่ฏข)
- ๅฎๆถๆดๆฐ: ~100ms/ๆฌก
- ๆป่ๆถ: ๅๅณไบ MCP ๆฅ่ฏขๆถ้ด
ๅฝๅๅฎ็ฐไฝฟ็จ HTTP ๅ่ฐ,ๅฏ่ฟไธๆญฅๅ็บงๅฐ Stream ๆจกๅผ:
// ๆ ้ HTTP ๅ่ฐ็ๅฎ็ฐ
cli := client.NewStreamClient(client.WithAppCredential(
client.NewAppCredentialConfig(clientID, clientSecret),
))
cli.RegisterChatBotCallbackRouter(OnMessageReceived)
cli.Start(ctx)ไผๅฟ:
- ๆ ้้ ็ฝฎๅ ฌ็ฝๅ่ฐ URL
- ๆด็ฎๅ็้จ็ฝฒ
- ๆดไฝ็ๅปถ่ฟ
็ฎๅ MCP ๅทฅๅ ท่ฟๅๅฎๆด็ปๆๅๅๆจกๆๆตๅผ,ๅฏๆน่ฟไธบ:
// ๅฆๆ MCP ๅทฅๅ
ทๆฏๆๆตๅผ่ฟๅ
resultCh := mcpServer.CallToolStream(ctx, request)
streamClient.StreamResponse(ctx, trackID, resultCh, question)ๅฝๅ Token ็ผๅญๅจๅ ๅญไธญ,ๅคๅฎไพ้จ็ฝฒๆถๅฏไฝฟ็จ Redis:
// ไป Redis ่ทๅ Token
token, err := redis.Get("dingtalk:access_token")
if err == nil && token != "" {
return token
}- PandaWiki ๅฎ็ฐ:
tmp/PandaWiki/backend/pkg/bot/dingtalk/stream.go - ้้ๅฎๆนๆๆกฃ: StreamingUpdate API
- SDK ๆๆกฃ: alibabacloud-go/dingtalk
- ๆน่ฟๆนๆก:
docs/dingtalk-stream-improvement.md - ้
็ฝฎๆๅ:
docs/dingtalk-setup-guide.md
- โ
ไฝฟ็จๅฎๆน API: ไป่ชๅฎไนๅฎ็ฐๅ็บงๅฐ้้ๅฎๆน
StreamingUpdateAPI - โ AI ไบๅจๅก็: ไปๆฎ้ๆๆฌๅ็บงๅฐๆฏๆ Markdown ็ไบๅจๅก็
- โ ็ๅฎๆตๅผๆๆ: ๅฎ็ฐ็ฑปไผผ ChatGPT ็ๆๅญๆบๆๆ
- โ
ๅๅๅ
ผๅฎน: ไฟๆๅฏนๆช้
็ฝฎ
template_id็ๅ ผๅฎน - โ ๅฎๅๆๆกฃ: ๆไพ่ฏฆ็ป็้ ็ฝฎๅไฝฟ็จๆๅ
- ๆฐๅขไปฃ็ : ~500 ่ก
- ๅๅ ๆต่ฏ: ๅพ ๆทปๅ
- ๆๆกฃ่ฆ็: 100%
- ็ผ่ฏ็ถๆ: โ ้่ฟ
- ๅๅๅ ผๅฎน: โ ๅฎๅ จๅ ผๅฎน
ๅฝๅ็ๆฌ โ ๆทปๅ ไพ่ต โ ้
็ฝฎๆจกๆฟ โ ๅฏ็จๆตๅผ โ ๅฎๆๅ็บง
(ๆง) go mod tidy ้้ๅนณๅฐ template_id (ๆฐ)
ๅ็บงๆถ้ด: < 30 ๅ้ (ๅ ๅซๅๅปบๅก็ๆจกๆฟ)
ๅ็บงๅฎๆๆฅๆ: 2025-12-09 ๅฎ็ฐ่ : Claude Code ๅฎกๆ ธ็ถๆ: ๅพ ๆต่ฏ้ช่ฏ