fix(backend): JWT config mapstructure tag 修复 + 模型从 hotel 重构为 shop

- 修复 JWTConfig 缺少 mapstructure tag 导致 access_expire_min 解析为 0,
  token 签发即过期,所有 API 请求返回 401
- 全部 config struct 补齐 mapstructure tag(secret/dsn/hmac_secret 等)
- 模型层从 hotel/HotelID 统一重命名为 shop/ShopID
- 删除旧 migrations(001-004),新增 001_init 综合迁移文件
- 更新 schema.sql、testutil、handler/service/model 相关引用

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-04-07 22:20:12 +08:00
parent 37112d6599
commit 31ea370cea
50 changed files with 2675 additions and 951 deletions
+43 -40
View File
@@ -46,7 +46,7 @@ func SetupTestDB() *gorm.DB {
// SQLite 不支持 ENUM,直接用原始 SQL 建表
stmts := []string{
`CREATE TABLE IF NOT EXISTS hotels (
`CREATE TABLE IF NOT EXISTS shops (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME,
updated_at DATETIME,
@@ -55,6 +55,9 @@ func SetupTestDB() *gorm.DB {
code TEXT UNIQUE,
address TEXT,
phone TEXT,
manager_name TEXT,
business_license TEXT,
shop_photos TEXT,
custom_fields TEXT
)`,
`CREATE TABLE IF NOT EXISTS users (
@@ -62,7 +65,7 @@ func SetupTestDB() *gorm.DB {
created_at DATETIME,
updated_at DATETIME,
deleted_at DATETIME,
hotel_id INTEGER NOT NULL,
shop_id INTEGER NOT NULL,
username TEXT,
password_hash TEXT,
real_name TEXT,
@@ -71,13 +74,13 @@ func SetupTestDB() *gorm.DB {
is_active INTEGER DEFAULT 1,
custom_fields TEXT
)`,
`CREATE UNIQUE INDEX IF NOT EXISTS uk_hotel_username ON users(hotel_id, username)`,
`CREATE UNIQUE INDEX IF NOT EXISTS uk_shop_username ON users(shop_id, username)`,
`CREATE TABLE IF NOT EXISTS licenses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME,
updated_at DATETIME,
deleted_at DATETIME,
hotel_id INTEGER NOT NULL,
shop_id INTEGER NOT NULL,
license_key TEXT UNIQUE,
device_id TEXT,
type TEXT DEFAULT 'trial',
@@ -91,7 +94,7 @@ func SetupTestDB() *gorm.DB {
created_at DATETIME,
updated_at DATETIME,
deleted_at DATETIME,
hotel_id INTEGER NOT NULL,
shop_id INTEGER NOT NULL,
name TEXT NOT NULL,
parent_id INTEGER,
sort_order INTEGER DEFAULT 0
@@ -101,7 +104,7 @@ func SetupTestDB() *gorm.DB {
created_at DATETIME,
updated_at DATETIME,
deleted_at DATETIME,
hotel_id INTEGER NOT NULL,
shop_id INTEGER NOT NULL,
code TEXT,
barcode TEXT,
name TEXT NOT NULL,
@@ -121,7 +124,7 @@ func SetupTestDB() *gorm.DB {
created_at DATETIME,
updated_at DATETIME,
deleted_at DATETIME,
hotel_id INTEGER NOT NULL,
shop_id INTEGER NOT NULL,
name TEXT NOT NULL,
location TEXT,
is_default INTEGER DEFAULT 0,
@@ -132,7 +135,7 @@ func SetupTestDB() *gorm.DB {
created_at DATETIME,
updated_at DATETIME,
deleted_at DATETIME,
hotel_id INTEGER NOT NULL,
shop_id INTEGER NOT NULL,
code TEXT,
name TEXT NOT NULL,
type TEXT DEFAULT 'supplier',
@@ -150,7 +153,7 @@ func SetupTestDB() *gorm.DB {
created_at DATETIME,
updated_at DATETIME,
deleted_at DATETIME,
hotel_id INTEGER NOT NULL,
shop_id INTEGER NOT NULL,
order_no TEXT,
type TEXT DEFAULT 'purchase',
warehouse_id INTEGER NOT NULL,
@@ -164,14 +167,14 @@ func SetupTestDB() *gorm.DB {
custom_fields TEXT,
remark TEXT
)`,
`CREATE UNIQUE INDEX IF NOT EXISTS uk_hotel_sio_order_no ON stock_in_orders(hotel_id, order_no)`,
`CREATE UNIQUE INDEX IF NOT EXISTS uk_shop_sio_order_no ON stock_in_orders(shop_id, order_no)`,
`CREATE TABLE IF NOT EXISTS stock_in_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME,
updated_at DATETIME,
deleted_at DATETIME,
order_id INTEGER NOT NULL,
hotel_id INTEGER NOT NULL,
shop_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity REAL NOT NULL,
unit_price REAL DEFAULT 0,
@@ -185,7 +188,7 @@ func SetupTestDB() *gorm.DB {
created_at DATETIME,
updated_at DATETIME,
deleted_at DATETIME,
hotel_id INTEGER NOT NULL,
shop_id INTEGER NOT NULL,
order_no TEXT,
type TEXT DEFAULT 'sale',
warehouse_id INTEGER NOT NULL,
@@ -199,14 +202,14 @@ func SetupTestDB() *gorm.DB {
custom_fields TEXT,
remark TEXT
)`,
`CREATE UNIQUE INDEX IF NOT EXISTS uk_hotel_soo_order_no ON stock_out_orders(hotel_id, order_no)`,
`CREATE UNIQUE INDEX IF NOT EXISTS uk_shop_soo_order_no ON stock_out_orders(shop_id, order_no)`,
`CREATE TABLE IF NOT EXISTS stock_out_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME,
updated_at DATETIME,
deleted_at DATETIME,
order_id INTEGER NOT NULL,
hotel_id INTEGER NOT NULL,
shop_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity REAL NOT NULL,
unit_price REAL DEFAULT 0,
@@ -216,16 +219,16 @@ func SetupTestDB() *gorm.DB {
)`,
`CREATE TABLE IF NOT EXISTS inventories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hotel_id INTEGER NOT NULL,
shop_id INTEGER NOT NULL,
warehouse_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity REAL DEFAULT 0,
updated_at DATETIME,
UNIQUE(hotel_id, warehouse_id, product_id)
UNIQUE(shop_id, warehouse_id, product_id)
)`,
`CREATE TABLE IF NOT EXISTS inventory_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hotel_id INTEGER NOT NULL,
shop_id INTEGER NOT NULL,
warehouse_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
direction TEXT,
@@ -242,7 +245,7 @@ func SetupTestDB() *gorm.DB {
created_at DATETIME,
updated_at DATETIME,
deleted_at DATETIME,
hotel_id INTEGER NOT NULL,
shop_id INTEGER NOT NULL,
check_no TEXT,
warehouse_id INTEGER NOT NULL,
operator_id INTEGER NOT NULL,
@@ -250,14 +253,14 @@ func SetupTestDB() *gorm.DB {
check_date DATETIME,
remark TEXT
)`,
`CREATE UNIQUE INDEX IF NOT EXISTS uk_hotel_check_no ON inventory_checks(hotel_id, check_no)`,
`CREATE UNIQUE INDEX IF NOT EXISTS uk_shop_check_no ON inventory_checks(shop_id, check_no)`,
`CREATE TABLE IF NOT EXISTS inventory_check_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME,
updated_at DATETIME,
deleted_at DATETIME,
check_id INTEGER NOT NULL,
hotel_id INTEGER NOT NULL,
shop_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
system_qty REAL,
actual_qty REAL,
@@ -265,13 +268,13 @@ func SetupTestDB() *gorm.DB {
)`,
`CREATE TABLE IF NOT EXISTS number_rules (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hotel_id INTEGER NOT NULL,
shop_id INTEGER NOT NULL,
type TEXT,
prefix TEXT DEFAULT '',
current_no INTEGER DEFAULT 0,
date_format TEXT DEFAULT 'YYYYMMDD',
updated_at DATETIME,
UNIQUE(hotel_id, type)
UNIQUE(shop_id, type)
)`,
}
@@ -284,16 +287,16 @@ func SetupTestDB() *gorm.DB {
return db
}
// CreateTestHotel 创建测试
func CreateTestHotel(db *gorm.DB, code string) *model.Hotel {
hotel := &model.Hotel{
Name: "Test Hotel " + code,
// CreateTestShop 创建测试
func CreateTestShop(db *gorm.DB, code string) *model.Shop {
shop := &model.Shop{
Name: "Test Shop " + code,
Code: code,
}
if err := db.Create(hotel).Error; err != nil {
panic(fmt.Sprintf("failed to create test hotel: %v", err))
if err := db.Create(shop).Error; err != nil {
panic(fmt.Sprintf("failed to create test shop: %v", err))
}
return hotel
return shop
}
// hashPassword 内部使用的密码哈希函数,避免循环依赖
@@ -306,12 +309,12 @@ func hashPassword(plain string) string {
}
// CreateTestUser 创建测试用户
func CreateTestUser(db *gorm.DB, hotelID uint64, username, password, role string) *model.User {
func CreateTestUser(db *gorm.DB, shopID uint64, username, password, role string) *model.User {
hash := hashPassword(password)
user := &model.User{
TenantBase: model.TenantBase{
Base: model.Base{},
HotelID: hotelID,
Base: model.Base{},
ShopID: shopID,
},
Username: username,
PasswordHash: hash,
@@ -326,10 +329,10 @@ func CreateTestUser(db *gorm.DB, hotelID uint64, username, password, role string
}
// CreateTestWarehouse 创建测试仓库
func CreateTestWarehouse(db *gorm.DB, hotelID uint64, name string) *model.Warehouse {
func CreateTestWarehouse(db *gorm.DB, shopID uint64, name string) *model.Warehouse {
w := &model.Warehouse{
TenantBase: model.TenantBase{
HotelID: hotelID,
ShopID: shopID,
},
Name: name,
IsDefault: true,
@@ -341,10 +344,10 @@ func CreateTestWarehouse(db *gorm.DB, hotelID uint64, name string) *model.Wareho
}
// CreateTestProduct 创建测试商品
func CreateTestProduct(db *gorm.DB, hotelID uint64, name string) *model.Product {
func CreateTestProduct(db *gorm.DB, shopID uint64, name string) *model.Product {
p := &model.Product{
TenantBase: model.TenantBase{
HotelID: hotelID,
ShopID: shopID,
},
Name: name,
Code: "P-" + name,
@@ -357,13 +360,13 @@ func CreateTestProduct(db *gorm.DB, hotelID uint64, name string) *model.Product
}
// GetAuthToken 生成测试 JWT token
func GetAuthToken(userID, hotelID uint64, role string) string {
func GetAuthToken(userID, shopID uint64, role string) string {
InitConfig()
now := time.Now()
claims := middleware.Claims{
UserID: userID,
HotelID: hotelID,
Role: role,
UserID: userID,
ShopID: shopID,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(now.Add(time.Hour)),
IssuedAt: jwt.NewNumericDate(now),