diff --git a/backend/cmd/backfill-pinyin/main.go b/backend/cmd/backfill-pinyin/main.go new file mode 100644 index 0000000..1f6b948 --- /dev/null +++ b/backend/cmd/backfill-pinyin/main.go @@ -0,0 +1,57 @@ +// backfill-pinyin is a one-time migration tool that fills name_pinyin and +// name_initials for products that have empty values. +// Usage: go run ./cmd/backfill-pinyin +package main + +import ( + "log" + "strings" + + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/logger" + + "github.com/spf13/viper" + "github.com/wangjia/jiu/backend/internal/model" + "github.com/wangjia/jiu/backend/internal/util" +) + +func main() { + viper.SetConfigName("config") + viper.SetConfigType("yaml") + viper.AddConfigPath(".") + viper.AddConfigPath("./config") + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.AutomaticEnv() + _ = viper.BindEnv("database.dsn", "DATABASE_DSN") + if err := viper.ReadInConfig(); err != nil { + log.Println("[config] no config file, relying on env vars") + } + + dsn := viper.GetString("database.dsn") + if dsn == "" { + log.Fatal("DATABASE_DSN is required") + } + + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Silent), + }) + if err != nil { + log.Fatalf("failed to connect database: %v", err) + } + + var products []model.Product + db.Where("name_pinyin = '' OR name_pinyin IS NULL").Find(&products) + if len(products) == 0 { + log.Println("No products to backfill.") + return + } + for i := range products { + full, initials := util.ToPinyin(products[i].Name) + db.Model(&products[i]).Updates(map[string]interface{}{ + "name_pinyin": full, + "name_initials": initials, + }) + } + log.Printf("backfill-pinyin: updated %d products", len(products)) +} diff --git a/backend/config/config.go b/backend/config/config.go index 55ed34a..94d8ee5 100644 --- a/backend/config/config.go +++ b/backend/config/config.go @@ -22,7 +22,9 @@ type ServerConfig struct { } type DatabaseConfig struct { - DSN string `mapstructure:"dsn"` + DSN string `mapstructure:"dsn"` + MaxIdleConns int `mapstructure:"max_idle_conns"` + MaxOpenConns int `mapstructure:"max_open_conns"` } type JWTConfig struct { @@ -71,6 +73,8 @@ func Load() { viper.SetDefault("server.cors_origin", "*") viper.SetDefault("jwt.access_expire_min", 60) viper.SetDefault("jwt.refresh_expire_h", 168) // 7天 + viper.SetDefault("database.max_idle_conns", 10) + viper.SetDefault("database.max_open_conns", 100) viper.SetDefault("storage.upload_dir", "./uploads/images") viper.SetDefault("storage.base_url", "http://localhost:8080/images") viper.SetDefault("storage.public_url", "http://localhost:8081") diff --git a/backend/internal/handler/auth.go b/backend/internal/handler/auth.go index 01bc017..e7d89b9 100644 --- a/backend/internal/handler/auth.go +++ b/backend/internal/handler/auth.go @@ -5,6 +5,7 @@ import ( "github.com/gin-gonic/gin" "github.com/wangjia/jiu/backend/internal/service" + "github.com/wangjia/jiu/backend/internal/util" ) type AuthHandler struct { @@ -63,7 +64,7 @@ func (h *AuthHandler) Register(c *gin.Context) { return } - c.JSON(http.StatusOK, gin.H{"data": result}) + util.RespondSuccess(c, result) } // Refresh POST /api/v1/auth/refresh @@ -82,5 +83,5 @@ func (h *AuthHandler) Refresh(c *gin.Context) { return } - c.JSON(http.StatusOK, gin.H{"data": pair}) + util.RespondSuccess(c, pair) } diff --git a/backend/internal/handler/finance.go b/backend/internal/handler/finance.go index 25c939a..1a0ea3f 100644 --- a/backend/internal/handler/finance.go +++ b/backend/internal/handler/finance.go @@ -188,7 +188,7 @@ func (h *FinanceHandler) Summary(c *gin.Context) { ORDER BY total_amount DESC `, shopID).Scan(&rows) - c.JSON(http.StatusOK, gin.H{"data": rows}) + util.RespondSuccess(c, rows) } // partnerLastBalance 查询该往来单位最后一条财务记录的余额 diff --git a/backend/internal/handler/inventory.go b/backend/internal/handler/inventory.go index 1e6bcb0..0b117f1 100644 --- a/backend/internal/handler/inventory.go +++ b/backend/internal/handler/inventory.go @@ -178,7 +178,7 @@ func (h *InventoryHandler) CreateCheck(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusCreated, gin.H{"data": req}) + util.RespondCreated(c, req) } // GetCheck GET /api/v1/inventory/checks/:id @@ -191,7 +191,7 @@ func (h *InventoryHandler) GetCheck(c *gin.Context) { c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) return } - c.JSON(http.StatusOK, gin.H{"data": check}) + util.RespondSuccess(c, check) } // CompleteCheck PUT /api/v1/inventory/checks/:id/complete diff --git a/backend/internal/handler/license.go b/backend/internal/handler/license.go index 52d3f93..bc4be82 100644 --- a/backend/internal/handler/license.go +++ b/backend/internal/handler/license.go @@ -6,6 +6,7 @@ import ( "github.com/gin-gonic/gin" "github.com/wangjia/jiu/backend/internal/middleware" "github.com/wangjia/jiu/backend/internal/service" + "github.com/wangjia/jiu/backend/internal/util" ) type LicenseHandler struct { @@ -35,7 +36,7 @@ func (h *LicenseHandler) Activate(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusOK, gin.H{"data": lic}) + util.RespondSuccess(c, lic) } // Verify GET /api/v1/license/verify @@ -52,7 +53,7 @@ func (h *LicenseHandler) Verify(c *gin.Context) { c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusOK, gin.H{"data": lic}) + util.RespondSuccess(c, lic) } // Info GET /api/v1/license/info — 当前门店授权概况 @@ -60,7 +61,7 @@ func (h *LicenseHandler) Info(c *gin.Context) { shopID := middleware.GetShopID(c) lic, err := h.svc.ShopInfo(shopID) if err != nil { - c.JSON(http.StatusOK, gin.H{"data": nil}) + util.RespondSuccess(c, nil) return } @@ -90,7 +91,7 @@ func (h *LicenseHandler) Devices(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusOK, gin.H{"data": devs}) + util.RespondSuccess(c, devs) } // Deactivate POST /api/v1/license/deactivate diff --git a/backend/internal/handler/number_rule.go b/backend/internal/handler/number_rule.go index 57092d7..77d787e 100644 --- a/backend/internal/handler/number_rule.go +++ b/backend/internal/handler/number_rule.go @@ -8,6 +8,7 @@ import ( "github.com/wangjia/jiu/backend/internal/middleware" "github.com/wangjia/jiu/backend/internal/model" + "github.com/wangjia/jiu/backend/internal/util" ) type NumberRuleHandler struct { @@ -43,7 +44,7 @@ func (h *NumberRuleHandler) List(c *gin.Context) { } } - c.JSON(http.StatusOK, gin.H{"data": rules}) + util.RespondSuccess(c, rules) } // Update PUT /api/v1/number-rules/:id @@ -82,5 +83,5 @@ func (h *NumberRuleHandler) Update(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusOK, gin.H{"data": rule}) + util.RespondSuccess(c, rule) } diff --git a/backend/internal/handler/partner.go b/backend/internal/handler/partner.go index e4e5309..afac2d7 100644 --- a/backend/internal/handler/partner.go +++ b/backend/internal/handler/partner.go @@ -70,7 +70,7 @@ func (h *PartnerHandler) Create(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusCreated, gin.H{"data": p}) + util.RespondCreated(c, p) } func (h *PartnerHandler) Update(c *gin.Context) { @@ -113,7 +113,7 @@ func (h *PartnerHandler) Update(c *gin.Context) { return } h.db.Where("id = ? AND shop_id = ?", p.ID, shopID).First(&p) - c.JSON(http.StatusOK, gin.H{"data": p}) + util.RespondSuccess(c, p) } func (h *PartnerHandler) Delete(c *gin.Context) { diff --git a/backend/internal/handler/product.go b/backend/internal/handler/product.go index 26c6145..715299e 100644 --- a/backend/internal/handler/product.go +++ b/backend/internal/handler/product.go @@ -104,7 +104,7 @@ func (h *ProductHandler) Create(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": createErr.Error()}) return } - c.JSON(http.StatusCreated, gin.H{"data": product}) + util.RespondCreated(c, product) } // Update PUT /api/v1/products/:id @@ -155,7 +155,7 @@ func (h *ProductHandler) Update(c *gin.Context) { // 重新读取完整数据返回 h.db.Preload("Category").First(&product, product.ID) - c.JSON(http.StatusOK, gin.H{"data": product}) + util.RespondSuccess(c, product) } // Detail GET /api/v1/products/:id/detail @@ -178,7 +178,7 @@ func (h *ProductHandler) Detail(c *gin.Context) { h.db.Model(&product).Update("public_id", product.PublicID) } - c.JSON(http.StatusOK, gin.H{"data": product}) + util.RespondSuccess(c, product) } // QRCode GET /api/v1/products/:id/qrcode @@ -248,7 +248,7 @@ func (h *ProductHandler) FindOrCreate(c *gin.Context) { if len(attrUpdates) > 0 { h.db.Model(&product).Updates(attrUpdates) } - c.JSON(http.StatusOK, gin.H{"data": product}) + util.RespondSuccess(c, product) return } if !errors.Is(err, gorm.ErrRecordNotFound) { @@ -277,13 +277,13 @@ func (h *ProductHandler) FindOrCreate(c *gin.Context) { // Race condition: try to find the record created by another request if h.db.Where("shop_id = ? AND name = ? AND series = ? AND spec = ? AND deleted_at IS NULL", shopID, req.Name, req.Series, req.Spec).First(&product).Error == nil { - c.JSON(http.StatusOK, gin.H{"data": product}) + util.RespondSuccess(c, product) return } c.JSON(http.StatusInternalServerError, gin.H{"error": createErr.Error()}) return } - c.JSON(http.StatusCreated, gin.H{"data": product}) + util.RespondCreated(c, product) } // Delete DELETE /api/v1/products/:id (软删除) diff --git a/backend/internal/handler/product_attr.go b/backend/internal/handler/product_attr.go index bce31ad..96fcf71 100644 --- a/backend/internal/handler/product_attr.go +++ b/backend/internal/handler/product_attr.go @@ -8,6 +8,7 @@ import ( "github.com/wangjia/jiu/backend/internal/middleware" "github.com/wangjia/jiu/backend/internal/model" + "github.com/wangjia/jiu/backend/internal/util" ) // ProductAttrHandler 处理商品属性字典(产地/保质期/储存方式/描述文档) @@ -25,7 +26,7 @@ func (h *ProductAttrHandler) ListOrigins(c *gin.Context) { shopID := middleware.GetShopID(c) items := make([]model.ProductOriginOption, 0) h.db.Where("shop_id = ?", shopID).Order("id ASC").Find(&items) - c.JSON(http.StatusOK, gin.H{"data": items}) + util.RespondSuccess(c, items) } func (h *ProductAttrHandler) CreateOrigin(c *gin.Context) { @@ -49,7 +50,7 @@ func (h *ProductAttrHandler) CreateOrigin(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusCreated, gin.H{"data": item}) + util.RespondCreated(c, item) } func (h *ProductAttrHandler) UpdateOrigin(c *gin.Context) { @@ -71,7 +72,7 @@ func (h *ProductAttrHandler) UpdateOrigin(c *gin.Context) { h.db.Model(&item).Where("shop_id = ?", shopID).Updates(map[string]interface{}{ "code": req.Code, "name": req.Name, "remark": req.Remark, }) - c.JSON(http.StatusOK, gin.H{"data": item}) + util.RespondSuccess(c, item) } func (h *ProductAttrHandler) DeleteOrigin(c *gin.Context) { @@ -86,7 +87,7 @@ func (h *ProductAttrHandler) ListShelfLives(c *gin.Context) { shopID := middleware.GetShopID(c) items := make([]model.ProductShelfLifeOption, 0) h.db.Where("shop_id = ?", shopID).Order("id ASC").Find(&items) - c.JSON(http.StatusOK, gin.H{"data": items}) + util.RespondSuccess(c, items) } func (h *ProductAttrHandler) CreateShelfLife(c *gin.Context) { @@ -110,7 +111,7 @@ func (h *ProductAttrHandler) CreateShelfLife(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusCreated, gin.H{"data": item}) + util.RespondCreated(c, item) } func (h *ProductAttrHandler) UpdateShelfLife(c *gin.Context) { @@ -132,7 +133,7 @@ func (h *ProductAttrHandler) UpdateShelfLife(c *gin.Context) { h.db.Model(&item).Where("shop_id = ?", shopID).Updates(map[string]interface{}{ "code": req.Code, "name": req.Name, "remark": req.Remark, }) - c.JSON(http.StatusOK, gin.H{"data": item}) + util.RespondSuccess(c, item) } func (h *ProductAttrHandler) DeleteShelfLife(c *gin.Context) { @@ -147,7 +148,7 @@ func (h *ProductAttrHandler) ListStorages(c *gin.Context) { shopID := middleware.GetShopID(c) items := make([]model.ProductStorageOption, 0) h.db.Where("shop_id = ?", shopID).Order("id ASC").Find(&items) - c.JSON(http.StatusOK, gin.H{"data": items}) + util.RespondSuccess(c, items) } func (h *ProductAttrHandler) CreateStorage(c *gin.Context) { @@ -171,7 +172,7 @@ func (h *ProductAttrHandler) CreateStorage(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusCreated, gin.H{"data": item}) + util.RespondCreated(c, item) } func (h *ProductAttrHandler) UpdateStorage(c *gin.Context) { @@ -193,7 +194,7 @@ func (h *ProductAttrHandler) UpdateStorage(c *gin.Context) { h.db.Model(&item).Where("shop_id = ?", shopID).Updates(map[string]interface{}{ "code": req.Code, "name": req.Name, "remark": req.Remark, }) - c.JSON(http.StatusOK, gin.H{"data": item}) + util.RespondSuccess(c, item) } func (h *ProductAttrHandler) DeleteStorage(c *gin.Context) { @@ -208,7 +209,7 @@ func (h *ProductAttrHandler) ListDescriptionDocs(c *gin.Context) { shopID := middleware.GetShopID(c) items := make([]model.ProductDescriptionDoc, 0) h.db.Where("shop_id = ?", shopID).Order("id ASC").Find(&items) - c.JSON(http.StatusOK, gin.H{"data": items}) + util.RespondSuccess(c, items) } func (h *ProductAttrHandler) CreateDescriptionDoc(c *gin.Context) { @@ -232,7 +233,7 @@ func (h *ProductAttrHandler) CreateDescriptionDoc(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusCreated, gin.H{"data": item}) + util.RespondCreated(c, item) } func (h *ProductAttrHandler) UpdateDescriptionDoc(c *gin.Context) { @@ -254,7 +255,7 @@ func (h *ProductAttrHandler) UpdateDescriptionDoc(c *gin.Context) { h.db.Model(&item).Where("shop_id = ?", shopID).Updates(map[string]interface{}{ "title": req.Title, "content": req.Content, "remark": req.Remark, }) - c.JSON(http.StatusOK, gin.H{"data": item}) + util.RespondSuccess(c, item) } func (h *ProductAttrHandler) DeleteDescriptionDoc(c *gin.Context) { diff --git a/backend/internal/handler/product_image.go b/backend/internal/handler/product_image.go index 933cb63..9e145e5 100644 --- a/backend/internal/handler/product_image.go +++ b/backend/internal/handler/product_image.go @@ -18,6 +18,7 @@ import ( "github.com/wangjia/jiu/backend/config" "github.com/wangjia/jiu/backend/internal/middleware" "github.com/wangjia/jiu/backend/internal/model" + "github.com/wangjia/jiu/backend/internal/util" ) type ProductImageHandler struct { @@ -105,7 +106,7 @@ func (h *ProductImageHandler) Upload(c *gin.Context) { return } - c.JSON(http.StatusCreated, gin.H{"data": pi}) + util.RespondCreated(c, pi) } // Delete DELETE /api/v1/products/:id/images/:image_id diff --git a/backend/internal/handler/product_option.go b/backend/internal/handler/product_option.go index 2d564f3..63993d8 100644 --- a/backend/internal/handler/product_option.go +++ b/backend/internal/handler/product_option.go @@ -8,6 +8,7 @@ import ( "github.com/wangjia/jiu/backend/internal/middleware" "github.com/wangjia/jiu/backend/internal/model" + "github.com/wangjia/jiu/backend/internal/util" ) type ProductOptionHandler struct { @@ -24,7 +25,7 @@ func (h *ProductOptionHandler) ListNames(c *gin.Context) { shopID := middleware.GetShopID(c) items := make([]model.ProductNameOption, 0) h.db.Where("shop_id = ?", shopID).Order("id ASC").Find(&items) - c.JSON(http.StatusOK, gin.H{"data": items}) + util.RespondSuccess(c, items) } func (h *ProductOptionHandler) CreateName(c *gin.Context) { @@ -48,7 +49,7 @@ func (h *ProductOptionHandler) CreateName(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusCreated, gin.H{"data": item}) + util.RespondCreated(c, item) } func (h *ProductOptionHandler) UpdateName(c *gin.Context) { @@ -70,7 +71,7 @@ func (h *ProductOptionHandler) UpdateName(c *gin.Context) { h.db.Model(&item).Where("shop_id = ?", shopID).Updates(map[string]interface{}{ "code": req.Code, "name": req.Name, "remark": req.Remark, }) - c.JSON(http.StatusOK, gin.H{"data": item}) + util.RespondSuccess(c, item) } func (h *ProductOptionHandler) DeleteName(c *gin.Context) { @@ -85,7 +86,7 @@ func (h *ProductOptionHandler) ListSeries(c *gin.Context) { shopID := middleware.GetShopID(c) items := make([]model.ProductSeriesOption, 0) h.db.Where("shop_id = ?", shopID).Order("id ASC").Find(&items) - c.JSON(http.StatusOK, gin.H{"data": items}) + util.RespondSuccess(c, items) } func (h *ProductOptionHandler) CreateSeries(c *gin.Context) { @@ -109,7 +110,7 @@ func (h *ProductOptionHandler) CreateSeries(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusCreated, gin.H{"data": item}) + util.RespondCreated(c, item) } func (h *ProductOptionHandler) UpdateSeries(c *gin.Context) { @@ -131,7 +132,7 @@ func (h *ProductOptionHandler) UpdateSeries(c *gin.Context) { h.db.Model(&item).Where("shop_id = ?", shopID).Updates(map[string]interface{}{ "code": req.Code, "name": req.Name, "remark": req.Remark, }) - c.JSON(http.StatusOK, gin.H{"data": item}) + util.RespondSuccess(c, item) } func (h *ProductOptionHandler) DeleteSeries(c *gin.Context) { @@ -146,7 +147,7 @@ func (h *ProductOptionHandler) ListSpecs(c *gin.Context) { shopID := middleware.GetShopID(c) items := make([]model.ProductSpecOption, 0) h.db.Where("shop_id = ?", shopID).Order("id ASC").Find(&items) - c.JSON(http.StatusOK, gin.H{"data": items}) + util.RespondSuccess(c, items) } func (h *ProductOptionHandler) CreateSpec(c *gin.Context) { @@ -172,7 +173,7 @@ func (h *ProductOptionHandler) CreateSpec(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusCreated, gin.H{"data": item}) + util.RespondCreated(c, item) } func (h *ProductOptionHandler) UpdateSpec(c *gin.Context) { @@ -195,7 +196,7 @@ func (h *ProductOptionHandler) UpdateSpec(c *gin.Context) { h.db.Model(&item).Where("shop_id = ?", shopID).Updates(map[string]interface{}{ "code": req.Code, "name": req.Name, "quantity": req.Quantity, "remark": req.Remark, }) - c.JSON(http.StatusOK, gin.H{"data": item}) + util.RespondSuccess(c, item) } func (h *ProductOptionHandler) DeleteSpec(c *gin.Context) { diff --git a/backend/internal/handler/stock_in.go b/backend/internal/handler/stock_in.go index 24d3c3c..94bc670 100644 --- a/backend/internal/handler/stock_in.go +++ b/backend/internal/handler/stock_in.go @@ -72,7 +72,7 @@ func (h *StockInHandler) Get(c *gin.Context) { c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) return } - c.JSON(http.StatusOK, gin.H{"data": order}) + util.RespondSuccess(c, order) } // Create POST /api/v1/stock-in/orders @@ -114,7 +114,7 @@ func (h *StockInHandler) Create(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusCreated, gin.H{"data": req}) + util.RespondCreated(c, req) } // Update PUT /api/v1/stock-in/orders/:id (只允许草稿状态) diff --git a/backend/internal/handler/stock_out.go b/backend/internal/handler/stock_out.go index dfb2197..a6470a8 100644 --- a/backend/internal/handler/stock_out.go +++ b/backend/internal/handler/stock_out.go @@ -63,7 +63,7 @@ func (h *StockOutHandler) Get(c *gin.Context) { c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) return } - c.JSON(http.StatusOK, gin.H{"data": order}) + util.RespondSuccess(c, order) } @@ -110,7 +110,7 @@ func (h *StockOutHandler) Create(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusCreated, gin.H{"data": req}) + util.RespondCreated(c, req) } // Update PUT /api/v1/stock-out/orders/:id (只允许草稿状态) diff --git a/backend/internal/handler/user.go b/backend/internal/handler/user.go index 211d801..5f4ab7c 100644 --- a/backend/internal/handler/user.go +++ b/backend/internal/handler/user.go @@ -9,6 +9,7 @@ import ( "github.com/wangjia/jiu/backend/internal/middleware" "github.com/wangjia/jiu/backend/internal/model" + "github.com/wangjia/jiu/backend/internal/util" ) type UserHandler struct { @@ -25,7 +26,7 @@ func (h *UserHandler) List(c *gin.Context) { users := make([]model.User, 0) h.db.Where("shop_id = ? AND deleted_at IS NULL", shopID). Order("id ASC").Find(&users) - c.JSON(http.StatusOK, gin.H{"data": users}) + util.RespondSuccess(c, users) } // Create POST /api/v1/users @@ -64,7 +65,7 @@ func (h *UserHandler) Create(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": "用户名已存在"}) return } - c.JSON(http.StatusCreated, gin.H{"data": u}) + util.RespondCreated(c, u) } // Update PUT /api/v1/users/:id @@ -100,7 +101,7 @@ func (h *UserHandler) Update(c *gin.Context) { h.db.Model(&u).Where("shop_id = ?", shopID).Updates(updates) } h.db.Where("id = ? AND shop_id = ?", u.ID, shopID).First(&u) - c.JSON(http.StatusOK, gin.H{"data": u}) + util.RespondSuccess(c, u) } // ResetPassword PUT /api/v1/users/:id/reset-password diff --git a/backend/internal/handler/warehouse.go b/backend/internal/handler/warehouse.go index b0b739e..0139089 100644 --- a/backend/internal/handler/warehouse.go +++ b/backend/internal/handler/warehouse.go @@ -8,6 +8,7 @@ import ( "github.com/wangjia/jiu/backend/internal/middleware" "github.com/wangjia/jiu/backend/internal/model" + "github.com/wangjia/jiu/backend/internal/util" ) type WarehouseHandler struct { @@ -22,7 +23,7 @@ func (h *WarehouseHandler) List(c *gin.Context) { shopID := middleware.GetShopID(c) warehouses := make([]model.Warehouse, 0) h.db.Where("shop_id = ? AND deleted_at IS NULL", shopID).Find(&warehouses) - c.JSON(http.StatusOK, gin.H{"data": warehouses}) + util.RespondSuccess(c, warehouses) } func (h *WarehouseHandler) Create(c *gin.Context) { @@ -34,7 +35,7 @@ func (h *WarehouseHandler) Create(c *gin.Context) { } w.ShopID = shopID h.db.Create(&w) - c.JSON(http.StatusCreated, gin.H{"data": w}) + util.RespondCreated(c, w) } func (h *WarehouseHandler) Update(c *gin.Context) { @@ -63,7 +64,7 @@ func (h *WarehouseHandler) Update(c *gin.Context) { return } h.db.Where("id = ? AND shop_id = ?", w.ID, shopID).First(&w) - c.JSON(http.StatusOK, gin.H{"data": w}) + util.RespondSuccess(c, w) } func (h *WarehouseHandler) Delete(c *gin.Context) { diff --git a/backend/internal/model/stock.go b/backend/internal/model/stock.go index af6a0cb..28a3163 100644 --- a/backend/internal/model/stock.go +++ b/backend/internal/model/stock.go @@ -91,16 +91,19 @@ type Inventory struct { StockInItemID *uint64 `json:"stock_in_item_id"` InventoryCheckID *uint64 `json:"inventory_check_id"` Quantity float64 `gorm:"type:decimal(12,3);not null;default:0" json:"quantity"` - ProductCode string `gorm:"size:50" json:"product_code"` - ProductName string `gorm:"size:200" json:"product_name"` - Series string `gorm:"size:100" json:"series"` - Spec string `gorm:"size:100" json:"spec"` - Unit string `gorm:"size:20" json:"unit"` - WarehouseName string `gorm:"size:100" json:"warehouse_name"` - UnitPrice *float64 `gorm:"type:decimal(16,2)" json:"unit_price"` - ProductionDate *Date `gorm:"type:date" json:"production_date"` - BatchNo string `gorm:"size:50" json:"batch_no"` - SupplierName string `gorm:"size:200" json:"supplier_name"` + // Snapshot fields: copied from product/warehouse/stock-in at approval time. + // They reflect the state at the moment of stock-in and are NOT updated when the + // referenced product or warehouse record is later modified. + ProductCode string `gorm:"size:50" json:"product_code"` + ProductName string `gorm:"size:200" json:"product_name"` + Series string `gorm:"size:100" json:"series"` + Spec string `gorm:"size:100" json:"spec"` + Unit string `gorm:"size:20" json:"unit"` + WarehouseName string `gorm:"size:100" json:"warehouse_name"` + UnitPrice *float64 `gorm:"type:decimal(16,2)" json:"unit_price"` + ProductionDate *Date `gorm:"type:date" json:"production_date"` + BatchNo string `gorm:"size:50" json:"batch_no"` + SupplierName string `gorm:"size:200" json:"supplier_name"` Remark string `gorm:"size:500" json:"remark"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` diff --git a/backend/main.go b/backend/main.go index 6c56996..8d664f6 100644 --- a/backend/main.go +++ b/backend/main.go @@ -12,7 +12,6 @@ import ( "github.com/wangjia/jiu/backend/config" "github.com/wangjia/jiu/backend/internal/model" "github.com/wangjia/jiu/backend/internal/router" - "github.com/wangjia/jiu/backend/internal/util" ) func main() { @@ -38,9 +37,6 @@ func main() { // 自动迁移(GORM AutoMigrate 只增不删,生产安全) autoMigrate(db) - // 回填存量商品的拼音索引(一次性,已有值的跳过) - backfillPinyin(db) - // 启动 Gin gin.SetMode(config.C.Server.Mode) r := gin.New() @@ -90,8 +86,8 @@ func initDB() *gorm.DB { } sqlDB, _ := db.DB() - sqlDB.SetMaxIdleConns(10) - sqlDB.SetMaxOpenConns(100) + sqlDB.SetMaxIdleConns(config.C.Database.MaxIdleConns) + sqlDB.SetMaxOpenConns(config.C.Database.MaxOpenConns) return db } @@ -132,18 +128,3 @@ func autoMigrate(db *gorm.DB) { log.Println("AutoMigrate completed") } -func backfillPinyin(db *gorm.DB) { - var products []model.Product - db.Where("name_pinyin = '' OR name_pinyin IS NULL").Find(&products) - if len(products) == 0 { - return - } - for i := range products { - full, initials := util.ToPinyin(products[i].Name) - db.Model(&products[i]).Updates(map[string]interface{}{ - "name_pinyin": full, - "name_initials": initials, - }) - } - log.Printf("backfillPinyin: updated %d products", len(products)) -} diff --git a/web/_data/site.json b/web/_data/site.json index 117a1d7..75db653 100644 --- a/web/_data/site.json +++ b/web/_data/site.json @@ -2,9 +2,10 @@ "name": "岩美酒库管理系统", "tagline": "为酒行与酒店设计的库存管理平台", "copyright": "© 2026 岩美科技 · 保留所有权利", - "icp": "沪 ICP 备 2026000000 号 · 沪公网安备 31010000000000 号", + "icp": "", "support": { - "email": "yammy2023@163.com" + "email": "yammy2023@163.com", + "wechat": "" }, "appUrl": "/app/", "appBaseUrl": "https://jiu.51yanmei.com", diff --git a/web/_includes/footer.njk b/web/_includes/footer.njk index fdcc5b1..0f372f2 100644 --- a/web/_includes/footer.njk +++ b/web/_includes/footer.njk @@ -5,6 +5,7 @@ 岩美

为酒行与酒店设计的库存、审核、财务一体化管理平台。

@@ -42,7 +43,7 @@ diff --git a/web/index.njk b/web/index.njk index c7e28dc..17db6f9 100644 --- a/web/index.njk +++ b/web/index.njk @@ -555,7 +555,7 @@ details[open] .faq-q .icon { transform: rotate(45deg); }
  • 单门店 · 最多 3 用户
  • 全部模块开放
  • Web 端 + 移动端
  • -
  • 邮件技术支持
  • +
  • 微信技术支持
  • 免费开通 @@ -585,7 +585,7 @@ details[open] .faq-q .icon { transform: rotate(45deg); }
  • 专属实施与培训服务
  • 7×24 专属客户经理
  • - 联系销售 + {% if site.support.wechat %}微信咨询:{{ site.support.wechat }}{% else %}联系销售{% endif %} @@ -639,7 +639,7 @@ details[open] .faq-q .icon { transform: rotate(45deg); }
    立即开通试用 - 邮件咨询 + {% if site.support.wechat %}微信咨询:{{ site.support.wechat }}{% else %}邮件咨询{% endif %}