Files
smartmate/backend/services/course/rpc/errors.go
Losita fd327f845b 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 记录
2026-05-05 12:07:31 +08:00

85 lines
3.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package rpc
import (
"errors"
"log"
"strings"
"github.com/LoveLosita/smartflow/backend/respond"
coursesv "github.com/LoveLosita/smartflow/backend/services/course/sv"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
const courseErrorDomain = "smartflow.course"
// grpcErrorFromServiceError 负责把 course 内部错误转换为 gRPC status。
//
// 职责边界:
// 1. respond.Response 保留项目内部 status/info供 gateway 反解;
// 2. 图片解析哨兵错误转换为历史 HTTP 兼容错误码;
// 3. 未分类错误只暴露通用内部错误,详细信息留在服务日志。
func grpcErrorFromServiceError(err error) error {
if err == nil {
return nil
}
var resp respond.Response
if errors.As(err, &resp) {
return grpcErrorFromResponse(resp)
}
switch {
case errors.Is(err, coursesv.ErrCourseImageParserUnavailable):
return grpcErrorFromResponse(respond.Response{Status: "50003", Info: "course image parser is not configured"})
case errors.Is(err, coursesv.ErrCourseImageTooLarge):
return grpcErrorFromResponse(respond.Response{Status: "40064", Info: "course image too large"})
case errors.Is(err, coursesv.ErrCourseImageUnsupportedMIME):
return grpcErrorFromResponse(respond.Response{Status: "40065", Info: "unsupported course image format"})
case errors.Is(err, coursesv.ErrCourseImageEmpty):
return grpcErrorFromResponse(respond.Response{Status: "40066", Info: "course image is empty"})
}
log.Printf("course rpc internal error: %v", err)
return status.Error(codes.Internal, "course service internal error")
}
func grpcErrorFromResponse(resp respond.Response) error {
code := grpcCodeFromRespondStatus(resp.Status)
message := strings.TrimSpace(resp.Info)
if message == "" {
message = strings.TrimSpace(resp.Status)
}
st := status.New(code, message)
detail := &errdetails.ErrorInfo{
Domain: courseErrorDomain,
Reason: resp.Status,
Metadata: map[string]string{
"info": resp.Info,
},
}
withDetails, err := st.WithDetails(detail)
if err != nil {
return st.Err()
}
return withDetails.Err()
}
func grpcCodeFromRespondStatus(statusValue string) codes.Code {
switch strings.TrimSpace(statusValue) {
case respond.MissingToken.Status, respond.InvalidToken.Status, respond.InvalidClaims.Status,
respond.ErrUnauthorized.Status, respond.WrongTokenType.Status, respond.UserLoggedOut.Status:
return codes.Unauthenticated
case respond.CourseNotBelongToUser.Status:
return codes.PermissionDenied
case respond.MissingParam.Status, respond.WrongParamType.Status, respond.ParamTooLong.Status,
respond.WrongUserID.Status, respond.WrongCourseID.Status, respond.WrongCourseInfo.Status,
respond.InsertCourseTwice.Status, respond.ScheduleConflict.Status, respond.InvalidSectionNumber.Status,
respond.InvalidWeekOrDayOfWeek.Status, respond.InvalidSectionRange.Status,
respond.TimeOutOfRangeOfThisSemester.Status:
return codes.InvalidArgument
}
if strings.HasPrefix(strings.TrimSpace(statusValue), "5") {
return codes.Internal
}
return codes.InvalidArgument
}