Files
zzyxyz_go_api/internal/bookmarks/user_np.go
zzy 9cab95c0f7 feat(bookmark): 添加 401 和 403 响应引用并完善错误定义
在 bookmark.yaml 配置文件中,为多个接口路径添加了 '401' 和 '403' 状态码的响应引用,
分别指向 components 中定义的 Unauthorized 和 Forbidden 响应。同时在 components
部分补充了 Forbidden 响应的定义,增强了 API 文档的完整性与规范性。

feat(user_np): 新增用户信息接口与基础错误结构定义

在 user_np.yaml 中新增了 /auth/info 路径下的 GET 和 PUT 接口,用于获取和保存用户信息。
同时,在 components 中定义了 ServerInternalError 响应和 Error 结构体,统一错误返回格式,
提升接口一致性与可维护性。

feat(vfs): 调整内容类型为 text/plain 并增强节点名称校验逻辑

将 vfs.yaml 中涉及二进制流传输的内容类型由 application/octet-stream 修改为 text/plain,
简化数据处理方式。同时在 vfs.go 模型中新增 CheckNameValid 方法,用于校验节点名称合法性,
防止非法字符(如斜杠)造成路径问题。

refactor(bookmark): 优化 API Key 验证逻辑并暴露更新时间字段

重构 BookMarksImpl 的 validateApiKey 函数,简化认证判断流程,并将 adminToken 从指针改为字符串常量。
此外,在 bookmarkModel2Res 函数中新增 UpdatedAt 字段,使书签响应包含更新时间信息。

feat(user_np): 实现用户信息相关接口占位函数

在 UserNPImpl 中新增 GetUserInfo 和 SaveUserInfo 两个方法的占位实现,为后续业务逻辑开发做好准备。

refactor(vfs): 使用文本请求体并加强服务节点操作校验

修改 vfs_impl.go 中读取请求体的方式,由 io.Reader 改为直接解引用文本内容,提升处理效率。
更新 CreateVFSNode、GetVFSNode 和 UpdateVFSNode 方法中对请求体和响应体的处理逻辑,
统一使用文本格式,增强代码一致性与健壮性。

feat(vfs): 为书签代理服务添加认证 Token 支持

在 vfs_bookmark.go 中为 VfsBookMarkService 结构体增加 token 字段,并在调用 bookmark 服务各接口时,
通过 HTTP 请求头设置 X-BookMark-Token,确保服务间通信的安全性与权限控制。
2025-09-26 14:42:22 +08:00

220 lines
5.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", reqs.Status())
}
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("未知错误")
}
// PostAuthLogin 用户登录
func (u *UserNPImpl) PostAuthLogin(c *gin.Context) {
var req api.PostAuthLoginJSONRequestBody
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 = ?", req.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,
})
}
// PostAuthRegister 用户注册
func (u *UserNPImpl) PostAuthRegister(c *gin.Context) {
var req api.PostAuthRegisterJSONRequestBody
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 = ?", req.Username).First(&existingUser).Error; err == nil {
c.JSON(http.StatusConflict, gin.H{"error": "用户名已存在"})
return
}
// 创建新用户
user := models.UserNP{
Username: req.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": "用户创建失败"})
}
if token, err := u.RegisterVFSService(req.Username, u.vfsToken); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "无法生成访问令牌"})
u.db.Delete(&user)
} else {
user.Token = token
u.db.Save(&user)
}
c.JSON(http.StatusCreated, nil)
}
// PutAuthPassword 修改密码
func (u *UserNPImpl) PutAuthPassword(c *gin.Context) {
// 获取Authorization头中的token
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "缺少访问令牌"})
return
}
var req api.PutAuthPasswordJSONRequestBody
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 = ?", req.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) {
panic("unimplemented")
}
// SaveUserInfo implements server.ServerInterface.
func (u *UserNPImpl) SaveUserInfo(c *gin.Context) {
panic("unimplemented")
}
// Make sure we conform to ServerInterface
var _ api.ServerInterface = (*UserNPImpl)(nil)