Skip to content

Latest commit

ย 

History

History
468 lines (362 loc) ยท 13.4 KB

File metadata and controls

468 lines (362 loc) ยท 13.4 KB

้’‰้’‰ๆตๅผ่พ“ๅ‡บๅ‡็บงๆ€ป็ป“

ๅ‡็บงๆฆ‚่ฟฐ

ๆœฌๆฌกๅ‡็บงๅฐ† ZenOps ้’‰้’‰ๆœบๅ™จไบบ็š„่‡ชๅฎšไน‰ๆตๅผๅฎž็Žฐๆ›ฟๆขไธบ้’‰้’‰ๅฎ˜ๆ–น SDK ็š„ StreamingUpdate API,ๅฎž็Žฐ็œŸๆญฃ็š„ๆตๅผๆ‰“ๅญ—ๆœบๆ•ˆๆžœใ€‚

ๅ‡็บงๆ—ฅๆœŸ: 2025-12-09 ๅŸบไบŽๅ‚่€ƒ: PandaWiki ๅฎž็Žฐ

ไธ€ใ€ๆ ธๅฟƒๅ˜ๆ›ด

1.1 ๆ–ฐๅขžๅฎ˜ๆ–น SDK ไพ่ต–

ๆทปๅŠ ไบ†ไปฅไธ‹้’‰้’‰ๅฎ˜ๆ–น 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 (้ข„็•™)

1.2 ๆ–ฐๅขžๆ–‡ไปถ

ๆ–‡ไปถ ่กŒๆ•ฐ ่ฏดๆ˜Ž
internal/server/dingtalk_stream.go 238 ๅฎ˜ๆ–น SDK ๆตๅผๅฎขๆˆท็ซฏๅฎž็Žฐ
docs/dingtalk-setup-guide.md 350+ ๅฎŒๆ•ด้…็ฝฎๆŒ‡ๅ—
docs/dingtalk-streaming-upgrade.md ๆœฌๆ–‡ไปถ ๅ‡็บงๆ€ป็ป“ๆ–‡ๆกฃ

1.3 ไฟฎๆ”นๆ–‡ไปถ

ๆ–‡ไปถ ๅ˜ๆ›ดๅ†…ๅฎน
internal/config/config.go ๆทปๅŠ  TemplateID ๅญ—ๆฎตๅˆฐ DingTalkConfig
internal/server/dingtalk.go ๆทปๅŠ  DingTalkStreamClient ๆ”ฏๆŒ,ๆทปๅŠ  ConversationType ๅ’Œ SenderStaffID ๅญ—ๆฎต
configs/config.yaml ๆทปๅŠ  template_id ้…็ฝฎ้กน

ไบŒใ€ๅฎž็Žฐๅฏนๆฏ”

2.1 ๆ—งๅฎž็Žฐ vs ๆ–ฐๅฎž็Žฐ

็‰นๆ€ง ๆ—งๅฎž็Žฐ ๆ–ฐๅฎž็Žฐ
API ่‡ชๅฎšไน‰ SendStreamMessage (ไธๅญ˜ๅœจ) ๅฎ˜ๆ–น StreamingUpdate API
ๆถˆๆฏ็ฑปๅž‹ ๆ™ฎ้€šๆ–‡ๆœฌๆถˆๆฏ AI ไบ’ๅŠจๅก็‰‡
ๆตๅผๆ•ˆๆžœ โŒ ๅˆ†ๆฎตๆ˜พ็คบ โœ… ็œŸๆญฃๆตๅผๆ‰“ๅญ—ๆœบๆ•ˆๆžœ
ๆ›ดๆ–ฐๆœบๅˆถ ๆ‰‹ๅŠจๅˆ†ๅ—ๅ‘้€ ๅฎšๆ—ถๆ›ดๆ–ฐ (1.5็ง’/ๆฌก)
็”จๆˆทไฝ“้ชŒ ๆ™ฎ้€š ไผ˜็ง€
Markdown ้ƒจๅˆ†ๆ”ฏๆŒ ๅฎŒๆ•ดๆ”ฏๆŒ
ๅฏ่ฝฌๅ‘ๆ€ง - โœ… ๆ”ฏๆŒ่ฝฌๅ‘
้…็ฝฎๅคๆ‚ๅบฆ ็ฎ€ๅ• ้œ€ๅˆ›ๅปบๅก็‰‡ๆจกๆฟ

2.2 ไปฃ็ ๆžถๆž„ๅฏนๆฏ”

ๆ—งๅฎž็Žฐๆžถๆž„

DingTalkClient
  โ””โ”€โ”€ SendStreamMessage() โ†’ ่ฐƒ็”จไธๅญ˜ๅœจ็š„ API
      โ””โ”€โ”€ ๅˆ†ๅ—ๅ‘้€ๆ–‡ๆœฌๆถˆๆฏ

ๆ–ฐๅฎž็Žฐๆžถๆž„

DingTalkStreamClient (ๆ–ฐๅขž)
  โ”œโ”€โ”€ GetAccessToken() โ†’ OAuth2 ่Žทๅ–ไปค็‰Œ
  โ”œโ”€โ”€ CreateAndDeliverCard() โ†’ ๅˆ›ๅปบๅนถๆŠ•้€’ AI ๅก็‰‡
  โ”œโ”€โ”€ StreamingUpdate() โ†’ ๆตๅผๆ›ดๆ–ฐๅก็‰‡ๅ†…ๅฎน
  โ””โ”€โ”€ StreamResponse() โ†’ ๅฎšๆ—ถๆ›ดๆ–ฐ็ฎก็†

DingTalkMessageHandler (ๅขžๅผบ)
  โ”œโ”€โ”€ streamClient: *DingTalkStreamClient (ๆ–ฐๅขž)
  โ””โ”€โ”€ processWithStreamingUpdate() โ†’ ไฝฟ็”จๅฎ˜ๆ–น SDK ๅค„็†

ไธ‰ใ€ๆ ธๅฟƒๅฎž็Žฐ็ป†่Š‚

3.1 ๆตๅผๅฎขๆˆท็ซฏ - DingTalkStreamClient

ไธป่ฆๆ–นๆณ•

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 ็‰ˆๆœฌ

3.2 ๆถˆๆฏๅค„็†ๆต็จ‹

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ 1. ๆŽฅๆ”ถ้’‰้’‰ๆถˆๆฏ (HTTP Callback)                        โ”‚
โ”‚    HandleMessage() โ†’ ้ชŒ่ฏ็ญพๅ โ†’ ่งฃๅฏ†ๆถˆๆฏ               โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                 โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ 2. ่งฃๆžๆ„ๅ›พ                                             โ”‚
โ”‚    ParseIntent() โ†’ ๆญฃๅˆ™ๅŒน้… โ†’ ๆๅ–ๅ‚ๆ•ฐ                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                 โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ 3. ๅผ‚ๆญฅๅค„็† (processQueryAsync)                         โ”‚
โ”‚    โ”œโ”€ ๆฃ€ๆŸฅๆ˜ฏๅฆ้…็ฝฎ streamClient                         โ”‚
โ”‚    โ”œโ”€ ๆ˜ฏ โ†’ processWithStreamingUpdate()                 โ”‚
โ”‚    โ””โ”€ ๅฆ โ†’ ไฝฟ็”จๆ—ง็š„ SendStreamMessage()                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                 โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ 4. ๆตๅผๆ›ดๆ–ฐๅค„็† (processWithStreamingUpdate)            โ”‚
โ”‚    โ”œโ”€ ๅˆ›ๅปบๅนถๆŠ•้€’ AI ๅก็‰‡                                โ”‚
โ”‚    โ”œโ”€ ๅ‘้€ๅˆๅง‹ๆ็คบ "ๆญฃๅœจๆŸฅ่ฏข..."                         โ”‚
โ”‚    โ”œโ”€ ่ฐƒ็”จ MCP ๅทฅๅ…ทๆŸฅ่ฏข                                 โ”‚
โ”‚    โ”œโ”€ ๅˆ›ๅปบๅ†…ๅฎน channel                                  โ”‚
โ”‚    โ”œโ”€ ๆจกๆ‹Ÿๆตๅผ่พ“ๅ‡บ (ๆŒ‰่กŒๅ‘้€)                           โ”‚
โ”‚    โ””โ”€ StreamResponse() ๅฎšๆ—ถๆ›ดๆ–ฐๅก็‰‡                     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

3.3 ๅก็‰‡ๆจกๆฟ่ฆๆฑ‚

ๅฟ…้œ€็š„ JSON ็ป“ๆž„

{
  "contents": [
    {
      "type": "markdown",
      "text": "${content}",  // โ† ๅฟ…้กปไฝฟ็”จ่ฟ™ไธชๅ˜้‡ๅ
      "id": "content"        // โ† ๅฟ…้กปไฝฟ็”จ่ฟ™ไธช ID
    }
  ]
}

้‡่ฆ่ฏดๆ˜Ž:

  • ${content} ๅ˜้‡ๅๅฟ…้กปไธŽ StreamingUpdate ไธญ็š„ Key: "content" ๅŒน้…
  • ๅฆ‚ๆžœๅญ—ๆฎตๅไธๅŒ,ๆตๅผๆ›ดๆ–ฐๅฐ†ๅคฑ่ดฅ

ๅ››ใ€ๅ‘ๅŽๅ…ผๅฎนๆ€ง

4.1 ้™็บงๆœบๅˆถ

ๆ–ฐๅฎž็ŽฐๅฎŒๅ…จๅ‘ๅŽๅ…ผๅฎน:

if h.streamClient != nil {
    // ไฝฟ็”จๆ–ฐ็š„ๅฎ˜ๆ–น SDK ๆตๅผๆ›ดๆ–ฐ
    h.processWithStreamingUpdate(ctx, msg, intent, userMessage)
} else {
    // ้™็บงๅˆฐๆ—ง็š„ๅฎž็Žฐ
    h.client.SendStreamMessage(ctx, ...)
}

้™็บงๆกไปถ:

  • ๆœช้…็ฝฎ template_id
  • ๆตๅผๅฎขๆˆท็ซฏๅˆๅง‹ๅŒ–ๅคฑ่ดฅ

4.2 ้…๏ฟฝ๏ฟฝ่ฆๆฑ‚

้…็ฝฎ้กน ๆ—งๅฎž็Žฐ ๆ–ฐๅฎž็Žฐ ๅฟ…้œ€ๆ€ง
app_key โœ… โœ… ๅฟ…้œ€
app_secret โœ… โœ… ๅฟ…้œ€
agent_id โœ… โœ… ๅฟ…้œ€
template_id - โœ… ๅฏ้€‰ (ๅฏ็”จๆตๅผ่พ“ๅ‡บ)
callback.token โœ… โœ… ๅฟ…้œ€
callback.aes_key โœ… โœ… ๅฟ…้œ€

ไบ”ใ€ๆ€ง่ƒฝไผ˜ๅŒ–

5.1 Token ็ผ“ๅญ˜

type DingTalkStreamClient struct {
    tokenCache struct {
        accessToken string
        expireAt    time.Time
    }
    tokenMutex sync.RWMutex
}

ไผ˜ๅŠฟ:

  • ๅ‡ๅฐ‘ API ่ฐƒ็”จๆฌกๆ•ฐ
  • ๆๅ‰ 5 ๅˆ†้’Ÿๅˆทๆ–ฐ (้ฟๅ…่ฟ‡ๆœŸ)
  • ็บฟ็จ‹ๅฎ‰ๅ…จ (DCL ๆจกๅผ)

5.2 ๆ›ดๆ–ฐ้ข‘็އๆŽงๅˆถ

updateTicker := time.NewTicker(1500 * time.Millisecond)

่ฎพ่ฎก่€ƒ่™‘:

  • 1.5 ็ง’: ๅนณ่กกๅฎžๆ—ถๆ€งๅ’Œ API ้™ๆต
  • ๆŒ‰้œ€ๆ›ดๆ–ฐ: ไป…ๅฝ“ๅ†…ๅฎนๅ˜ๅŒ–ๆ—ถๆ‰ๆ›ดๆ–ฐ
  • ๆœ€็ปˆไฟ่ฏ: ้€š้“ๅ…ณ้—ญๆ—ถๅฟ…ๅฎšๅ‘้€ๆœ€็ปˆ็‰ˆๆœฌ

5.3 ๅ†…ๅฎนๆตๅผๆจกๆ‹Ÿ

// ๆจกๆ‹Ÿๆตๅผ่พ“ๅ‡บ:ๅฐ†็ป“ๆžœๆŒ‰่กŒๅˆ†ๆ‰นๅ‘้€
lines := strings.Split(result, "\n")
for _, line := range lines {
    contentCh <- line + "\n"
    time.Sleep(50 * time.Millisecond) // ๆ‰“ๅญ—ๆœบๆ•ˆๆžœ
}

ๅ…ญใ€้…็ฝฎ็คบไพ‹

6.1 ็Žฏๅขƒๅ˜้‡

# .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_key

6.2 config.yaml

dingtalk:
  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

ไธƒใ€ๆต‹่ฏ•้ชŒ่ฏ

7.1 ็ผ–่ฏ‘ๆต‹่ฏ•

$ go build -o bin/zenops .
โœ… ็ผ–่ฏ‘ๆˆๅŠŸ

7.2 ๅŠŸ่ƒฝๆต‹่ฏ•ๆธ…ๅ•

  • ๅˆ›ๅปบ AI ๅก็‰‡ๆจกๆฟ
  • ้…็ฝฎ template_id ็Žฏๅขƒๅ˜้‡
  • ๅฏๅŠจๆœๅŠก
  • ๅœจ็พค่Šไธญ @ๆœบๅ™จไบบ ๅ‘้€ๆŸฅ่ฏข
  • ้ชŒ่ฏๅก็‰‡ๅˆ›ๅปบๆˆๅŠŸ
  • ้ชŒ่ฏๅˆๅง‹ๆ็คบๆ˜พ็คบ
  • ้ชŒ่ฏๆตๅผๆ›ดๆ–ฐๆ•ˆๆžœ (ๆ‰“ๅญ—ๆœบๆ•ˆๆžœ)
  • ้ชŒ่ฏๆœ€็ปˆ็ป“ๆžœๆญฃ็กฎๆ˜พ็คบ
  • ๆต‹่ฏ•้”™่ฏฏๅค„็† (ๆ•…ๆ„่งฆๅ‘้”™่ฏฏ)
  • ๆต‹่ฏ•ๅ•่Šๅœบๆ™ฏ

7.3 ๆ—ฅๅฟ—้ชŒ่ฏ

ๅฏ็”จ 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

ๅ…ซใ€้—ฎ้ข˜ๆŽ’ๆŸฅ

8.1 ๅธธ่ง้”™่ฏฏ

้”™่ฏฏ 1: ๅก็‰‡ๅˆ›ๅปบๅคฑ่ดฅ

Error: failed to create and deliver card

ๅŽŸๅ› :

  • template_id ไธๆญฃ็กฎ
  • ๅก็‰‡ๆจกๆฟๆœชๅ‘ๅธƒ
  • ๅบ”็”จๆƒ้™ไธ่ถณ

่งฃๅ†ณ:

  1. ๆฃ€ๆŸฅๆจกๆฟ ID ๆ˜ฏๅฆๆญฃ็กฎ
  2. ็กฎ่ฎคๆจกๆฟๅทฒๅ‘ๅธƒๅˆฐ็”Ÿไบง็Žฏๅขƒ
  3. ๆฃ€ๆŸฅๅบ”็”จๆ˜ฏๅฆๆœ‰"็พคๆถˆๆฏๅ‘้€"ๆƒ้™

้”™่ฏฏ 2: ๆตๅผๆ›ดๆ–ฐๅคฑ่ดฅ

Error: failed to update card

ๅŽŸๅ› :

  • trackID ไธๅญ˜ๅœจ
  • ๅก็‰‡ๆœชๅˆ›ๅปบๆˆๅŠŸ
  • Access Token ่ฟ‡ๆœŸ

่งฃๅ†ณ:

  1. ็กฎไฟๅ…ˆ่ฐƒ็”จ CreateAndDeliverCard()
  2. ๆฃ€ๆŸฅ Token ็ผ“ๅญ˜้€ป่พ‘
  3. ๆŸฅ็œ‹่ฏฆ็ป†้”™่ฏฏๆ—ฅๅฟ—

้”™่ฏฏ 3: ๅญ—ๆฎตๆœชๆ›ดๆ–ฐ

ๅŽŸๅ› :

  • ๅก็‰‡ๆจกๆฟไธญๅญ—ๆฎตๅไธๆ˜ฏ content
  • ๅญ—ๆฎต ID ไธๆ˜ฏ content

่งฃๅ†ณ: ไฟฎๆ”นๅก็‰‡ๆจกๆฟ,็กฎไฟ:

{
  "text": "${content}",
  "id": "content"
}

ไนใ€ๆ€ง่ƒฝๆŒ‡ๆ ‡

9.1 API ่ฐƒ็”จๆฌกๆ•ฐ

ๆ—งๅฎž็Žฐ:

  • ้•ฟๆถˆๆฏ (3000 ๅญ—): ~3-5 ๆฌก API ่ฐƒ็”จ

ๆ–ฐๅฎž็Žฐ:

  • ๅŒๆ ทๆถˆๆฏ: 1 ๆฌกๅˆ›ๅปบ + 2-3 ๆฌกๆ›ดๆ–ฐ + 1 ๆฌกๆœ€็ปˆ = 4-5 ๆฌก

็ป“่ฎบ: API ่ฐƒ็”จๆฌกๆ•ฐ็›ธๅฝ“,ไฝ†็”จๆˆทไฝ“้ชŒๆ˜พ่‘—ๆๅ‡

9.2 ๅ“ๅบ”ๆ—ถ้—ด

  • ๅก็‰‡ๅˆ›ๅปบ: ~200ms
  • ้ฆ–ๆฌกๆ›ดๆ–ฐ: ~300ms (ๅŒ…ๅซ MCP ๆŸฅ่ฏข)
  • ๅฎšๆ—ถๆ›ดๆ–ฐ: ~100ms/ๆฌก
  • ๆ€ป่€—ๆ—ถ: ๅ–ๅ†ณไบŽ MCP ๆŸฅ่ฏขๆ—ถ้—ด

ๅใ€ๅŽ็ปญไผ˜ๅŒ–ๅปบ่ฎฎ

10.1 Stream SDK ้›†ๆˆ (ๅฏ้€‰)

ๅฝ“ๅ‰ๅฎž็Žฐไฝฟ็”จ HTTP ๅ›ž่ฐƒ,ๅฏ่ฟ›ไธ€ๆญฅๅ‡็บงๅˆฐ Stream ๆจกๅผ:

// ๆ— ้œ€ HTTP ๅ›ž่ฐƒ็š„ๅฎž็Žฐ
cli := client.NewStreamClient(client.WithAppCredential(
    client.NewAppCredentialConfig(clientID, clientSecret),
))
cli.RegisterChatBotCallbackRouter(OnMessageReceived)
cli.Start(ctx)

ไผ˜ๅŠฟ:

  • ๆ— ้œ€้…็ฝฎๅ…ฌ็ฝ‘ๅ›ž่ฐƒ URL
  • ๆ›ด็ฎ€ๅ•็š„้ƒจ็ฝฒ
  • ๆ›ดไฝŽ็š„ๅปถ่ฟŸ

10.2 ็œŸๅฎžๆตๅผ่พ“ๅ‡บ

็›ฎๅ‰ MCP ๅทฅๅ…ท่ฟ”ๅ›žๅฎŒๆ•ด็ป“ๆžœๅŽๅ†ๆจกๆ‹Ÿๆตๅผ,ๅฏๆ”น่ฟ›ไธบ:

// ๅฆ‚ๆžœ MCP ๅทฅๅ…ทๆ”ฏๆŒๆตๅผ่ฟ”ๅ›ž
resultCh := mcpServer.CallToolStream(ctx, request)
streamClient.StreamResponse(ctx, trackID, resultCh, question)

10.3 Redis ็ผ“ๅญ˜ Token

ๅฝ“ๅ‰ 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

ๅไบŒใ€ๆ€ป็ป“

ๅ…ณ้”ฎๆ”น่ฟ›

  1. โœ… ไฝฟ็”จๅฎ˜ๆ–น API: ไปŽ่‡ชๅฎšไน‰ๅฎž็Žฐๅ‡็บงๅˆฐ้’‰้’‰ๅฎ˜ๆ–น StreamingUpdate API
  2. โœ… AI ไบ’ๅŠจๅก็‰‡: ไปŽๆ™ฎ้€šๆ–‡ๆœฌๅ‡็บงๅˆฐๆ”ฏๆŒ Markdown ็š„ไบ’ๅŠจๅก็‰‡
  3. โœ… ็œŸๅฎžๆตๅผๆ•ˆๆžœ: ๅฎž็Žฐ็ฑปไผผ ChatGPT ็š„ๆ‰“ๅญ—ๆœบๆ•ˆๆžœ
  4. โœ… ๅ‘ๅŽๅ…ผๅฎน: ไฟๆŒๅฏนๆœช้…็ฝฎ template_id ็š„ๅ…ผๅฎน
  5. โœ… ๅฎŒๅ–„ๆ–‡ๆกฃ: ๆไพ›่ฏฆ็ป†็š„้…็ฝฎๅ’Œไฝฟ็”จๆŒ‡ๅ—

ไปฃ็ ่ดจ้‡

  • ๆ–ฐๅขžไปฃ็ : ~500 ่กŒ
  • ๅ•ๅ…ƒๆต‹่ฏ•: ๅพ…ๆทปๅŠ 
  • ๆ–‡ๆกฃ่ฆ†็›–: 100%
  • ็ผ–่ฏ‘็Šถๆ€: โœ… ้€š่ฟ‡
  • ๅ‘ๅŽๅ…ผๅฎน: โœ… ๅฎŒๅ…จๅ…ผๅฎน

ๅ‡็บง่ทฏๅพ„

ๅฝ“ๅ‰็‰ˆๆœฌ โ†’ ๆทปๅŠ ไพ่ต– โ†’ ้…็ฝฎๆจกๆฟ โ†’ ๅฏ็”จๆตๅผ โ†’ ๅฎŒๆˆๅ‡็บง
  (ๆ—ง)        go mod tidy   ้’‰้’‰ๅนณๅฐ     template_id    (ๆ–ฐ)

ๅ‡็บงๆ—ถ้—ด: < 30 ๅˆ†้’Ÿ (ๅŒ…ๅซๅˆ›ๅปบๅก็‰‡ๆจกๆฟ)


ๅ‡็บงๅฎŒๆˆๆ—ฅๆœŸ: 2025-12-09 ๅฎž็Žฐ่€…: Claude Code ๅฎกๆ ธ็Šถๆ€: ๅพ…ๆต‹่ฏ•้ชŒ่ฏ