7bbc944ae2
Deploy Server / release-deploy-server (push) Successful in 51s
服务端安全加固:多维限流(按 IP/按门店)+ 敏感接口独立速率上限抵御 DDoS/刷接口; 登录暴力破解新增按来源 IP 锁定;反代后正确识别真实客户端 IP; 门店 custom_fields 轻量配置(录入默认值)透传保存。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y2Wdwo7SmgBJU37cBrkhPK
135 lines
3.8 KiB
Go
135 lines
3.8 KiB
Go
package handler
|
||
|
||
import (
|
||
"fmt"
|
||
"image"
|
||
_ "image/jpeg"
|
||
_ "image/png"
|
||
"net/http"
|
||
"os"
|
||
"path/filepath"
|
||
|
||
"github.com/disintegration/imaging"
|
||
"github.com/gin-gonic/gin"
|
||
"gorm.io/gorm"
|
||
|
||
"github.com/wangjia/jiu/backend/config"
|
||
"github.com/wangjia/jiu/backend/internal/middleware"
|
||
"github.com/wangjia/jiu/backend/internal/model"
|
||
)
|
||
|
||
type ShopHandler struct {
|
||
db *gorm.DB
|
||
}
|
||
|
||
func NewShopHandler(db *gorm.DB) *ShopHandler {
|
||
return &ShopHandler{db: db}
|
||
}
|
||
|
||
// GetInfo GET /api/v1/shop/info
|
||
func (h *ShopHandler) GetInfo(c *gin.Context) {
|
||
shopID := middleware.GetShopID(c)
|
||
var shop model.Shop
|
||
if err := h.db.First(&shop, shopID).Error; err != nil {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "shop not found"})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, shop)
|
||
}
|
||
|
||
// UpdateInfo PUT /api/v1/shop/info (admin only)
|
||
func (h *ShopHandler) UpdateInfo(c *gin.Context) {
|
||
shopID := middleware.GetShopID(c)
|
||
|
||
var req struct {
|
||
Name string `json:"name"`
|
||
Address string `json:"address"`
|
||
Phone string `json:"phone"`
|
||
ManagerName string `json:"manager_name"`
|
||
LogoURL string `json:"logo_url"`
|
||
WechatID string `json:"wechat_id"`
|
||
CustomFields map[string]interface{} `json:"custom_fields"`
|
||
}
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
updates := map[string]interface{}{
|
||
"name": req.Name,
|
||
"address": req.Address,
|
||
"phone": req.Phone,
|
||
"manager_name": req.ManagerName,
|
||
"wechat_id": req.WechatID,
|
||
}
|
||
if req.LogoURL != "" {
|
||
updates["logo_url"] = req.LogoURL
|
||
}
|
||
// custom_fields 增量 merge:保留已有键(如其它店级配置),只覆盖本次传入的键。
|
||
if req.CustomFields != nil {
|
||
var cur model.Shop
|
||
h.db.Select("custom_fields").Where("id = ?", shopID).First(&cur)
|
||
merged := model.JSON{}
|
||
for k, v := range cur.CustomFields {
|
||
merged[k] = v
|
||
}
|
||
for k, v := range req.CustomFields {
|
||
merged[k] = v
|
||
}
|
||
updates["custom_fields"] = merged
|
||
}
|
||
if err := h.db.Model(&model.Shop{}).Where("id = ?", shopID).Updates(updates).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
var shop model.Shop
|
||
h.db.First(&shop, shopID)
|
||
c.JSON(http.StatusOK, shop)
|
||
}
|
||
|
||
// UploadLogo POST /api/v1/shop/logo (admin only)
|
||
func (h *ShopHandler) UploadLogo(c *gin.Context) {
|
||
shopID := middleware.GetShopID(c)
|
||
|
||
if err := c.Request.ParseMultipartForm(2 << 20); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "文件超过 2MB 限制"})
|
||
return
|
||
}
|
||
file, _, err := c.Request.FormFile("file")
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "请上传文件(field: file)"})
|
||
return
|
||
}
|
||
defer file.Close()
|
||
|
||
img, _, err := image.Decode(file)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "仅支持 JPEG/PNG 图片"})
|
||
return
|
||
}
|
||
|
||
// 裁剪为正方形后缩放到 256×256
|
||
resized := imaging.Fill(img, 256, 256, imaging.Center, imaging.Lanczos)
|
||
|
||
subdir := filepath.Join(config.C.Storage.UploadDir, "shops", fmt.Sprintf("%d", shopID))
|
||
if err := os.MkdirAll(subdir, 0755); err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "存储目录创建失败"})
|
||
return
|
||
}
|
||
fullPath := filepath.Join(subdir, "logo.jpg")
|
||
|
||
if err := imaging.Save(resized, fullPath, imaging.JPEGQuality(90)); err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "图片保存失败"})
|
||
return
|
||
}
|
||
|
||
logoURL := fmt.Sprintf("/images/shops/%d/logo.jpg", shopID)
|
||
if err := h.db.Model(&model.Shop{}).Where("id = ?", shopID).Update("logo_url", logoURL).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{"logo_url": logoURL})
|
||
}
|