feat: 新增用户自助注册功能

后端新增 POST /api/v1/public/register 接口,支持门店自助注册并自动生成门店编码(S000001 格式);shops 表加 description 字段。营销站新增 /register/ 注册页,含门店信息和管理员账号表单,注册成功后展示门店编码和登录提示。Flutter 登录页底部加「前往官网注册」跳转链接。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-05-26 13:56:34 +08:00
parent 471b980179
commit eda7d37b64
9 changed files with 391 additions and 1 deletions
+72
View File
@@ -2,9 +2,11 @@ package service
import (
"errors"
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
@@ -61,6 +63,76 @@ func (s *AuthService) Login(shopCode, username, password string) (*TokenPair, *m
return pair, &user, nil
}
// RegisterInput 注册新门店所需参数
type RegisterInput struct {
ShopName string `json:"shop_name" binding:"required"`
Address string `json:"address" binding:"required"`
ManagerName string `json:"manager_name" binding:"required"`
Phone string `json:"phone" binding:"required"`
Description string `json:"description"`
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required,min=6"`
}
// RegisterResult 注册成功后返回的数据
type RegisterResult struct {
ShopCode string `json:"shop_code"`
ShopName string `json:"shop_name"`
Username string `json:"username"`
}
// Register 自助注册新门店(公开接口,无需认证)
func (s *AuthService) Register(in RegisterInput) (*RegisterResult, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(in.Password), bcrypt.DefaultCost)
if err != nil {
return nil, err
}
var result RegisterResult
err = s.db.Transaction(func(tx *gorm.DB) error {
// 先用 UUID 占位创建门店,得到真实 ID
shop := model.Shop{
Name: in.ShopName,
Code: uuid.New().String(),
Address: in.Address,
Phone: in.Phone,
ManagerName: in.ManagerName,
Description: in.Description,
}
if err := tx.Create(&shop).Error; err != nil {
return err
}
// 用自增 ID 生成正式门店编码
shop.Code = fmt.Sprintf("S%06d", shop.ID)
if err := tx.Model(&shop).Update("code", shop.Code).Error; err != nil {
return err
}
// 创建管理员用户
user := model.User{
ShopID: shop.ID,
Username: in.Username,
PasswordHash: string(hash),
RealName: in.ManagerName,
Phone: in.Phone,
Role: "admin",
IsActive: true,
}
if err := tx.Create(&user).Error; err != nil {
return err
}
result = RegisterResult{
ShopCode: shop.Code,
ShopName: shop.Name,
Username: user.Username,
}
return nil
})
return &result, err
}
// HashPassword 生成 bcrypt 哈希
func HashPassword(plain string) (string, error) {
b, err := bcrypt.GenerateFromPassword([]byte(plain), bcrypt.DefaultCost)