feat: raw API update README.

This commit is contained in:
fantiu committed 2026-04-27 17:08:32 +08:00
1 parent 592749c986
commit e06b1d68ff
7 files changed
+67 -70

No files matched your search

+8 -8
View File
@@ -289,8 +289,8 @@ dws auth login --client-id <APP_KEY> --client-secret <APP_SECRET>
# === api.dingtalk.com ===
# Get current user info
dws api GET /v1.0/contact/users/me
# List all enterprise apps
dws api GET /v1.0/microApp/allApps
# Search users (POST + JSON body)
dws api POST /v1.0/contact/users/search \
@@ -301,16 +301,16 @@ dws api POST /v1.0/contact/users/search \
# Get user details (use --base-url to specify domain)
dws api POST /topapi/v2/user/get \
--base-url https://oapi.dingtalk.com \
--data '{"userid":"manager123"}'
--data '{"userid":"<USER_ID>"}'
# Or use the full URL directly
dws api POST https://oapi.dingtalk.com/topapi/v2/user/get \
--data '{"userid":"manager123"}'
--data '{"userid":"<USER_ID>"}'
# === General ===
dws api GET /v1.0/attendance/groups --page-all # auto-paginate
dws api GET /v1.0/contact/users/me --dry-run # preview request
dws api GET /v1.0/contact/users/me --jq '.nick' # jq filtering
dws api GET /v1.0/microApp/allApps --page-all # auto-paginate
dws api GET /v1.0/microApp/allApps --dry-run # preview request
dws api GET /v1.0/microApp/allApps --jq '.agentId' # jq filtering
```
| Feature | Details |
@@ -318,7 +318,7 @@ dws api GET /v1.0/contact/users/me --jq '.nick' # jq filtering
| Dual-form auto-detection | Automatically selects api.dingtalk.com (header auth) or oapi.dingtalk.com (query-param auth) based on URL |
| Automatic token management | App-level accessToken is fetched on first call, cached while valid, auto-refreshed on expiry |
| Domain allowlist | Only `api.dingtalk.com` and `oapi.dingtalk.com` permitted — prevents token leakage |
| Auto-pagination | `--page-all` iterates all pages; `--page-limit` caps the maximum |
| Auto-pagination | `--page-all` iterates all pages. `--page-limit` caps the maximum (default 10, set to 0 for unlimited, hard cap at 500 to prevent infinite loops) |
</details>
+9 -9
View File
@@ -281,7 +281,7 @@ curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace
`dws api` 让你直接调用任意钉钉 OpenAPI,无需 SDK,Token 自动获取和刷新。
> **前置条件**:必须使用自有应用凭证登录(见[自建应用模式](#开始使用))。通过 MCP 默认凭证登录的加密 token 不支持 raw API 调用。
> **前置条件**:必须使用自有应用凭证登录(见[自建应用模式](#开始使用))。通过 MCP 默认凭证登录 不支持 raw API 调用。
```bash
# 登录(仅首次)
@@ -289,8 +289,8 @@ dws auth login --client-id <APP_KEY> --client-secret <APP_SECRET>
# === api.dingtalk.com ===
# 获取当前用户信息
dws api GET /v1.0/contact/users/me
# 获取企业所有应用列表
dws api GET /v1.0/microApp/allApps
# 搜索用户 (POST + JSON body)
dws api POST /v1.0/contact/users/search \
@@ -301,16 +301,16 @@ dws api POST /v1.0/contact/users/search \
# 获取用户详情(使用 --base-url 指定域名)
dws api POST /topapi/v2/user/get \
--base-url https://oapi.dingtalk.com \
--data '{"userid":"manager123"}'
--data '{"userid":"<USER_ID>"}'
# 也可以直接使用完整 URL
dws api POST https://oapi.dingtalk.com/topapi/v2/user/get \
--data '{"userid":"manager123"}'
--data '{"userid":"<USER_ID>"}'
# === 通用功能 ===
dws api GET /v1.0/attendance/groups --page-all # 自动翻页
dws api GET /v1.0/contact/users/me --dry-run # 预览请求
dws api GET /v1.0/contact/users/me --jq '.nick' # jq 过滤
dws api GET /v1.0/microApp/allApps --page-all # 自动翻页
dws api GET /v1.0/microApp/allApps --dry-run # 预览请求
dws api GET /v1.0/microApp/allApps --jq '.agentId' # jq 过滤
```
| 特性 | 说明 |
@@ -318,7 +318,7 @@ dws api GET /v1.0/contact/users/me --jq '.nick' # jq 过滤
| 双形态自动识别 | 根据 URL 自动选择 api.dingtalk.com(Header 认证)或 oapi.dingtalk.com(Query 参数认证) |
| Token 自动管理 | 首次调用自动获取应用级 accessToken,有效期内缓存,过期自动刷新 |
| 域名白名单 | 仅允许 `api.dingtalk.com` 和 `oapi.dingtalk.com`,防止 Token 泄露 |
| 自动分页 | `--page-all` 自动遍历所有分页,`--page-limit` 控制上限 |
| 自动分页 | `--page-all` 自动遍历所有分页。`--page-limit` 控制翻页上限(默认 10,设为 0 不限制,硬上限 500 防止死循环) |
</details>
+6 -6
View File
@@ -45,13 +45,13 @@ func newAPICommand(flags *GlobalFlags) *cobra.Command {
cmd := &cobra.Command{
Use: "api <METHOD> <PATH> [flags]",
Short: "调用钉钉 OpenAPI (Raw HTTP)",
Long: `直接调用钉钉 OpenAPI,支持新版和旧版两种 API 形态。
Long: `直接调用钉钉 OpenAPI,支持 api.dingtalk.com 和 oapi.dingtalk.com 两个域名。
新版 API (api.dingtalk.com):
api.dingtalk.com:
Token 通过 HTTP Header (x-acs-dingtalk-access-token) 传递。
路径格式: /v1.0/xxx 或 /v2.0/xxx
旧版 API (oapi.dingtalk.com):
oapi.dingtalk.com:
Token 通过 URL 查询参数 (access_token) 传递。
路径格式: /topapi/v2/xxx 或完整 URL https://oapi.dingtalk.com/topapi/...
@@ -59,7 +59,7 @@ func newAPICommand(flags *GlobalFlags) *cobra.Command {
通过 MCP 默认凭证登录获取的加密 token 不支持 raw API 调用。
示例:
# === 新版 API (api.dingtalk.com) ===
# === api.dingtalk.com ===
# 获取当前用户信息
dws api GET /v1.0/contact/users/me
@@ -72,9 +72,9 @@ func newAPICommand(flags *GlobalFlags) *cobra.Command {
dws api POST /v1.0/calendar/users/me/calendars/primary/events \
--data '{"summary":"Team Meeting","start":{"dateTime":"2026-01-01T10:00:00+08:00"}}'
# === 旧版 API (oapi.dingtalk.com) ===
# === oapi.dingtalk.com ===
# 获取用户详情 (旧版,使用 --base-url)
# 获取用户详情 (使用 --base-url)
dws api POST /topapi/v2/user/get \
--base-url https://oapi.dingtalk.com \
--data '{"userid":"manager123"}'
+24 -27
View File
@@ -15,9 +15,6 @@ package app
import (
"context"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/executor"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/authretry"
)
// authRetryingKey marks a context that has already attempted one
@@ -41,12 +38,12 @@ func IsAuthRetrying(ctx context.Context) bool {
// withAuthRetrying returns a child context flagged as "already retried once"
// so the runner does not enter an infinite refresh loop if the second attempt
// also returns AuthRefreshRequired.
func withAuthRetrying(ctx context.Context) context.Context {
if ctx == nil {
ctx = context.Background()
}
return context.WithValue(ctx, authRetryingKey, true)
}
// func withAuthRetrying(ctx context.Context) context.Context {
// if ctx == nil {
// ctx = context.Background()
// }
// return context.WithValue(ctx, authRetryingKey, true)
// }
// handleAuthRefreshRequired performs a one-shot ForceRefresh using the active
// configDir and re-runs the invocation through the supplied runner. It must
@@ -63,21 +60,21 @@ func withAuthRetrying(ctx context.Context) context.Context {
// re-runs the invocation with withAuthRetrying applied so a second
// refresh request from the overlay degrades gracefully to "show the
// original error".
func handleAuthRefreshRequired(
ctx context.Context,
r executor.Runner,
invocation executor.Invocation,
refresh *authretry.AuthRefreshRequired,
) (executor.Result, error) {
if refresh == nil {
return executor.Result{}, nil
}
if IsAuthRetrying(ctx) {
return executor.Result{}, refresh.Cause
}
if _, err := ForceRefreshAccessToken(ctx, defaultConfigDir()); err != nil {
return executor.Result{}, refresh.Cause
}
ResetRuntimeTokenCache()
return r.Run(withAuthRetrying(ctx), invocation)
}
// func handleAuthRefreshRequired(
// ctx context.Context,
// r executor.Runner,
// invocation executor.Invocation,
// refresh *authretry.AuthRefreshRequired,
// ) (executor.Result, error) {
// if refresh == nil {
// return executor.Result{}, nil
// }
// if IsAuthRetrying(ctx) {
// return executor.Result{}, refresh.Cause
// }
// if _, err := ForceRefreshAccessToken(ctx, defaultConfigDir()); err != nil {
// return executor.Result{}, refresh.Cause
// }
// ResetRuntimeTokenCache()
// return r.Run(withAuthRetrying(ctx), invocation)
// }
+6 -6
View File
@@ -430,9 +430,9 @@ func printPerfReportSummary(w io.Writer, report *PerfReport) {
fmt.Fprintf(w, " %-25s %dms (框架开销 %dms)\n", "总耗时", report.TotalMs, report.OverheadMs)
}
func formatLocalTime(t time.Time) string {
if t.IsZero() {
return ""
}
return t.Local().Format("2006-01-02 15:04")
}
// func formatLocalTime(t time.Time) string {
// if t.IsZero() {
// return ""
// }
// return t.Local().Format("2006-01-02 15:04")
// }
+3 -3
View File
@@ -297,9 +297,9 @@ func getRuntimeCredentials() (clientID, clientSecret string) {
}
// getEnvClientID returns the environment variable client ID if set.
func getEnvClientID() string {
return os.Getenv("DWS_CLIENT_ID")
}
// func getEnvClientID() string {
// return os.Getenv("DWS_CLIENT_ID")
// }
// getDefaultConfigDir returns the default configuration directory.
// Priority: DWS_CONFIG_DIR env var > ~/.dws
+11 -11
View File
@@ -573,15 +573,15 @@ func TestSyncSkills(t *testing.T) {
}
}
func contains(s, substr string) bool {
return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsSubstring(s, substr))
}
// func contains(s, substr string) bool {
// return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsSubstring(s, substr))
// }
func containsSubstring(s, sub string) bool {
for i := 0; i+len(sub) <= len(s); i++ {
if s[i:i+len(sub)] == sub {
return true
}
}
return false
}
// func containsSubstring(s, sub string) bool {
// for i := 0; i+len(sub) <= len(s); i++ {
// if s[i:i+len(sub)] == sub {
// return true
// }
// }
// return false
// }