Version: 0.9.73.dev.260505
后端: 1.阶段 5 course 服务边界落地 - 新增 cmd/course 独立进程入口,落地 services/course dao/rpc/sv - 新增 gateway/client/course、shared/contracts/course 和 shared/ports course port - 将 /api/v1/course/* HTTP 门面切到 course zrpc,gateway 只保留鉴权、限流、幂等、文件读取和响应透传 - 保留 course 迁移期直写 schedule_events / schedules 权限,维持课程导入两个表同事务写入语义 - 为 course parse-image 补 bytes RPC 契约和 gRPC 消息大小配置,兼容课表图片上传 - 补充 course.rpc 示例配置与阶段 5 文档基线、切流点、残留依赖和 smoke 记录
This commit is contained in:
141
backend/services/course/rpc/handler.go
Normal file
141
backend/services/course/rpc/handler.go
Normal file
@@ -0,0 +1,141 @@
|
||||
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/course/rpc/pb"
|
||||
coursesv "github.com/LoveLosita/smartflow/backend/services/course/sv"
|
||||
coursecontracts "github.com/LoveLosita/smartflow/backend/shared/contracts/course"
|
||||
)
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user