feat: add toolbox query, upload module, update config and gitignore
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
47
app/main/api/internal/logic/toolbox/toolboxquerylogic.go
Normal file
47
app/main/api/internal/logic/toolbox/toolboxquerylogic.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/app/main/api/internal/types"
|
||||
"qnc-server/common/xerr"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type ToolboxQueryLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewToolboxQueryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ToolboxQueryLogic {
|
||||
return &ToolboxQueryLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ToolboxQueryLogic) ToolboxQuery(req *types.ToolboxQueryReq) (*types.ToolboxQueryResp, error) {
|
||||
if req.ToolKey == "" {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("tool_key 不能为空"), "")
|
||||
}
|
||||
if l.svcCtx.ToolboxService == nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "工具箱服务未初始化")
|
||||
}
|
||||
params := req.Params
|
||||
if params == nil {
|
||||
params = map[string]interface{}{}
|
||||
}
|
||||
result, err := l.svcCtx.ToolboxService.Query(l.ctx, req.ToolKey, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &types.ToolboxQueryResp{
|
||||
ToolKey: req.ToolKey,
|
||||
Result: result,
|
||||
}, nil
|
||||
}
|
||||
67
app/main/api/internal/logic/upload/serveuploadlogic.go
Normal file
67
app/main/api/internal/logic/upload/serveuploadlogic.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package upload
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"qnc-server/app/main/api/internal/svc"
|
||||
"qnc-server/common/xerr"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type ServeUploadLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewServeUploadLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ServeUploadLogic {
|
||||
return &ServeUploadLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ServeUploadLogic) ServeUpload(name string, w http.ResponseWriter) error {
|
||||
name = filepath.Base(strings.TrimSpace(name))
|
||||
if name == "" || name == "." || name == ".." || !isSafeUploadFileName(name) {
|
||||
return errors.Wrapf(xerr.NewErrMsg("无效的文件名"), "")
|
||||
}
|
||||
fullPath := filepath.Join(uploadImageDir, name)
|
||||
data, err := os.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return errors.Wrapf(xerr.NewErrMsg("文件不存在"), "")
|
||||
}
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "读取文件失败: %v", err)
|
||||
}
|
||||
w.Header().Set("Cache-Control", "public, max-age=86400")
|
||||
switch filepath.Ext(name) {
|
||||
case ".png":
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
case ".gif":
|
||||
w.Header().Set("Content-Type", "image/gif")
|
||||
case ".webp":
|
||||
w.Header().Set("Content-Type", "image/webp")
|
||||
default:
|
||||
w.Header().Set("Content-Type", "image/jpeg")
|
||||
}
|
||||
_, err = w.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
func isSafeUploadFileName(name string) bool {
|
||||
for _, c := range name {
|
||||
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-' {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
93
app/main/api/internal/logic/upload/uploadimagelogic.go
Normal file
93
app/main/api/internal/logic/upload/uploadimagelogic.go
Normal file
@@ -0,0 +1,93 @@
|
||||
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"
|
||||
}
|
||||
Reference in New Issue
Block a user