为 CORS 配置添加 `Authorization` 请求头,以支持携带认证信息的跨域请求。 feat(user-np): 改进错误信息和用户注册流程 - 在注册 VFS 服务失败时返回更详细的错误信息,包括状态码和响应体内容。 - 用户创建失败时返回具体的错误详情,并确保数据库记录被正确回滚。 - 注销用户时先尝试删除 VFS 服务中的相关资源,再执行数据库删除操作。 - 使用 `Unscoped().Delete()` 确保物理删除用户数据。 feat(vfs): 完善用户目录结构及节点创建逻辑 - 创建用户主目录 `/home/username` 及其子目录 `.Recycle_Bin`。 - 调整 `CreateVFSNode` 方法参数类型为值传递。 - 修复创建用户目录时传参不一致的问题。 - 删除用户时递归清理其在 VFS 中的所有节点,并通过代理删除关联的服务节点。 feat(mage): 新增生成 TypeScript 类型定义的任务 新增 `Gen_TS` Mage 任务用于将 OpenAPI 配置文件转换为 TypeScript 类型定义文件, 支持 `vfs`、`bookmark` 和 `user_np` 模块。
250 lines
6.4 KiB
Go
250 lines
6.4 KiB
Go
// internal/handlers/user_np.go
|
|
|
|
package bookmarks
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/user_np_server"
|
|
client "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/vfs_client"
|
|
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/bookmarks/models"
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type UserNPImpl struct {
|
|
db *gorm.DB
|
|
client *client.ClientWithResponses
|
|
vfsToken string
|
|
}
|
|
|
|
func NewUserNP(dbPath string) (*UserNPImpl, error) {
|
|
var err error
|
|
var db *gorm.DB
|
|
db, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 自动迁移表结构
|
|
err = db.AutoMigrate(&models.UserNP{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
client, err := client.NewClientWithResponses("http://localhost:8080/api")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &UserNPImpl{
|
|
db: db,
|
|
client: client,
|
|
vfsToken: "random_token",
|
|
}, nil
|
|
}
|
|
|
|
func (u *UserNPImpl) RegisterVFSService(username, token string) (*string, error) {
|
|
ctx := context.Background()
|
|
reqs, err := u.client.CreateUserWithResponse(ctx, username, func(ctx context.Context, req *http.Request) error {
|
|
req.Header.Set("X-VFS-Token", token)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if reqs.StatusCode() != http.StatusCreated {
|
|
return nil, fmt.Errorf("failed to register vfs service: %s %s", reqs.Status(), string(reqs.Body))
|
|
}
|
|
|
|
tokenHeader := reqs.HTTPResponse.Header.Get("X-VFS-Token")
|
|
if tokenHeader == "" {
|
|
return nil, fmt.Errorf("X-VFS-Token header is missing")
|
|
}
|
|
return &tokenHeader, nil
|
|
}
|
|
|
|
func (u *UserNPImpl) UnregisterVFSService(username, token string) error {
|
|
ctx := context.Background()
|
|
reqs, err := u.client.DeleteUserWithResponse(ctx, username, func(ctx context.Context, req *http.Request) error {
|
|
req.Header.Set("X-VFS-Token", token)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if reqs.StatusCode() == http.StatusNoContent {
|
|
return nil
|
|
}
|
|
|
|
if reqs.JSON404 != nil {
|
|
return fmt.Errorf("用户不存在 %s", reqs.JSON404.Message)
|
|
}
|
|
|
|
if reqs.JSON500 != nil {
|
|
return fmt.Errorf("服务器错误 %s", reqs.JSON500.Message)
|
|
}
|
|
|
|
return fmt.Errorf("未知错误")
|
|
}
|
|
|
|
// UserLogin implements server.ServerInterface.
|
|
func (u *UserNPImpl) UserLogin(c *gin.Context, username string) {
|
|
var req api.UserLoginJSONRequestBody
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// 查找用户
|
|
var user models.UserNP
|
|
if err := u.db.Where("username = ?", username).First(&user).Error; err != nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"})
|
|
return
|
|
}
|
|
|
|
// 验证密码
|
|
if !user.CheckPassword(req.Password) {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, api.LoginResponse{
|
|
Token: user.Token,
|
|
UserId: &user.ID,
|
|
})
|
|
}
|
|
|
|
// UserRegister implements server.ServerInterface.
|
|
func (u *UserNPImpl) UserRegister(c *gin.Context, username string) {
|
|
var req api.UserRegisterJSONRequestBody
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// 检查用户名是否已存在
|
|
var existingUser models.UserNP
|
|
if err := u.db.Where("username = ?", username).First(&existingUser).Error; err == nil {
|
|
c.JSON(http.StatusConflict, gin.H{"error": "用户名已存在"})
|
|
return
|
|
}
|
|
|
|
// 创建新用户
|
|
user := models.UserNP{
|
|
Username: username,
|
|
Email: req.Email,
|
|
}
|
|
|
|
// 加密密码
|
|
if err := user.HashPassword(req.Password); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "密码处理失败"})
|
|
return
|
|
}
|
|
|
|
// 保存到数据库
|
|
if err := u.db.Create(&user).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "用户创建失败", "detail": err.Error()})
|
|
return
|
|
}
|
|
|
|
if token, err := u.RegisterVFSService(username, u.vfsToken); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "无法生成访问令牌", "detail": err.Error()})
|
|
u.db.Unscoped().Delete(&user)
|
|
} else {
|
|
user.Token = token
|
|
u.db.Save(&user)
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, nil)
|
|
}
|
|
|
|
// DeleteUser implements server.ServerInterface.
|
|
func (u *UserNPImpl) DeleteUser(c *gin.Context, username string) {
|
|
authHeader := c.GetHeader("Authorization")
|
|
if authHeader == "" {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "缺少访问令牌"})
|
|
return
|
|
}
|
|
|
|
// 查找用户
|
|
var user models.UserNP
|
|
if err := u.db.Where("username = ?", username).First(&user).Error; err != nil {
|
|
c.JSON(http.StatusNoContent, nil)
|
|
return
|
|
}
|
|
|
|
if user.Token == nil || *user.Token != authHeader {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "访问令牌错误"})
|
|
return
|
|
}
|
|
|
|
if err := u.UnregisterVFSService(username, u.vfsToken); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "无法注销访问令牌", "detail": err.Error()})
|
|
return
|
|
}
|
|
|
|
u.db.Unscoped().Delete(&user)
|
|
c.JSON(http.StatusNoContent, nil)
|
|
}
|
|
|
|
// UpdatePassword implements server.ServerInterface.
|
|
func (u *UserNPImpl) UpdatePassword(c *gin.Context, username string) {
|
|
// 获取Authorization头中的token
|
|
authHeader := c.GetHeader("Authorization")
|
|
if authHeader == "" {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "缺少访问令牌"})
|
|
return
|
|
}
|
|
|
|
var req api.UpdatePasswordJSONRequestBody
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// 查找用户
|
|
var user models.UserNP
|
|
if err := u.db.Where("username = ?", username).First(&user).Error; err != nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户不存在"})
|
|
return
|
|
}
|
|
|
|
// 验证旧密码
|
|
if !user.CheckPassword(req.OldPassword) {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "旧密码错误"})
|
|
return
|
|
}
|
|
|
|
// 加密新密码
|
|
if err := user.HashPassword(req.NewPassword); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "密码处理失败"})
|
|
return
|
|
}
|
|
|
|
// 保存到数据库
|
|
if err := u.db.Save(&user).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "密码更新失败"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, nil)
|
|
}
|
|
|
|
// GetUserInfo implements server.ServerInterface.
|
|
func (u *UserNPImpl) GetUserInfo(c *gin.Context, username string) {
|
|
panic("unimplemented")
|
|
}
|
|
|
|
// SaveUserInfo implements server.ServerInterface.
|
|
func (u *UserNPImpl) SaveUserInfo(c *gin.Context, username string) {
|
|
panic("unimplemented")
|
|
}
|
|
|
|
// Make sure we conform to ServerInterface
|
|
var _ api.ServerInterface = (*UserNPImpl)(nil)
|