后端:
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 个
142 lines
4.6 KiB
Go
142 lines
4.6 KiB
Go
package rpc
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
|
|
"github.com/LoveLosita/smartflow/backend/services/course/rpc/pb"
|
|
coursesv "github.com/LoveLosita/smartflow/backend/services/course/sv"
|
|
"github.com/LoveLosita/smartflow/backend/services/runtime/model"
|
|
coursecontracts "github.com/LoveLosita/smartflow/backend/shared/contracts/course"
|
|
"github.com/LoveLosita/smartflow/backend/shared/respond"
|
|
)
|
|
|
|
type Handler struct {
|
|
pb.UnimplementedCourseServer
|
|
svc *coursesv.CourseService
|
|
}
|
|
|
|
func NewHandler(svc *coursesv.CourseService) *Handler {
|
|
return &Handler{svc: svc}
|
|
}
|
|
|
|
// Ping 供调用方在启动期确认 course zrpc 已可用。
|
|
func (h *Handler) Ping(ctx context.Context, req *pb.StatusResponse) (*pb.StatusResponse, error) {
|
|
if err := h.ensureReady(req); err != nil {
|
|
return nil, err
|
|
}
|
|
return &pb.StatusResponse{}, nil
|
|
}
|
|
|
|
func (h *Handler) ValidateCourse(ctx context.Context, req *pb.JSONRequest) (*pb.JSONResponse, error) {
|
|
if err := h.ensureReady(req); err != nil {
|
|
return nil, err
|
|
}
|
|
var contractReq coursecontracts.UserCheckCourseRequest
|
|
if err := json.Unmarshal(req.PayloadJson, &contractReq); err != nil {
|
|
return nil, grpcErrorFromServiceError(respond.WrongParamType)
|
|
}
|
|
if !coursesv.CheckSingleCourse(toModelCheckCourseRequest(contractReq)) {
|
|
return nil, grpcErrorFromServiceError(respond.WrongCourseInfo)
|
|
}
|
|
return jsonResponse(nil, nil)
|
|
}
|
|
|
|
func (h *Handler) ImportCourses(ctx context.Context, req *pb.JSONRequest) (*pb.JSONResponse, error) {
|
|
if err := h.ensureReady(req); err != nil {
|
|
return nil, err
|
|
}
|
|
var contractReq coursecontracts.UserImportCoursesRequest
|
|
if err := json.Unmarshal(req.PayloadJson, &contractReq); err != nil {
|
|
return nil, grpcErrorFromServiceError(respond.WrongParamType)
|
|
}
|
|
conflicts, err := h.svc.AddUserCourses(ctx, toModelImportCoursesRequest(contractReq), contractReq.UserID)
|
|
if errors.Is(err, respond.ScheduleConflict) {
|
|
rawConflicts, marshalErr := json.Marshal(conflicts)
|
|
if marshalErr != nil {
|
|
return nil, grpcErrorFromServiceError(marshalErr)
|
|
}
|
|
return jsonResponse(coursecontracts.ImportCoursesResult{
|
|
Conflict: true,
|
|
Conflicts: rawConflicts,
|
|
}, nil)
|
|
}
|
|
return jsonResponse(coursecontracts.ImportCoursesResult{Conflict: false}, err)
|
|
}
|
|
|
|
func (h *Handler) ParseCourseImage(ctx context.Context, req *pb.CourseImageRequest) (*pb.JSONResponse, error) {
|
|
if err := h.ensureReady(req); err != nil {
|
|
return nil, err
|
|
}
|
|
draft, err := h.svc.ParseCourseTableImage(ctx, model.CourseImageParseRequest{
|
|
Filename: req.Filename,
|
|
MIMEType: req.MimeType,
|
|
ImageBytes: req.ImageBytes,
|
|
})
|
|
return jsonResponse(draft, err)
|
|
}
|
|
|
|
func (h *Handler) ensureReady(req any) error {
|
|
if h == nil || h.svc == nil {
|
|
return grpcErrorFromServiceError(errors.New("course service dependency not initialized"))
|
|
}
|
|
if req == nil {
|
|
return grpcErrorFromServiceError(respond.MissingParam)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func jsonResponse(value any, err error) (*pb.JSONResponse, error) {
|
|
if err != nil {
|
|
return nil, grpcErrorFromServiceError(err)
|
|
}
|
|
raw, err := json.Marshal(value)
|
|
if err != nil {
|
|
return nil, grpcErrorFromServiceError(err)
|
|
}
|
|
return &pb.JSONResponse{DataJson: raw}, nil
|
|
}
|
|
|
|
func toModelImportCoursesRequest(req coursecontracts.UserImportCoursesRequest) model.UserImportCoursesRequest {
|
|
courses := make([]model.UserCheckCourseRequest, 0, len(req.Courses))
|
|
for _, course := range req.Courses {
|
|
courses = append(courses, toModelCheckCourseRequest(course))
|
|
}
|
|
return model.UserImportCoursesRequest{Courses: courses}
|
|
}
|
|
|
|
func toModelCheckCourseRequest(req coursecontracts.UserCheckCourseRequest) model.UserCheckCourseRequest {
|
|
arrangements := make([]struct {
|
|
StartWeek int `json:"start_week"`
|
|
EndWeek int `json:"end_week"`
|
|
DayOfWeek int `json:"day_of_week"`
|
|
StartSection int `json:"start_section"`
|
|
EndSection int `json:"end_section"`
|
|
WeekType string `json:"week_type"`
|
|
}, 0, len(req.Arrangements))
|
|
for _, arrangement := range req.Arrangements {
|
|
arrangements = append(arrangements, struct {
|
|
StartWeek int `json:"start_week"`
|
|
EndWeek int `json:"end_week"`
|
|
DayOfWeek int `json:"day_of_week"`
|
|
StartSection int `json:"start_section"`
|
|
EndSection int `json:"end_section"`
|
|
WeekType string `json:"week_type"`
|
|
}{
|
|
StartWeek: arrangement.StartWeek,
|
|
EndWeek: arrangement.EndWeek,
|
|
DayOfWeek: arrangement.DayOfWeek,
|
|
StartSection: arrangement.StartSection,
|
|
EndSection: arrangement.EndSection,
|
|
WeekType: arrangement.WeekType,
|
|
})
|
|
}
|
|
return model.UserCheckCourseRequest{
|
|
CourseName: req.CourseName,
|
|
Location: req.Location,
|
|
IsAllowTasks: req.IsAllowTasks,
|
|
Arrangements: arrangements,
|
|
}
|
|
}
|