增加精确修复IM账号工具

This commit is contained in:
zhx 2026-07-13 23:23:43 +08:00
parent cfba26a2f3
commit 5a674a8c98
2 changed files with 143 additions and 0 deletions

View File

@ -0,0 +1,119 @@
// Command import-im-accounts performs an explicit, bounded Tencent IM account
// repair before replaying private notices that failed with invalid identifier.
package main
import (
"context"
"crypto/sha256"
"flag"
"fmt"
"os"
"sort"
"strconv"
"strings"
"time"
"hyapp/pkg/tencentim"
"hyapp/services/notice-service/internal/config"
)
const executeConfirmation = "IMPORT_EXACT_TENCENT_IM_ACCOUNTS"
type userIDFlags []int64
func (values *userIDFlags) String() string {
parts := make([]string, 0, len(*values))
for _, value := range *values {
parts = append(parts, strconv.FormatInt(value, 10))
}
return strings.Join(parts, ",")
}
func (values *userIDFlags) Set(raw string) error {
value, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 64)
if err != nil || value <= 0 {
return fmt.Errorf("user-id must be a positive int64")
}
*values = append(*values, value)
return nil
}
func main() {
configPath := flag.String("config", "services/notice-service/configs/config.yaml", "notice-service config containing Tencent IM credentials")
execute := flag.Bool("execute", false, "perform the exact account imports; default is dry-run")
confirmation := flag.String("confirm", "", "required execution confirmation")
timeout := flag.Duration("timeout", 30*time.Second, "total import timeout")
var requested userIDFlags
flag.Var(&requested, "user-id", "exact internal user_id to import; repeat for multiple users")
flag.Parse()
userIDs, err := normalizeUserIDs(requested)
if err != nil {
fatal(err)
}
if *timeout <= 0 || *timeout > 5*time.Minute {
fatal(fmt.Errorf("timeout must be within (0,5m]"))
}
if *execute && strings.TrimSpace(*confirmation) != executeConfirmation {
fatal(fmt.Errorf("--execute requires --confirm=%s", executeConfirmation))
}
fingerprint := userIDsSHA256(userIDs)
if !*execute {
fmt.Printf("mode=dry-run users=%d user_ids_sha256=%s\n", len(userIDs), fingerprint)
return
}
cfg, err := config.Load(strings.TrimSpace(*configPath))
if err != nil {
fatal(err)
}
if !cfg.TencentIM.Enabled {
fatal(fmt.Errorf("tencent_im.enabled is required"))
}
client, err := tencentim.NewRESTClient(cfg.TencentIM.RESTConfig())
if err != nil {
fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
defer cancel()
for _, userID := range userIDs {
// Account import is idempotent in the shared REST client: Tencent's
// already-exists response is success, while transport and other API errors
// stop the bounded batch before the notice replay is attempted.
if err := client.ImportAccount(ctx, tencentim.AccountProfile{UserID: userID}); err != nil {
fatal(fmt.Errorf("import user index %d: %w", sort.Search(len(userIDs), func(index int) bool { return userIDs[index] >= userID })+1, err))
}
}
fmt.Printf("mode=execute imported=%d user_ids_sha256=%s\n", len(userIDs), fingerprint)
}
func normalizeUserIDs(values []int64) ([]int64, error) {
if len(values) == 0 || len(values) > 100 {
return nil, fmt.Errorf("user-id count must be between 1 and 100")
}
normalized := append([]int64(nil), values...)
sort.Slice(normalized, func(i, j int) bool { return normalized[i] < normalized[j] })
result := normalized[:0]
for _, value := range normalized {
if value <= 0 {
return nil, fmt.Errorf("user-id must be a positive int64")
}
if len(result) == 0 || result[len(result)-1] != value {
result = append(result, value)
}
}
return result, nil
}
func userIDsSHA256(values []int64) string {
parts := make([]string, 0, len(values))
for _, value := range values {
parts = append(parts, strconv.FormatInt(value, 10))
}
return fmt.Sprintf("%x", sha256.Sum256([]byte(strings.Join(parts, "\n"))))
}
func fatal(err error) {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}

View File

@ -0,0 +1,24 @@
package main
import "testing"
func TestNormalizeUserIDsSortsAndDeduplicatesExactSet(t *testing.T) {
got, err := normalizeUserIDs([]int64{42, 7, 42})
if err != nil {
t.Fatalf("normalizeUserIDs failed: %v", err)
}
if len(got) != 2 || got[0] != 7 || got[1] != 42 {
t.Fatalf("normalized IDs mismatch: %+v", got)
}
if fingerprint := userIDsSHA256(got); fingerprint != userIDsSHA256([]int64{7, 42}) {
t.Fatalf("fingerprint must be stable, got %s", fingerprint)
}
}
func TestNormalizeUserIDsRejectsUnsafeSets(t *testing.T) {
for _, values := range [][]int64{nil, {0}, {-1}} {
if _, err := normalizeUserIDs(values); err == nil {
t.Fatalf("unsafe user IDs must fail: %+v", values)
}
}
}