新增书签服务主程序,使用 Gin 框架搭建 HTTP 服务器,并注册书签相关接口处理器。 配置 CORS 中间件以支持跨域请求。服务监听端口为 8081。 feat(user_np): 初始化用户权限服务并注册认证接口 新增用户权限服务主程序,使用 Gin 框架搭建 HTTP 服务器,并注册登录、注册及修改密码等接口处理器。 服务监听端口为 8082。 refactor(config): 重构 OpenAPI 配置文件结构并拆分模块 将原有合并的 OpenAPI 配置文件按功能模块拆分为 bookmark 和 user_np 两个独立目录, 分别管理各自的 server、client 及 API 定义文件,便于后续维护和扩展。 refactor(vfs): 调整虚拟文件系统 API 接口路径与参数定义 更新 VFS API 配置文件,修改部分接口路径及参数结构, 如将文件路径参数由 path 转为 query 参数,并优化响应结构体定义。
355 lines
8.6 KiB
Go
355 lines
8.6 KiB
Go
package handlers
|
|
|
|
import (
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
|
|
api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/vfs"
|
|
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/models"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// // 一行代码生成安全的随机token
|
|
// token := make([]byte, 16)
|
|
// rand.Read(token) // 忽略错误处理以简化代码(生产环境建议处理)
|
|
// tokenStr := hex.EncodeToString(token)
|
|
// adminToken = &tokenStr
|
|
// log.Printf("Admin API Token (for Swagger testing): %s", *adminToken)
|
|
|
|
// if e, err := casbin.NewEnforcer("./config/model.conf", ".data/policy.csv"); err == nil {
|
|
// log.Fatalf("Failed to create casbin enforcer: %v", err)
|
|
// } else {
|
|
// enforcer = e
|
|
// }
|
|
|
|
// ServiceProxy 服务代理接口
|
|
type ServiceProxy interface {
|
|
// Get 从后端服务获取数据
|
|
Get(c *gin.Context, servicePath string, node *models.VfsNode) (any, error)
|
|
|
|
// Create 在后端服务创建资源
|
|
Create(c *gin.Context, servicePath string, node *models.VfsNode, data []byte) (string, error) // 返回创建的资源ID
|
|
|
|
// Update 更新后端服务资源
|
|
Update(c *gin.Context, servicePath string, node *models.VfsNode, data []byte) error
|
|
|
|
// Delete 删除后端服务资源
|
|
Delete(c *gin.Context, servicePath string, node *models.VfsNode) error
|
|
|
|
// GetName 获取代理名称
|
|
GetName() string
|
|
}
|
|
|
|
// ProxyEntry 代理表条目
|
|
type ProxyEntry struct {
|
|
Name string
|
|
Proxy ServiceProxy // 对应的代理实现
|
|
}
|
|
|
|
type VfsImpl struct {
|
|
vfs *models.Vfs
|
|
proxyTable []*ProxyEntry // 动态代理表
|
|
proxyMutex sync.RWMutex // 保护代理表的读写锁
|
|
}
|
|
|
|
func NewVfsHandler(vfs models.Vfs) (*VfsImpl, error) {
|
|
return &VfsImpl{
|
|
vfs: &vfs,
|
|
proxyTable: make([]*ProxyEntry, 0),
|
|
proxyMutex: sync.RWMutex{},
|
|
}, nil
|
|
}
|
|
|
|
// CreateUser implements api.ServerInterface.
|
|
func (v *VfsImpl) CreateUser(c *gin.Context) {
|
|
panic("unimplemented")
|
|
}
|
|
|
|
// DeleteUser implements api.ServerInterface.
|
|
func (v *VfsImpl) DeleteUser(c *gin.Context) {
|
|
panic("unimplemented")
|
|
}
|
|
|
|
// CreateVFSNode implements api.ServerInterface.
|
|
func (v *VfsImpl) CreateVFSNode(c *gin.Context, params api.CreateVFSNodeParams) {
|
|
// 解析路径组件
|
|
parentPath, nodeName, nodeType, err := models.ParsePathComponents(params.Path)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, api.Error{
|
|
Errtype: "error",
|
|
Message: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
// 创建节点
|
|
node, err := v.vfs.CreateNodeByComponents(parentPath, nodeName, nodeType)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, api.Error{
|
|
Errtype: "CreateNodeByComponents",
|
|
Message: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
if nodeType == models.VfsNodeTypeService {
|
|
if !v.Proxy2Service(c, node) {
|
|
v.vfs.DeleteVFSNode(node)
|
|
return
|
|
}
|
|
}
|
|
|
|
// 返回创建成功的节点
|
|
c.JSON(http.StatusCreated, api.VFSNodeResponse{
|
|
Name: node.Name,
|
|
Type: ModelType2ResponseType(node.Type),
|
|
CreatedAt: node.CreatedAt,
|
|
UpdatedAt: node.UpdatedAt,
|
|
})
|
|
}
|
|
|
|
// DeleteVFSNode implements api.ServerInterface.
|
|
func (v *VfsImpl) DeleteVFSNode(c *gin.Context, params api.DeleteVFSNodeParams) {
|
|
node, err := v.vfs.GetNodeByPath(params.Path)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, api.Error{
|
|
Errtype: "error",
|
|
Message: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
if node.Type == models.VfsNodeTypeService {
|
|
if !v.Proxy2Service(c, node) {
|
|
return
|
|
}
|
|
}
|
|
|
|
if err := v.vfs.DeleteVFSNode(node); err != nil {
|
|
c.JSON(http.StatusInternalServerError, api.Error{
|
|
Errtype: "error",
|
|
Message: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
c.JSON(http.StatusNoContent, nil)
|
|
}
|
|
|
|
// GetVFSNode implements api.ServerInterface.
|
|
func (v *VfsImpl) GetVFSNode(c *gin.Context, params api.GetVFSNodeParams) {
|
|
node, err := v.vfs.GetNodeByPath(params.Path)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, api.Error{
|
|
Errtype: "error",
|
|
Message: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
switch node.Type {
|
|
case models.VfsNodeTypeDirectory:
|
|
if entries, err := v.vfs.GetChildren(node.ID); err != nil {
|
|
c.JSON(http.StatusInternalServerError, api.Error{
|
|
Errtype: "error",
|
|
Message: err.Error(),
|
|
})
|
|
return
|
|
} else {
|
|
var responseEntries []api.VFSDirectoryEntry
|
|
for _, entry := range entries {
|
|
responseEntries = append(responseEntries, api.VFSDirectoryEntry{
|
|
Name: entry.Name,
|
|
Type: ModelType2ResponseType(entry.Type),
|
|
})
|
|
}
|
|
c.JSON(http.StatusOK, responseEntries)
|
|
return
|
|
}
|
|
case models.VfsNodeTypeService:
|
|
v.Proxy2Service(c, node)
|
|
default:
|
|
c.JSON(http.StatusBadRequest, api.Error{
|
|
Errtype: "error",
|
|
Message: "Not a directory",
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
// UpdateVFSNode implements api.ServerInterface.
|
|
func (v *VfsImpl) UpdateVFSNode(c *gin.Context, params api.UpdateVFSNodeParams) {
|
|
var req api.UpdateVFSNodeJSONRequestBody
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, api.Error{
|
|
Errtype: "ParameterError",
|
|
Message: "Invalid request parameters",
|
|
})
|
|
return
|
|
}
|
|
|
|
node, err := v.vfs.GetNodeByPath(params.Path)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, api.Error{
|
|
Errtype: "error",
|
|
Message: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
if req.NewName != nil {
|
|
node.Name = *req.NewName
|
|
}
|
|
|
|
// FIXME
|
|
if node.Type == models.VfsNodeTypeService {
|
|
if !v.Proxy2Service(c, node) {
|
|
return
|
|
}
|
|
}
|
|
|
|
// TODO
|
|
if err := v.vfs.UpdateVFSNode(node); err != nil {
|
|
c.JSON(http.StatusInternalServerError, api.Error{
|
|
Errtype: "error",
|
|
Message: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
// Make sure we conform to ServerInterface
|
|
var _ api.ServerInterface = (*VfsImpl)(nil)
|
|
|
|
func ModelType2ResponseType(nodeType models.VfsNodeType) api.VFSNodeType {
|
|
var reponseType api.VFSNodeType
|
|
switch nodeType {
|
|
case models.VfsNodeTypeFile:
|
|
reponseType = api.File
|
|
case models.VfsNodeTypeDirectory:
|
|
reponseType = api.Directory
|
|
case models.VfsNodeTypeService:
|
|
reponseType = api.Service
|
|
}
|
|
return reponseType
|
|
}
|
|
|
|
// FindProxyByServiceName 根据服务节点名称查找对应的代理
|
|
func (v *VfsImpl) FindProxyByServiceName(serviceName string) ServiceProxy {
|
|
v.proxyMutex.RLock()
|
|
defer v.proxyMutex.RUnlock()
|
|
|
|
if serviceName == "" {
|
|
return nil
|
|
}
|
|
|
|
// 根据服务名称匹配前缀
|
|
for _, entry := range v.proxyTable {
|
|
if entry.Name == serviceName {
|
|
return entry.Proxy
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (v *VfsImpl) RegisterProxy(entry *ProxyEntry) {
|
|
v.proxyMutex.Lock()
|
|
defer v.proxyMutex.Unlock()
|
|
|
|
v.proxyTable = append(v.proxyTable, entry)
|
|
}
|
|
|
|
// Proxy2Service 通用服务代理处理函数
|
|
func (v *VfsImpl) Proxy2Service(c *gin.Context, node *models.VfsNode) bool {
|
|
exts := strings.Split(node.Name, ".")
|
|
var serviceName = exts[1]
|
|
log.Println("Proxy2Service: ", serviceName)
|
|
// 查找对应的代理
|
|
proxy := v.FindProxyByServiceName(serviceName)
|
|
if proxy == nil {
|
|
c.JSON(http.StatusNotImplemented, api.Error{
|
|
Errtype: "error",
|
|
Message: "Service proxy not found for: " + serviceName,
|
|
})
|
|
return false
|
|
}
|
|
|
|
// 根据HTTP方法调用相应的代理方法
|
|
switch c.Request.Method {
|
|
case http.MethodGet:
|
|
result, err := proxy.Get(c, serviceName, node)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, api.Error{
|
|
Errtype: "error",
|
|
Message: "Failed to get service data: " + err.Error(),
|
|
})
|
|
return false
|
|
}
|
|
|
|
c.JSON(http.StatusOK, result)
|
|
return true
|
|
case http.MethodPost:
|
|
// 读取请求体数据
|
|
data, err := c.GetRawData()
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, api.Error{
|
|
Errtype: "error",
|
|
Message: "Failed to read request data: " + err.Error(),
|
|
})
|
|
return false
|
|
}
|
|
|
|
resourceID, err := proxy.Create(c, serviceName, node, data)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, api.Error{
|
|
Errtype: "error",
|
|
Message: "Failed to create service resource: " + err.Error(),
|
|
})
|
|
return false
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, gin.H{"resource_id": resourceID})
|
|
return true
|
|
case http.MethodPut, http.MethodPatch:
|
|
// 读取请求体数据
|
|
data, err := c.GetRawData()
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, api.Error{
|
|
Errtype: "error",
|
|
Message: "Failed to read request data: " + err.Error(),
|
|
})
|
|
return false
|
|
}
|
|
|
|
err = proxy.Update(c, serviceName, node, data)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, api.Error{
|
|
Errtype: "error",
|
|
Message: "Failed to update service resource: " + err.Error(),
|
|
})
|
|
return false
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Updated successfully"})
|
|
return true
|
|
case http.MethodDelete:
|
|
err := proxy.Delete(c, serviceName, node)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, api.Error{
|
|
Errtype: "error",
|
|
Message: "Failed to delete service resource: " + err.Error(),
|
|
})
|
|
return false
|
|
}
|
|
|
|
c.JSON(http.StatusNoContent, nil)
|
|
return true
|
|
default:
|
|
c.JSON(http.StatusMethodNotAllowed, api.Error{
|
|
Errtype: "error",
|
|
Message: "Method not allowed",
|
|
})
|
|
return false
|
|
}
|
|
}
|