refactor(bookmark): 重构书签服务入口文件并整合用户权限功能
将 bookmark.go 重命名为 main.go,并调整包引用路径。将 bookmarks 和 user_np 两个模块的处理逻辑合并到同一个服务中,统一注册路由。同时更新了相关 API 的引用路径,确保生成代码与内部实现正确绑定。 此外,移除了独立的 user_np 服务入口文件,其功能已整合至 bookmark 服务中。 配置文件中调整了 user_np 和 vfs 服务的端口及部分接口定义,完善了用户 相关操作的路径参数和请求体结构。
This commit is contained in:
239
internal/bookmarks/bookmark.go
Normal file
239
internal/bookmarks/bookmark.go
Normal file
@ -0,0 +1,239 @@
|
||||
// internal/handlers/note_link.go
|
||||
|
||||
package bookmarks
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/bookmarks"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/bookmarks/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type BookMarksImpl struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
var adminToken *string
|
||||
|
||||
func validateApiKey(apiKey string) bool {
|
||||
if adminToken != nil && apiKey == *adminToken {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func AuthMiddleware() api.MiddlewareFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 检查当前请求是否需要认证
|
||||
if _, exists := c.Get(api.ApiKeyAuthScopes); exists {
|
||||
// 提取 API Key
|
||||
apiKey := c.GetHeader("X-BookMark-Token")
|
||||
|
||||
// 验证 API Key(您需要实现这个逻辑)
|
||||
if apiKey == "" || !validateApiKey(apiKey) {
|
||||
c.JSON(http.StatusUnauthorized, api.Error{
|
||||
Errtype: "Unauthorized",
|
||||
Message: "Invalid or missing API key",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func NewBookMarkPermission() (*api.GinServerOptions, error) {
|
||||
return &api.GinServerOptions{
|
||||
Middlewares: []api.MiddlewareFunc{AuthMiddleware()},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewBookMarks(dbPath string) (*BookMarksImpl, 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.Bookmark{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &BookMarksImpl{db: db}, nil
|
||||
}
|
||||
|
||||
func (b *BookMarksImpl) FindBMFromExternalID(externalID int64) (models.Bookmark, error) {
|
||||
var db = b.db
|
||||
var bookmark models.Bookmark
|
||||
|
||||
// 使用ExternalID查询书签
|
||||
if err := db.Where("external_id = ?", externalID).First(&bookmark).Error; err != nil {
|
||||
return bookmark, err
|
||||
}
|
||||
|
||||
return bookmark, nil
|
||||
}
|
||||
|
||||
// CreateBookmark implements api.ServerInterface.
|
||||
func (b *BookMarksImpl) CreateBookmark(c *gin.Context, id int64) {
|
||||
var db = b.db
|
||||
var req api.BookmarkRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, api.Error{
|
||||
Errtype: "ParameterError",
|
||||
Message: "Invalid request parameters",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查外部ID是否已经存在
|
||||
var existingBookmark models.Bookmark
|
||||
result := db.Where("external_id = ?", id).First(&existingBookmark)
|
||||
if result.Error == nil {
|
||||
// ExternalID已存在,返回冲突错误
|
||||
c.JSON(http.StatusConflict, api.Error{
|
||||
Errtype: "ConflictError",
|
||||
Message: "Bookmark with this External ID already exists",
|
||||
})
|
||||
return
|
||||
} else if result.Error != gorm.ErrRecordNotFound {
|
||||
// 数据库查询出错
|
||||
c.JSON(http.StatusInternalServerError, api.Error{
|
||||
Errtype: "DatabaseError",
|
||||
Message: "Database query error",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
bookmark := bookmarkReq2Model(req)
|
||||
bookmark.ExternalID = id // 设置外部ID
|
||||
if err := db.Create(&bookmark).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, api.Error{
|
||||
Errtype: "DatabaseError",
|
||||
Message: "Failed to create bookmark",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
response := bookmarkModel2Res(bookmark)
|
||||
c.JSON(http.StatusCreated, response)
|
||||
}
|
||||
|
||||
// DeleteBookmark implements api.ServerInterface.
|
||||
func (b *BookMarksImpl) DeleteBookmark(c *gin.Context, id int64) {
|
||||
var db = b.db
|
||||
var bookmark models.Bookmark
|
||||
|
||||
// 使用ExternalID删除书签
|
||||
if err := db.Where("external_id = ?", id).Delete(&bookmark).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, api.Error{
|
||||
Errtype: "DatabaseError",
|
||||
Message: "Failed to delete bookmark",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// GetBookmark implements api.ServerInterface.
|
||||
func (b *BookMarksImpl) GetBookmark(c *gin.Context, id int64) {
|
||||
var db = b.db
|
||||
var bookmark models.Bookmark
|
||||
|
||||
// 使用ExternalID查询书签
|
||||
if err := db.Where("external_id = ?", id).First(&bookmark).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, api.Error{
|
||||
Errtype: "NotFoundError",
|
||||
Message: "Bookmark not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 构造响应
|
||||
response := bookmarkModel2Res(bookmark)
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// UpdateBookmark implements api.ServerInterface.
|
||||
func (b *BookMarksImpl) UpdateBookmark(c *gin.Context, id int64) {
|
||||
var db = b.db
|
||||
var req api.BookmarkRequest
|
||||
|
||||
// 绑定请求参数
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, api.Error{
|
||||
Errtype: "ParameterError",
|
||||
Message: "Invalid request parameters",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 查找要更新的书签(使用ExternalID)
|
||||
var bookmark models.Bookmark
|
||||
if err := db.Where("external_id = ?", id).First(&bookmark).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, api.Error{
|
||||
Errtype: "NotFoundError",
|
||||
Message: "Bookmark not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新书签字段
|
||||
if req.Name != "" {
|
||||
bookmark.Name = req.Name
|
||||
}
|
||||
if req.Link != nil {
|
||||
bookmark.Link = req.Link
|
||||
}
|
||||
if req.Detail != nil {
|
||||
bookmark.Detail = req.Detail
|
||||
}
|
||||
if req.Description != nil {
|
||||
bookmark.Description = req.Description
|
||||
}
|
||||
|
||||
// 保存更新
|
||||
if err := db.Save(&bookmark).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, api.Error{
|
||||
Errtype: "DatabaseError",
|
||||
Message: "Failed to update bookmark",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 构造响应
|
||||
response := bookmarkModel2Res(bookmark)
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
func bookmarkReq2Model(req api.BookmarkRequest) models.Bookmark {
|
||||
return models.Bookmark{
|
||||
Name: req.Name,
|
||||
Link: req.Link,
|
||||
Detail: req.Detail,
|
||||
Description: req.Description,
|
||||
}
|
||||
}
|
||||
|
||||
func bookmarkModel2Res(bookmark models.Bookmark) api.BookmarkResponse {
|
||||
return api.BookmarkResponse{
|
||||
Id: bookmark.ID,
|
||||
Name: bookmark.Name,
|
||||
Link: bookmark.Link,
|
||||
Detail: bookmark.Detail,
|
||||
Description: bookmark.Description,
|
||||
CreatedAt: bookmark.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we conform to ServerInterface
|
||||
var _ api.ServerInterface = (*BookMarksImpl)(nil)
|
4
internal/bookmarks/config.go
Normal file
4
internal/bookmarks/config.go
Normal file
@ -0,0 +1,4 @@
|
||||
package bookmarks
|
||||
|
||||
type Config struct {
|
||||
}
|
19
internal/bookmarks/models/bookmark.go
Normal file
19
internal/bookmarks/models/bookmark.go
Normal file
@ -0,0 +1,19 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Bookmark struct {
|
||||
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
ExternalID int64 `json:"external_id" gorm:"uniqueIndex;not null"`
|
||||
Name string `json:"name" gorm:"not null;index;size:255"`
|
||||
Link *string `json:"link" gorm:"type:url"`
|
||||
Detail *string `json:"detail" gorm:"type:text"`
|
||||
Description *string `json:"description" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
|
||||
}
|
38
internal/bookmarks/models/user_np.go
Normal file
38
internal/bookmarks/models/user_np.go
Normal file
@ -0,0 +1,38 @@
|
||||
// internal/models/user_np.go
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UserNP 用户名密码认证模型
|
||||
type UserNP struct {
|
||||
ID int64 `json:"id" gorm:"primaryKey"`
|
||||
Username string `json:"username" gorm:"not null;index;size:255;unique"`
|
||||
Password string `json:"password" gorm:"not null;size:255"`
|
||||
Email *string `json:"email" gorm:"type:text;unique"`
|
||||
Token *string `json:"token" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
|
||||
}
|
||||
|
||||
// HashPassword 对密码进行哈希处理
|
||||
func (u *UserNP) HashPassword(password string) error {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Password = string(bytes)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckPassword 验证密码
|
||||
func (u *UserNP) CheckPassword(providedPassword string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(providedPassword))
|
||||
return err == nil
|
||||
}
|
209
internal/bookmarks/user_np.go
Normal file
209
internal/bookmarks/user_np.go
Normal file
@ -0,0 +1,209 @@
|
||||
// internal/handlers/user_np.go
|
||||
|
||||
package bookmarks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/user_np"
|
||||
client "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/vfs"
|
||||
"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)
|
||||
}
|
||||
|
||||
// Make sure we conform to ServerInterface
|
||||
var _ api.ServerInterface = (*UserNPImpl)(nil)
|
Reference in New Issue
Block a user