后端:
1.阶段 6 CP4/CP5 目录收口与共享边界纯化
- 将 backend 根目录收口为 services、client、gateway、cmd、shared 五个一级目录
- 收拢 bootstrap、inits、infra/kafka、infra/outbox、conv、respond、pkg、middleware,移除根目录旧实现与空目录
- 将 utils 下沉到 services/userauth/internal/auth,将 logic 下沉到 services/schedule/core/planning
- 将迁移期 runtime 桥接实现统一收拢到 services/runtime/{conv,dao,eventsvc,model},删除 shared/legacy 与未再被 import 的旧 service 实现
- 将 gateway/shared/respond 收口为 HTTP/Gin 错误写回适配,shared/respond 仅保留共享错误语义与状态映射
- 将 HTTP IdempotencyMiddleware 与 RateLimitMiddleware 收口到 gateway/middleware
- 将 GormCachePlugin 下沉到 shared/infra/gormcache,将共享 RateLimiter 下沉到 shared/infra/ratelimit,将 agent token budget 下沉到 services/agent/shared
- 删除 InitEino 兼容壳,收缩 cmd/internal/coreinit 仅保留旧组合壳残留域初始化语义
- 更新微服务迁移计划与桌面 checklist,补齐 CP4/CP5 当前切流点、目录终态与验证结果
- 完成 go test ./...、git diff --check 与最终真实 smoke;health、register/login、task/create+get、schedule/today、task-class/list、memory/items、agent chat/meta/timeline/context-stats 全部 200,SSE 合并结果为 CP5_OK 且 [DONE] 只有 1 个
122 lines
4.7 KiB
Go
122 lines
4.7 KiB
Go
package sv
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"errors"
|
||
"strings"
|
||
"time"
|
||
|
||
agenttools "github.com/LoveLosita/smartflow/backend/services/agent/tools"
|
||
"github.com/LoveLosita/smartflow/backend/services/runtime/model"
|
||
taskclasscontracts "github.com/LoveLosita/smartflow/backend/shared/contracts/taskclass"
|
||
)
|
||
|
||
const taskClassUpsertRPCTimeout = 6 * time.Second
|
||
|
||
// TaskClassUpsertRPCClient 描述 agent 写入任务类时依赖的 task-class RPC 最小能力。
|
||
//
|
||
// 职责边界:
|
||
// 1. 只覆盖 upsert_task_class 工具需要的新增/更新能力;
|
||
// 2. 不暴露 task-class DAO、事务细节或 schedule 迁移期直写语义;
|
||
// 3. 读取型能力由 schedule provider 的独立接口承载,避免接口膨胀。
|
||
type TaskClassUpsertRPCClient interface {
|
||
AddTaskClass(ctx context.Context, req taskclasscontracts.UpsertTaskClassRequest) (json.RawMessage, error)
|
||
UpdateTaskClass(ctx context.Context, req taskclasscontracts.UpsertTaskClassRequest) (json.RawMessage, error)
|
||
}
|
||
|
||
type taskClassRPCUpsertAdapter struct {
|
||
client TaskClassUpsertRPCClient
|
||
}
|
||
|
||
// NewTaskClassRPCUpsertFunc 把 task-class zrpc client 适配成 agent 工具写入函数。
|
||
//
|
||
// 职责边界:
|
||
// 1. 只替换 agent upsert_task_class 的 DAO 直连路径;
|
||
// 2. 入参仍复用 agent 工具层已标准化的 UserAddTaskClassRequest;
|
||
// 3. client 为空时返回会失败的闭包,让工具层保留既有错误包装语义。
|
||
func NewTaskClassRPCUpsertFunc(client TaskClassUpsertRPCClient) func(userID int, input agenttools.TaskClassUpsertInput) (agenttools.TaskClassUpsertPersistResult, error) {
|
||
adapter := &taskClassRPCUpsertAdapter{client: client}
|
||
return adapter.UpsertTaskClass
|
||
}
|
||
|
||
// UpsertTaskClass 通过 task-class zrpc 新增或更新任务类,并返回稳定 task_class_id。
|
||
func (a *taskClassRPCUpsertAdapter) UpsertTaskClass(userID int, input agenttools.TaskClassUpsertInput) (agenttools.TaskClassUpsertPersistResult, error) {
|
||
if a == nil || a.client == nil {
|
||
return agenttools.TaskClassUpsertPersistResult{}, errors.New("task-class rpc client is nil")
|
||
}
|
||
req := taskClassUpsertInputToContract(userID, input)
|
||
|
||
ctx, cancel := context.WithTimeout(context.Background(), taskClassUpsertRPCTimeout)
|
||
defer cancel()
|
||
|
||
var raw json.RawMessage
|
||
var err error
|
||
created := input.ID == 0
|
||
// 调用目的:把 agent 工具产出的任务类写入 task-class 服务,避免 agent 继续直连 task_classes/task_items。
|
||
if created {
|
||
raw, err = a.client.AddTaskClass(ctx, req)
|
||
} else {
|
||
raw, err = a.client.UpdateTaskClass(ctx, req)
|
||
}
|
||
if err != nil {
|
||
return agenttools.TaskClassUpsertPersistResult{}, err
|
||
}
|
||
|
||
var resp taskclasscontracts.UpsertTaskClassResponse
|
||
if err := json.Unmarshal(raw, &resp); err != nil {
|
||
return agenttools.TaskClassUpsertPersistResult{}, err
|
||
}
|
||
if resp.TaskClassID <= 0 {
|
||
return agenttools.TaskClassUpsertPersistResult{}, errors.New("task-class rpc upsert returned invalid task_class_id")
|
||
}
|
||
return agenttools.TaskClassUpsertPersistResult{
|
||
TaskClassID: resp.TaskClassID,
|
||
Created: resp.Created,
|
||
}, nil
|
||
}
|
||
|
||
func taskClassUpsertInputToContract(userID int, input agenttools.TaskClassUpsertInput) taskclasscontracts.UpsertTaskClassRequest {
|
||
req := input.Request
|
||
items := make([]taskclasscontracts.UpsertTaskClassItemConfig, 0, len(req.Items))
|
||
for _, item := range req.Items {
|
||
items = append(items, taskclasscontracts.UpsertTaskClassItemConfig{
|
||
ID: item.ID,
|
||
Order: item.Order,
|
||
Content: strings.TrimSpace(item.Content),
|
||
EmbeddedTime: toTaskClassContractTargetTime(item.EmbeddedTime),
|
||
})
|
||
}
|
||
return taskclasscontracts.UpsertTaskClassRequest{
|
||
UserID: userID,
|
||
TaskClassID: input.ID,
|
||
Name: strings.TrimSpace(req.Name),
|
||
StartDate: strings.TrimSpace(req.StartDate),
|
||
EndDate: strings.TrimSpace(req.EndDate),
|
||
Mode: strings.TrimSpace(req.Mode),
|
||
SubjectType: strings.TrimSpace(req.SubjectType),
|
||
DifficultyLevel: strings.TrimSpace(req.DifficultyLevel),
|
||
CognitiveIntensity: strings.TrimSpace(req.CognitiveIntensity),
|
||
Config: taskclasscontracts.UpsertTaskClassConfig{
|
||
TotalSlots: req.Config.TotalSlots,
|
||
AllowFillerCourse: req.Config.AllowFillerCourse,
|
||
Strategy: strings.TrimSpace(req.Config.Strategy),
|
||
ExcludedSlots: append([]int(nil), req.Config.ExcludedSlots...),
|
||
ExcludedDaysOfWeek: append([]int(nil), req.Config.ExcludedDaysOfWeek...),
|
||
},
|
||
Items: items,
|
||
}
|
||
}
|
||
|
||
func toTaskClassContractTargetTime(value *model.TargetTime) *taskclasscontracts.TargetTime {
|
||
if value == nil {
|
||
return nil
|
||
}
|
||
return &taskclasscontracts.TargetTime{
|
||
Week: value.Week,
|
||
DayOfWeek: value.DayOfWeek,
|
||
SectionFrom: value.SectionFrom,
|
||
SectionTo: value.SectionTo,
|
||
}
|
||
}
|