重构数据引擎和报表引擎

This commit is contained in:
2026-06-11 13:06:54 +08:00
parent 285a0fc632
commit 419473f266
53 changed files with 8434 additions and 375 deletions

View File

@@ -29,7 +29,7 @@ type ApiResult struct {
type ApiClient struct {
config *PlatformConfig
client *http.Client
rateLimiter <-chan time.Time // 限流 ticker
rateLimiter *time.Ticker // 限流 ticker,可被 GC
}
// NewApiClient 创建客户端
@@ -38,14 +38,22 @@ func NewApiClient(config *PlatformConfig) *ApiClient {
if config.RequestTimeoutMs > 0 {
timeout = time.Duration(config.RequestTimeoutMs) * time.Millisecond
}
transport := &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 20,
IdleConnTimeout: 90 * time.Second,
}
ac := &ApiClient{
config: config,
client: &http.Client{Timeout: timeout},
client: &http.Client{
Timeout: timeout,
Transport: transport,
},
}
// 初始化限流
if config.RateLimitPerMinute > 0 {
interval := time.Minute / time.Duration(config.RateLimitPerMinute)
ac.rateLimiter = time.Tick(interval)
ac.rateLimiter = time.NewTicker(interval)
logrus.Infof("限流已启用: %d 次/分钟, 间隔 %v", config.RateLimitPerMinute, interval)
}
return ac
@@ -61,6 +69,13 @@ func (c *ApiClient) PostJSON(ctx context.Context, path string, body interface{})
return c.doRequest(ctx, "POST", path, body, false)
}
// Close 释放客户端资源(限流 ticker
func (c *ApiClient) Close() {
if c.rateLimiter != nil {
c.rateLimiter.Stop()
}
}
// Request 通用请求方法(支持 GET/POST支持参数在 query 或 body
func (c *ApiClient) Request(ctx context.Context, method, path string, params map[string]interface{}, paramsInQuery bool) (*ApiResult, error) {
if paramsInQuery {
@@ -99,8 +114,9 @@ func (c *ApiClient) execute(ctx context.Context, method, path string, body inter
// 限流等待
if c.rateLimiter != nil {
select {
case <-c.rateLimiter:
case <-c.rateLimiter.C:
case <-ctx.Done():
c.rateLimiter.Stop()
return nil, ctx.Err()
}
}
@@ -112,8 +128,13 @@ func (c *ApiClient) execute(ctx context.Context, method, path string, body inter
fullURL = c.applyAuthURL(fullURL)
var reqBody io.Reader
var reqBodyBytes []byte
if body != nil && !paramsInQuery {
b, _ := json.Marshal(body)
b, err := json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("JSON序列化请求体失败: %w", err)
}
reqBodyBytes = b
reqBody = bytes.NewBuffer(b)
}
@@ -133,7 +154,7 @@ func (c *ApiClient) execute(ctx context.Context, method, path string, body inter
return nil, fmt.Errorf("创建请求失败: %w", err)
}
c.applyAuthHeader(req)
c.applyAuthHeader(req, reqBodyBytes)
req.Header.Set("User-Agent", "data-engine/1.0")
if body != nil && !paramsInQuery {
req.Header.Set("Content-Type", "application/json")
@@ -145,7 +166,10 @@ func (c *ApiClient) execute(ctx context.Context, method, path string, body inter
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("读取响应体失败: %w", err)
}
result := &ApiResult{Body: respBody, DurationMs: time.Since(start).Milliseconds()}
if resp.StatusCode >= 400 {
@@ -157,7 +181,11 @@ func (c *ApiClient) execute(ctx context.Context, method, path string, body inter
// buildQueryURL 将 params 拼接到 URL 查询参数中
// 支持数组/对象类型的值自动 JSON 序列化 + URL 编码
func (c *ApiClient) buildQueryURL(rawURL string, params map[string]interface{}) string {
parsed, _ := url.Parse(rawURL)
parsed, err := url.Parse(rawURL)
if err != nil || parsed == nil {
logrus.Errorf("buildQueryURL: 解析 URL 失败: %v", err)
return rawURL
}
q := parsed.Query()
for k, v := range params {
@@ -224,7 +252,11 @@ func (c *ApiClient) applyAuthURL(rawURL string) string {
return rawURL
}
parsed, _ := url.Parse(rawURL)
parsed, err := url.Parse(rawURL)
if err != nil || parsed == nil {
logrus.Errorf("applyAuthURL: 解析 URL 失败: %v", err)
return rawURL
}
q := parsed.Query()
if tokenInQuery && token != "" {
q.Set(queryKey, token)
@@ -236,10 +268,16 @@ func (c *ApiClient) applyAuthURL(rawURL string) string {
return parsed.String()
}
func (c *ApiClient) applyAuthHeader(req *http.Request) {
func (c *ApiClient) applyAuthHeader(req *http.Request, bodyBytes []byte) {
cfg := c.config.AuthConfig
token := c.config.AccessToken
// APP_SIGNATURE 认证app-id + signature 头部(如钉钉智能薪酬)
if c.config.AuthType == "APP_SIGNATURE" {
c.applyAppSignatureAuth(req, bodyBytes)
return
}
if cfg != nil {
if tiq, _ := cfg["token_in_query"].(bool); tiq {
return
@@ -251,9 +289,9 @@ func (c *ApiClient) applyAuthHeader(req *http.Request) {
if cfg != nil {
if h, ok := cfg["header_name"].(string); ok {
f := cfg["header_format"].(string)
if f == "" {
f = "{token}"
f := "{token}"
if fv, ok2 := cfg["header_format"].(string); ok2 {
f = fv
}
req.Header.Set(h, strings.ReplaceAll(f, "{token}", token))
return
@@ -268,6 +306,73 @@ func (c *ApiClient) applyAuthHeader(req *http.Request) {
}
}
// applyAppSignatureAuth 设置 app-id + signature 认证头部
func (c *ApiClient) applyAppSignatureAuth(req *http.Request, bodyBytes []byte) {
cfg := c.config.AuthConfig
if cfg == nil {
return
}
// 1. 设置 app-id 头部
appIdHeader := "app-id"
if h, _ := cfg["app_id_header"].(string); h != "" {
appIdHeader = h
}
appId := c.config.AppKey
if appId == "" {
if aid, _ := cfg["app_id"].(string); aid != "" {
appId = aid
}
}
if appId != "" {
req.Header.Set(appIdHeader, appId)
}
// 2. 计算签名并设置 signature 头部
signHeader := "signature"
if h, _ := cfg["sign_header"].(string); h != "" {
signHeader = h
}
secret := c.config.AppSecret
signAlgo := "md5_upper_body"
if a, _ := cfg["sign_algorithm"].(string); a != "" {
signAlgo = a
}
sig := computeBodySignature(bodyBytes, secret, signAlgo)
if sig != "" {
req.Header.Set(signHeader, sig)
}
}
// computeBodySignature 计算基于请求体的签名
// 支持的算法:
// - md5_upper_body: MD5(body_string + secret) 大写(默认,钉钉智能薪酬)
// - md5_body: MD5(body_string + secret) 小写
func computeBodySignature(bodyBytes []byte, secret, algo string) string {
if secret == "" {
return ""
}
bodyStr := ""
if len(bodyBytes) > 0 {
bodyStr = string(bodyBytes)
}
switch algo {
case "md5_body", "md5_upper_body":
h := md5.Sum([]byte(bodyStr + secret))
sig := hex.EncodeToString(h[:])
if algo == "md5_upper_body" {
sig = strings.ToUpper(sig)
}
return sig
default:
logrus.Warnf("未知签名算法: %s", algo)
return ""
}
}
func generateNonce() string {
nanoPart := time.Now().UnixNano() % 1000000000000
r, _ := rand.Int(rand.Reader, big.NewInt(10000))
@@ -293,7 +398,11 @@ func (c *ApiClient) applySignature(rawURL string, body interface{}, paramsInQuer
return rawURL
}
parsed, _ := url.Parse(rawURL)
parsed, err := url.Parse(rawURL)
if err != nil || parsed == nil {
logrus.Errorf("applySignature: 解析 URL 失败: %v", err)
return rawURL
}
q := parsed.Query()
// 收集所有参数并按 key 排序