Compare commits

..

No commits in common. 'v1.1.1' and 'master' have entirely different histories.

@ -18,7 +18,6 @@ import (
"github.com/veypi/vbase/libs/jwt" "github.com/veypi/vbase/libs/jwt"
"github.com/veypi/vbase/models" "github.com/veypi/vbase/models"
"github.com/veypi/vigo" "github.com/veypi/vigo"
"gorm.io/gorm"
) )
// Email 正则表达式 // Email 正则表达式
@ -72,50 +71,47 @@ func register(x *vigo.X, req *RegisterRequest) (*AuthResponse, error) {
return nil, vigo.ErrInvalidArg.WithString("phone is required") return nil, vigo.ErrInvalidArg.WithString("phone is required")
} }
// 使用事务处理注册,防止并发创建多个首个管理员用户 // 检查是否是第一个用户(需要在创建用户之前检查)
var user *models.User
err := cfg.DB().Transaction(func(tx *gorm.DB) error {
// 检查是否是第一个用户(在事务内检查,带锁)
var userCount int64 var userCount int64
if err := tx.Model(&models.User{}).Count(&userCount).Error; err != nil { if err := cfg.DB().Model(&models.User{}).Count(&userCount).Error; err != nil {
return err return nil, vigo.ErrInternalServer.WithError(err)
} }
// 检查用户名是否已存在 // 检查用户名是否已存在
var count int64 var count int64
if err := tx.Model(&models.User{}).Where("username = ?", req.Username).Count(&count).Error; err != nil { if err := cfg.DB().Model(&models.User{}).Where("username = ?", req.Username).Count(&count).Error; err != nil {
return err return nil, vigo.ErrInternalServer.WithError(err)
} }
if count > 0 { if count > 0 {
return vigo.ErrInvalidArg.WithArgs("username already exists") return nil, vigo.ErrInvalidArg.WithArgs("username already exists")
} }
// 检查邮箱是否已存在 // 检查邮箱是否已存在
if req.Email != "" { if req.Email != "" {
count = 0 count = 0 // 重置计数器
if err := tx.Model(&models.User{}).Where("email = ?", req.Email).Count(&count).Error; err != nil { if err := cfg.DB().Model(&models.User{}).Where("email = ?", req.Email).Count(&count).Error; err != nil {
return err return nil, vigo.ErrInternalServer.WithError(err)
} }
if count > 0 { if count > 0 {
return vigo.ErrInvalidArg.WithArgs("email already exists") return nil, vigo.ErrInvalidArg.WithArgs("email already exists")
} }
} }
// 检查手机是否已存在 // 检查手机是否已存在
if req.Phone != "" { if req.Phone != "" {
count = 0 count = 0 // 重置计数器
if err := tx.Model(&models.User{}).Where("phone = ?", req.Phone).Count(&count).Error; err != nil { if err := cfg.DB().Model(&models.User{}).Where("phone = ?", req.Phone).Count(&count).Error; err != nil {
return err return nil, vigo.ErrInternalServer.WithError(err)
} }
if count > 0 { if count > 0 {
return vigo.ErrInvalidArg.WithArgs("phone already exists") return nil, vigo.ErrInvalidArg.WithArgs("phone already exists")
} }
} }
// 哈希密码 // 哈希密码
hashedPassword, err := crypto.HashPassword(req.Password, 12) hashedPassword, err := crypto.HashPassword(req.Password, 12)
if err != nil { if err != nil {
return err return nil, vigo.ErrInternalServer.WithError(err)
} }
// 创建用户 // 创建用户
@ -128,7 +124,7 @@ func register(x *vigo.X, req *RegisterRequest) (*AuthResponse, error) {
phone = &req.Phone phone = &req.Phone
} }
user = &models.User{ user := &models.User{
Username: req.Username, Username: req.Username,
Password: hashedPassword, Password: hashedPassword,
Email: email, Email: email,
@ -144,27 +140,20 @@ func register(x *vigo.X, req *RegisterRequest) (*AuthResponse, error) {
// 生成随机头像 // 生成随机头像
user.Avatar = fmt.Sprintf("https://public.veypi.com/img/avatar/%04d.jpg", rand.New(rand.NewSource(time.Now().UnixNano())).Intn(220)) user.Avatar = fmt.Sprintf("https://public.veypi.com/img/avatar/%04d.jpg", rand.New(rand.NewSource(time.Now().UnixNano())).Intn(220))
// 第一个用户固定 ID 为 admin if err := cfg.DB().Create(user).Error; err != nil {
if userCount == 0 { return nil, vigo.ErrInternalServer.WithError(err)
user.ID = "admin"
}
if err := tx.Create(user).Error; err != nil {
return err
}
return nil
})
if err != nil {
return nil, err
} }
// 授予角色(事务外,因为事务已确保用户创建成功) // 第一个用户授予 admin 角色,其他用户授予 user 角色
roleCode := "user" roleCode := "user"
if user.ID == "admin" { if userCount == 0 {
roleCode = "admin" roleCode = "admin"
} }
if err := cfg.Auth.GrantRole(x.Context(), user.ID, roleCode); err != nil { if err := cfg.Auth.GrantRole(x.Context(), user.ID, roleCode); err != nil {
// 记录错误但允许注册继续,或者回滚
// 这里简单处理,继续流程,用户可能需要管理员手动授权
// 或者返回错误
return nil, vigo.ErrInternalServer.WithError(err) return nil, vigo.ErrInternalServer.WithError(err)
} }

@ -24,6 +24,9 @@ type Options struct {
Redis config.Redis `json:"redis" usage:"Redis 配置addr: memory 使用内存模式"` Redis config.Redis `json:"redis" usage:"Redis 配置addr: memory 使用内存模式"`
Key config.Key `json:"key" usage:"系统密钥,用于加密敏感数据(建议 32 位以上)"` Key config.Key `json:"key" usage:"系统密钥,用于加密敏感数据(建议 32 位以上)"`
// === 文件存储 ===
StoragePath string `json:"storage_path" usage:"文件存储路径"`
// === JWT 配置(安全敏感,保留在本地) === // === JWT 配置(安全敏感,保留在本地) ===
JWT JWTConfig `json:"jwt" usage:"JWT 配置"` JWT JWTConfig `json:"jwt" usage:"JWT 配置"`
@ -59,6 +62,7 @@ var Global = &Options{
Addr: "memory", Addr: "memory",
}, },
Key: "your-secret-key-change-in-production-min-32-characters", Key: "your-secret-key-change-in-production-min-32-characters",
StoragePath: "./data",
JWT: JWTConfig{ JWT: JWTConfig{
Secret: "", Secret: "",
AccessExpiry: time.Hour * 24, AccessExpiry: time.Hour * 24,

@ -18,7 +18,7 @@ import (
"github.com/veypi/vigo" "github.com/veypi/vigo"
) )
const Version = "v1.1.1" const Version = "v0.6.4"
var Router = vigo.NewRouter() var Router = vigo.NewRouter()
var ( var (

@ -102,7 +102,6 @@ func initAdminUser() error {
EmailVerified: email != nil, EmailVerified: email != nil,
LastLoginAt: &now, LastLoginAt: &now,
} }
user.ID = "admin"
if err := db.Create(user).Error; err != nil { if err := db.Create(user).Error; err != nil {
return fmt.Errorf("create admin user failed: %w", err) return fmt.Errorf("create admin user failed: %w", err)

@ -19,7 +19,7 @@ import (
type Setting struct { type Setting struct {
vigo.Model vigo.Model
Key string `gorm:"uniqueIndex;size:100;not null" json:"key"` Key string `gorm:"uniqueIndex;size:100;not null" json:"key"`
Value string `gorm:"type:longtext" json:"value"` Value string `gorm:"type:text" json:"value"`
Type string `gorm:"size:20;default:'string'" json:"type"` // string/int/bool/json Type string `gorm:"size:20;default:'string'" json:"type"` // string/int/bool/json
Category string `gorm:"index;size:50" json:"category"` Category string `gorm:"index;size:50" json:"category"`
Desc string `gorm:"size:200" json:"desc"` Desc string `gorm:"size:200" json:"desc"`

@ -372,34 +372,6 @@ class VBase {
} }
); );
} }
wrapFetch(urlprefix) {
const originalFetch = window.fetch;
const self = this;
return async function wrappedFetch(input, init = {}) {
let url;
if (typeof input === 'string') {
url = input;
} else if (input instanceof Request) {
url = input.url;
} else {
url = String(input);
}
if (urlprefix && !url.startsWith('http://') && !url.startsWith('https://')) {
url = urlprefix + url;
}
const headers = { ...init.headers };
const authHeaders = self.getAuthHeaders();
for (const [key, value] of Object.entries(authHeaders)) {
if (!headers[key]) {
headers[key] = value;
}
}
return originalFetch(url, { ...init, headers });
};
}
_cachePublicUser(user) { _cachePublicUser(user) {
if (!user?.id) return; if (!user?.id) return;

Loading…
Cancel
Save