2026-05-29 19:56:14 +08:00

99 lines
2.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package teamsalarysettlement
import (
"context"
"log/slog"
"strings"
"sync"
"time"
)
const autoRunnerInterval = time.Hour
// AutoRunner 是 admin-server 内的轻量自动结算循环;真正的幂等不靠 runner而靠 wallet 结算进度表。
type AutoRunner struct {
service *Service
appCode string
worker string
cancel context.CancelFunc
wg sync.WaitGroup
}
// NewAutoRunner 只保存运行参数,不立刻启动 goroutine便于 main 按 jobs.enabled 控制生命周期。
func NewAutoRunner(service *Service, appCode string, worker string) *AutoRunner {
return &AutoRunner{
service: service,
appCode: normalizeAppCode(appCode),
worker: strings.TrimSpace(worker),
}
}
// Start 启动后台循环;重复调用不创建第二个 worker避免同进程内重复扫描。
func (r *AutoRunner) Start() {
if r == nil || r.service == nil || r.cancel != nil {
return
}
ctx, cancel := context.WithCancel(context.Background())
r.cancel = cancel
r.wg.Add(1)
go r.loop(ctx)
}
// Close 停止后台循环并等待退出,保证 admin-server 优雅关闭时不会截断正在执行的事务。
func (r *AutoRunner) Close() {
if r == nil || r.cancel == nil {
return
}
r.cancel()
r.wg.Wait()
r.cancel = nil
}
// loop 启动后先立即检查一次,再按小时检查;是否真正发薪由 RunAutomaticDue 按日期判断。
func (r *AutoRunner) loop(ctx context.Context) {
defer r.wg.Done()
r.runOnce(ctx, time.Now())
ticker := time.NewTicker(autoRunnerInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case now := <-ticker.C:
r.runOnce(ctx, now)
}
}
}
// runOnce 给单次自动结算设置超时,避免数据库异常时 goroutine 永久卡住。
func (r *AutoRunner) runOnce(parent context.Context, now time.Time) {
ctx, cancel := context.WithTimeout(parent, 10*time.Minute)
defer cancel()
// 多节点同时触发时不单独抢锁;结算进度行和 command_id 唯一索引会保证同一周期同一用户只发一次。
result, err := r.service.RunAutomaticDue(ctx, r.appCode, now)
if err != nil {
slog.Error("team_salary_auto_settlement_failed", "worker", r.worker, "error", err)
return
}
if !result.Due {
return
}
slog.Info("team_salary_auto_settlement_completed",
"worker", r.worker,
"cycle_key", result.CycleKey,
"bd_success", result.Results[policyTypeBD].SuccessCount,
"bd_skipped", result.Results[policyTypeBD].SkippedCount,
"admin_success", result.Results[policyTypeAdmin].SuccessCount,
"admin_skipped", result.Results[policyTypeAdmin].SkippedCount,
)
}
// normalizeAppCode 兜底默认应用编码,保证自动任务和后台手动入口都使用同一 app 维度。
func normalizeAppCode(value string) string {
value = strings.ToLower(strings.TrimSpace(value))
if value == "" {
return "lalu"
}
return value
}