package upload import ( "context" "encoding/base64" "fmt" "os" "path/filepath" "strings" "qnc-server/app/main/api/internal/svc" "qnc-server/app/main/api/internal/types" "qnc-server/common/xerr" "github.com/google/uuid" "github.com/pkg/errors" "github.com/zeromicro/go-zero/core/logx" ) const uploadImageDir = "uploads/images" type UploadImageLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewUploadImageLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UploadImageLogic { return &UploadImageLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *UploadImageLogic) UploadImage(req *types.UploadImageReq) (*types.UploadImageResp, error) { raw := strings.TrimSpace(req.ImageBase64) if raw == "" { return nil, errors.Wrapf(xerr.NewErrMsg("image_base64 不能为空"), "") } if idx := strings.Index(raw, ","); idx >= 0 { raw = raw[idx+1:] } data, err := base64.StdEncoding.DecodeString(raw) if err != nil { return nil, errors.Wrapf(xerr.NewErrMsg("图片 Base64 解析失败"), "") } if len(data) == 0 { return nil, errors.Wrapf(xerr.NewErrMsg("图片内容为空"), "") } if len(data) > 5*1024*1024 { return nil, errors.Wrapf(xerr.NewErrMsg("图片不能超过 5MB"), "") } ext := detectImageExt(data) fileName := fmt.Sprintf("%s%s", uuid.NewString(), ext) dir := uploadImageDir if err := os.MkdirAll(dir, 0o755); err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "创建上传目录失败: %v", err) } fullPath := filepath.Join(dir, fileName) if err := os.WriteFile(fullPath, data, 0o644); err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "保存图片失败: %v", err) } base := publicAPIBase(l.svcCtx.Config.Promotion.OfficialDomain) url := fmt.Sprintf("%s/api/v1/upload/file/%s", base, fileName) return &types.UploadImageResp{Url: url}, nil } func detectImageExt(data []byte) string { if len(data) >= 3 && data[0] == 0xFF && data[1] == 0xD8 && data[2] == 0xFF { return ".jpg" } if len(data) >= 8 && string(data[0:8]) == "\x89PNG\r\n\x1a\n" { return ".png" } if len(data) >= 6 && string(data[0:6]) == "GIF87a" || string(data[0:6]) == "GIF89a" { return ".gif" } if len(data) >= 12 && string(data[8:12]) == "WEBP" { return ".webp" } return ".jpg" } func publicAPIBase(officialDomain string) string { base := strings.TrimRight(officialDomain, "/") if base != "" { return base } return "http://127.0.0.1:8888" }