Files
smartmate/backend/services/task_class/rpc/handler.go
Losita 7ed8adf8d1 Version: 0.9.72.dev.260505
后端:
1.task-class 服务边界落地
- 新增 cmd/task-class 独立进程入口,落地 services/task_class dao/rpc/sv
- 新增 gateway/client/taskclass、shared/contracts/taskclass 和 shared/ports task-class port
- 将 /api/v1/task-class/* HTTP 门面切到 task-class zrpc,gateway 只保留鉴权、幂等、参数绑定和响应透传
- 保留 task-class 迁移期直写 schedule_events / schedules 权限,维持 insert/apply 与 item 状态更新的本地事务语义
- 修复 task-class 删除已排入日程任务块时 schedules / schedule_events 的外键删除顺序
- 补充 taskClass.rpc 示例配置与阶段 5 文档基线、切流点、残留依赖和 smoke 记录
- 忽略根目录 .tmp 临时烟测产物
2026-05-05 11:24:16 +08:00

222 lines
7.8 KiB
Go

package rpc
import (
"context"
"encoding/json"
"errors"
"github.com/LoveLosita/smartflow/backend/model"
"github.com/LoveLosita/smartflow/backend/respond"
"github.com/LoveLosita/smartflow/backend/services/task_class/rpc/pb"
taskclasssv "github.com/LoveLosita/smartflow/backend/services/task_class/sv"
taskclasscontracts "github.com/LoveLosita/smartflow/backend/shared/contracts/taskclass"
)
const (
taskClassCreate = 0
taskClassUpdate = 1
)
type Handler struct {
pb.UnimplementedTaskClassServer
svc *taskclasssv.TaskClassService
}
func NewHandler(svc *taskclasssv.TaskClassService) *Handler {
return &Handler{svc: svc}
}
// Ping 供调用方在启动期确认 task-class 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) AddTaskClass(ctx context.Context, req *pb.JSONRequest) (*pb.JSONResponse, error) {
if err := h.ensureReady(req); err != nil {
return nil, err
}
var contractReq taskclasscontracts.UpsertTaskClassRequest
if err := json.Unmarshal(req.PayloadJson, &contractReq); err != nil {
return nil, grpcErrorFromServiceError(respond.WrongParamType)
}
err := h.svc.AddOrUpdateTaskClass(ctx, toModelTaskClassRequest(contractReq), contractReq.UserID, taskClassCreate, 0)
return jsonResponse(nil, err)
}
func (h *Handler) ListTaskClasses(ctx context.Context, req *pb.JSONRequest) (*pb.JSONResponse, error) {
if err := h.ensureReady(req); err != nil {
return nil, err
}
var contractReq taskclasscontracts.UserRequest
if err := json.Unmarshal(req.PayloadJson, &contractReq); err != nil {
return nil, grpcErrorFromServiceError(respond.WrongParamType)
}
data, err := h.svc.GetUserTaskClassInfos(ctx, contractReq.UserID)
return jsonResponse(data, err)
}
func (h *Handler) GetTaskClass(ctx context.Context, req *pb.JSONRequest) (*pb.JSONResponse, error) {
if err := h.ensureReady(req); err != nil {
return nil, err
}
var contractReq taskclasscontracts.GetTaskClassRequest
if err := json.Unmarshal(req.PayloadJson, &contractReq); err != nil {
return nil, grpcErrorFromServiceError(respond.WrongParamType)
}
data, err := h.svc.GetUserCompleteTaskClass(ctx, contractReq.UserID, contractReq.TaskClassID)
return jsonResponse(data, err)
}
func (h *Handler) UpdateTaskClass(ctx context.Context, req *pb.JSONRequest) (*pb.JSONResponse, error) {
if err := h.ensureReady(req); err != nil {
return nil, err
}
var contractReq taskclasscontracts.UpsertTaskClassRequest
if err := json.Unmarshal(req.PayloadJson, &contractReq); err != nil {
return nil, grpcErrorFromServiceError(respond.WrongParamType)
}
err := h.svc.AddOrUpdateTaskClass(ctx, toModelTaskClassRequest(contractReq), contractReq.UserID, taskClassUpdate, contractReq.TaskClassID)
return jsonResponse(nil, err)
}
func (h *Handler) InsertTaskClassItemIntoSchedule(ctx context.Context, req *pb.JSONRequest) (*pb.JSONResponse, error) {
if err := h.ensureReady(req); err != nil {
return nil, err
}
var contractReq taskclasscontracts.InsertTaskClassItemIntoScheduleRequest
if err := json.Unmarshal(req.PayloadJson, &contractReq); err != nil {
return nil, grpcErrorFromServiceError(respond.WrongParamType)
}
err := h.svc.AddTaskClassItemIntoSchedule(ctx, toModelInsertTaskClassItemRequest(contractReq), contractReq.UserID, contractReq.TaskItemID)
return jsonResponse(nil, err)
}
func (h *Handler) DeleteTaskClassItem(ctx context.Context, req *pb.JSONRequest) (*pb.JSONResponse, error) {
if err := h.ensureReady(req); err != nil {
return nil, err
}
var contractReq taskclasscontracts.DeleteTaskClassItemRequest
if err := json.Unmarshal(req.PayloadJson, &contractReq); err != nil {
return nil, grpcErrorFromServiceError(respond.WrongParamType)
}
err := h.svc.DeleteTaskClassItem(ctx, contractReq.UserID, contractReq.TaskItemID)
return jsonResponse(nil, err)
}
func (h *Handler) DeleteTaskClass(ctx context.Context, req *pb.JSONRequest) (*pb.JSONResponse, error) {
if err := h.ensureReady(req); err != nil {
return nil, err
}
var contractReq taskclasscontracts.DeleteTaskClassRequest
if err := json.Unmarshal(req.PayloadJson, &contractReq); err != nil {
return nil, grpcErrorFromServiceError(respond.WrongParamType)
}
err := h.svc.DeleteTaskClass(ctx, contractReq.UserID, contractReq.TaskClassID)
return jsonResponse(nil, err)
}
func (h *Handler) ApplyBatchIntoSchedule(ctx context.Context, req *pb.JSONRequest) (*pb.JSONResponse, error) {
if err := h.ensureReady(req); err != nil {
return nil, err
}
var contractReq taskclasscontracts.ApplyBatchIntoScheduleRequest
if err := json.Unmarshal(req.PayloadJson, &contractReq); err != nil {
return nil, grpcErrorFromServiceError(respond.WrongParamType)
}
err := h.svc.BatchApplyPlans(ctx, contractReq.TaskClassID, contractReq.UserID, toModelBatchRequest(contractReq))
return jsonResponse(nil, err)
}
func (h *Handler) ensureReady(req any) error {
if h == nil || h.svc == nil {
return grpcErrorFromServiceError(errors.New("task-class 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 toModelTaskClassRequest(req taskclasscontracts.UpsertTaskClassRequest) *model.UserAddTaskClassRequest {
items := make([]model.UserAddTaskClassItemRequest, 0, len(req.Items))
for _, item := range req.Items {
items = append(items, model.UserAddTaskClassItemRequest{
ID: item.ID,
Order: item.Order,
Content: item.Content,
EmbeddedTime: toModelTargetTime(item.EmbeddedTime),
})
}
return &model.UserAddTaskClassRequest{
Name: req.Name,
StartDate: req.StartDate,
EndDate: req.EndDate,
Mode: req.Mode,
SubjectType: req.SubjectType,
DifficultyLevel: req.DifficultyLevel,
CognitiveIntensity: req.CognitiveIntensity,
Config: model.UserAddTaskClassConfig{
TotalSlots: req.Config.TotalSlots,
AllowFillerCourse: req.Config.AllowFillerCourse,
Strategy: req.Config.Strategy,
ExcludedSlots: append([]int(nil), req.Config.ExcludedSlots...),
ExcludedDaysOfWeek: append([]int(nil), req.Config.ExcludedDaysOfWeek...),
},
Items: items,
}
}
func toModelTargetTime(value *taskclasscontracts.TargetTime) *model.TargetTime {
if value == nil {
return nil
}
return &model.TargetTime{
Week: value.Week,
DayOfWeek: value.DayOfWeek,
SectionFrom: value.SectionFrom,
SectionTo: value.SectionTo,
}
}
func toModelInsertTaskClassItemRequest(req taskclasscontracts.InsertTaskClassItemIntoScheduleRequest) *model.UserInsertTaskClassItemToScheduleRequest {
return &model.UserInsertTaskClassItemToScheduleRequest{
Week: req.Week,
DayOfWeek: req.DayOfWeek,
StartSection: req.StartSection,
EndSection: req.EndSection,
EmbedCourseEventID: req.EmbedCourseEventID,
}
}
func toModelBatchRequest(req taskclasscontracts.ApplyBatchIntoScheduleRequest) *model.UserInsertTaskClassItemToScheduleRequestBatch {
items := make([]model.SingleTaskClassItem, 0, len(req.Items))
for _, item := range req.Items {
items = append(items, model.SingleTaskClassItem{
TaskItemID: item.TaskItemID,
Week: item.Week,
DayOfWeek: item.DayOfWeek,
StartSection: item.StartSection,
EndSection: item.EndSection,
EmbedCourseEventID: item.EmbedCourseEventID,
})
}
return &model.UserInsertTaskClassItemToScheduleRequestBatch{
TaskClassID: req.TaskClassID,
Items: items,
}
}