chore: 初始化 eightbf-analysis-go(对齐 Java 会商作业服务)
Go 实现 /api/v1/analysis 契约、Demo/Live Worker、DeerFlow SSE;含设计文档与冒烟脚本。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
commit
0c45b86d67
29 files changed
+3789
No files matched your search
@@ -0,0 +1,298 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"git.gozeros.com/wsk/eightbf-analysis-go/internal/domain"
|
||||
)
|
||||
|
||||
// JobRepo 会商作业表读写(eba_analysis_job)。大字段整段 JSON 文本存。
|
||||
type JobRepo struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
func NewJobRepo(db *sql.DB) *JobRepo {
|
||||
return &JobRepo{DB: db}
|
||||
}
|
||||
|
||||
// EnsureSchema 执行建表脚本(幂等;重复索引错误由调用方忽略亦可)。
|
||||
func (r *JobRepo) EnsureSchema(schemaSQL string) error {
|
||||
_, err := r.DB.Exec(schemaSQL)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *JobRepo) Insert(job *domain.AnalysisJob) error {
|
||||
now := time.Now()
|
||||
job.CreatedAt = &now
|
||||
job.UpdatedAt = &now
|
||||
_, err := r.DB.Exec(`INSERT INTO eba_analysis_job(
|
||||
id,request_id,task_id,draft_id,draft_key,status,phase,cycle,active_agent_id,
|
||||
consensus_percentage,pending_clarification,orchestration_mode,model,error_message,
|
||||
owner_account_id,agents_json,chain_json,plan_json,access_json,thread_ids_json,
|
||||
progress_json,result_json,cancel_flag,created_at,updated_at
|
||||
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,0,?,?)`,
|
||||
job.ID, nullStr(job.RequestID), nullStr(job.TaskID), nullStr(job.DraftID), nullStr(job.DraftKey),
|
||||
job.Status, nullStr(job.Phase), job.Cycle, nullStr(job.ActiveAgentID),
|
||||
job.ConsensusPercentage, nullStr(job.PendingClarification), nullStr(job.OrchestrationMode),
|
||||
nullStr(job.Model), nullStr(job.Error), nullStr(job.OwnerAccountID),
|
||||
mustJSON(job.Agents), mustJSON(job.Dialogues), mustJSON(job.Plan), mustJSON(job.Access),
|
||||
mustJSON(job.ThreadIDs), mustJSON(job.Progress), mustJSON(job.Result),
|
||||
now, now,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateProgress 全量刷新进度相关列;cancelled 时同步 cancel_flag=1。
|
||||
func (r *JobRepo) UpdateProgress(job *domain.AnalysisJob) error {
|
||||
now := time.Now()
|
||||
job.UpdatedAt = &now
|
||||
cancel := 0
|
||||
if job.Status == domain.StatusCancelled {
|
||||
cancel = 1
|
||||
}
|
||||
_, err := r.DB.Exec(`UPDATE eba_analysis_job SET status=?, phase=?, cycle=?, active_agent_id=?,
|
||||
consensus_percentage=?, pending_clarification=?, model=?, error_message=?, owner_account_id=?,
|
||||
agents_json=?, chain_json=?, plan_json=?, access_json=?, thread_ids_json=?,
|
||||
progress_json=?, result_json=?, cancel_flag=?, updated_at=? WHERE id=?`,
|
||||
job.Status, nullStr(job.Phase), job.Cycle, nullStr(job.ActiveAgentID),
|
||||
job.ConsensusPercentage, nullStr(job.PendingClarification), nullStr(job.Model),
|
||||
nullStr(job.Error), nullStr(job.OwnerAccountID),
|
||||
mustJSON(job.Agents), mustJSON(job.Dialogues), mustJSON(job.Plan), mustJSON(job.Access),
|
||||
mustJSON(job.ThreadIDs), mustJSON(job.Progress), mustJSON(job.Result),
|
||||
cancel, now, job.ID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *JobRepo) FindByID(id string) (*domain.AnalysisJob, error) {
|
||||
rows, err := r.DB.Query(`SELECT * FROM eba_analysis_job WHERE id=?`, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return nil, nil
|
||||
}
|
||||
return scanJob(rows)
|
||||
}
|
||||
|
||||
func (r *JobRepo) FindActiveByTaskID(taskID string) (*domain.AnalysisJob, error) {
|
||||
if taskID == "" {
|
||||
return nil, nil
|
||||
}
|
||||
rows, err := r.DB.Query(`SELECT * FROM eba_analysis_job WHERE task_id=? AND status IN (?,?,?)
|
||||
ORDER BY created_at DESC LIMIT 1`,
|
||||
taskID, domain.StatusQueued, domain.StatusRunning, domain.StatusAwaitingInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return nil, nil
|
||||
}
|
||||
return scanJob(rows)
|
||||
}
|
||||
|
||||
func (r *JobRepo) FindLatestByTaskID(taskID string) (*domain.AnalysisJob, error) {
|
||||
if taskID == "" {
|
||||
return nil, nil
|
||||
}
|
||||
rows, err := r.DB.Query(`SELECT * FROM eba_analysis_job WHERE task_id=? ORDER BY created_at DESC LIMIT 1`, taskID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return nil, nil
|
||||
}
|
||||
return scanJob(rows)
|
||||
}
|
||||
|
||||
func (r *JobRepo) FindActiveJobs() ([]*domain.AnalysisJob, error) {
|
||||
rows, err := r.DB.Query(`SELECT * FROM eba_analysis_job WHERE status IN (?,?,?) ORDER BY created_at ASC`,
|
||||
domain.StatusQueued, domain.StatusRunning, domain.StatusAwaitingInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return scanJobs(rows)
|
||||
}
|
||||
|
||||
func (r *JobRepo) FindQueuedJobs() ([]*domain.AnalysisJob, error) {
|
||||
rows, err := r.DB.Query(`SELECT * FROM eba_analysis_job WHERE status=? ORDER BY created_at ASC`, domain.StatusQueued)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return scanJobs(rows)
|
||||
}
|
||||
|
||||
func (r *JobRepo) MarkCancel(id string) (bool, error) {
|
||||
res, err := r.DB.Exec(`UPDATE eba_analysis_job SET cancel_flag=1, updated_at=? WHERE id=? AND status IN (?,?,?)`,
|
||||
time.Now(), id, domain.StatusQueued, domain.StatusRunning, domain.StatusAwaitingInput)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
n, _ := res.RowsAffected()
|
||||
return n > 0, nil
|
||||
}
|
||||
|
||||
func (r *JobRepo) IsCancelFlag(id string) (bool, error) {
|
||||
var v sql.NullInt64
|
||||
err := r.DB.QueryRow(`SELECT cancel_flag FROM eba_analysis_job WHERE id=?`, id).Scan(&v)
|
||||
if err == sql.ErrNoRows {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return v.Valid && v.Int64 == 1, nil
|
||||
}
|
||||
|
||||
func scanJobs(rows *sql.Rows) ([]*domain.AnalysisJob, error) {
|
||||
var out []*domain.AnalysisJob
|
||||
for rows.Next() {
|
||||
j, err := scanJob(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, j)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func scanJob(rows *sql.Rows) (*domain.AnalysisJob, error) {
|
||||
cols, err := rows.Columns()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vals := make([]interface{}, len(cols))
|
||||
ptrs := make([]interface{}, len(cols))
|
||||
for i := range vals {
|
||||
ptrs[i] = &vals[i]
|
||||
}
|
||||
if err := rows.Scan(ptrs...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := map[string]interface{}{}
|
||||
for i, c := range cols {
|
||||
m[c] = vals[i]
|
||||
}
|
||||
j := &domain.AnalysisJob{}
|
||||
j.ID = asString(m["id"])
|
||||
j.RequestID = asString(m["request_id"])
|
||||
j.TaskID = asString(m["task_id"])
|
||||
j.DraftID = asString(m["draft_id"])
|
||||
j.DraftKey = asString(m["draft_key"])
|
||||
j.Status = asString(m["status"])
|
||||
j.Phase = asString(m["phase"])
|
||||
j.Cycle = asInt(m["cycle"])
|
||||
j.ActiveAgentID = asString(m["active_agent_id"])
|
||||
j.ConsensusPercentage = asInt(m["consensus_percentage"])
|
||||
j.PendingClarification = asString(m["pending_clarification"])
|
||||
j.OrchestrationMode = asString(m["orchestration_mode"])
|
||||
j.Model = asString(m["model"])
|
||||
j.Error = asString(m["error_message"])
|
||||
j.OwnerAccountID = asString(m["owner_account_id"])
|
||||
j.Agents = readList(asString(m["agents_json"]))
|
||||
j.Dialogues = readList(asString(m["chain_json"]))
|
||||
j.Plan = readMap(asString(m["plan_json"]))
|
||||
j.Access = readMap(asString(m["access_json"]))
|
||||
j.ThreadIDs = readMap(asString(m["thread_ids_json"]))
|
||||
j.Progress = readMap(asString(m["progress_json"]))
|
||||
j.Result = readMap(asString(m["result_json"]))
|
||||
if t := asTime(m["created_at"]); t != nil {
|
||||
j.CreatedAt = t
|
||||
}
|
||||
if t := asTime(m["updated_at"]); t != nil {
|
||||
j.UpdatedAt = t
|
||||
}
|
||||
return j, nil
|
||||
}
|
||||
|
||||
func mustJSON(v interface{}) interface{} {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func readMap(jsonStr string) map[string]interface{} {
|
||||
if jsonStr == "" {
|
||||
return nil
|
||||
}
|
||||
var m map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(jsonStr), &m); err != nil {
|
||||
return nil
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func readList(jsonStr string) []map[string]interface{} {
|
||||
if jsonStr == "" {
|
||||
return []map[string]interface{}{}
|
||||
}
|
||||
var list []map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(jsonStr), &list); err != nil {
|
||||
return []map[string]interface{}{}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func nullStr(s string) interface{} {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func asString(v interface{}) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
return t
|
||||
case []byte:
|
||||
return string(t)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func asInt(v interface{}) int {
|
||||
if v == nil {
|
||||
return 0
|
||||
}
|
||||
switch t := v.(type) {
|
||||
case int64:
|
||||
return int(t)
|
||||
case int32:
|
||||
return int(t)
|
||||
case int:
|
||||
return t
|
||||
case []byte:
|
||||
var n int
|
||||
_ = json.Unmarshal(t, &n)
|
||||
return n
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func asTime(v interface{}) *time.Time {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
if t, ok := v.(time.Time); ok {
|
||||
tt := t
|
||||
return &tt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user