refactor(bookmark): 重构书签服务入口文件并整合用户权限功能
将 bookmark.go 重命名为 main.go,并调整包引用路径。将 bookmarks 和 user_np 两个模块的处理逻辑合并到同一个服务中,统一注册路由。同时更新了相关 API 的引用路径,确保生成代码与内部实现正确绑定。 此外,移除了独立的 user_np 服务入口文件,其功能已整合至 bookmark 服务中。 配置文件中调整了 user_np 和 vfs 服务的端口及部分接口定义,完善了用户 相关操作的路径参数和请求体结构。
This commit is contained in:
@ -3,8 +3,9 @@ package main
|
||||
import (
|
||||
"log"
|
||||
|
||||
bookmarks "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/bookmarks"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/handlers"
|
||||
bookmarks_api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/bookmarks"
|
||||
user_np_api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/user_np"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/bookmarks"
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@ -25,12 +26,20 @@ func main() {
|
||||
{
|
||||
// create a type that satisfies the `api.ServerInterface`,
|
||||
// which contains an implementation of every operation from the generated code
|
||||
if server, err := handlers.NewBookMarks("./data/bookmark.sqlite3"); err != nil {
|
||||
if server, err := bookmarks.NewBookMarks("./data/bookmark.sqlite3"); err != nil {
|
||||
log.Fatal("Failed to create bookmarks server:", err)
|
||||
} else if permission, err := handlers.NewBookMarkPermission(); err != nil || permission == nil {
|
||||
} else if permission, err := bookmarks.NewBookMarkPermission(); err != nil || permission == nil {
|
||||
log.Fatal("Failed to create bookmarks permission:", err)
|
||||
} else {
|
||||
bookmarks.RegisterHandlersWithOptions(api_router, server, *permission)
|
||||
bookmarks_api.RegisterHandlersWithOptions(api_router, server, *permission)
|
||||
}
|
||||
|
||||
// create a type that satisfies the `api.ServerInterface`,
|
||||
// which contains an implementation of every operation from the generated code
|
||||
if server, err := bookmarks.NewUserNP("./data/user_np.sqlite3"); err != nil {
|
||||
log.Fatal("Failed to create user_np server:", err)
|
||||
} else {
|
||||
user_np_api.RegisterHandlers(api_router, server)
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
user_np "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/user_np"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/handlers"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// gin.SetMode(gin.ReleaseMode)
|
||||
router := gin.Default()
|
||||
|
||||
api_router := router.Group("/api")
|
||||
{
|
||||
// create a type that satisfies the `api.ServerInterface`,
|
||||
// which contains an implementation of every operation from the generated code
|
||||
if server, err := handlers.NewUserNP("./data/user_np.sqlite3"); err != nil {
|
||||
log.Fatal("Failed to create user_np server:", err)
|
||||
} else {
|
||||
user_np.RegisterHandlers(api_router, server)
|
||||
}
|
||||
}
|
||||
|
||||
var listener = "localhost:8082"
|
||||
log.Printf("Starting server at http://%s", listener)
|
||||
log.Fatal(router.Run(listener))
|
||||
}
|
@ -4,7 +4,7 @@ info:
|
||||
description: 用户节点权限相关操作(user_name and password)
|
||||
version: '1.0'
|
||||
servers:
|
||||
- url: http://localhost:8082/api
|
||||
- url: http://localhost:8081/api
|
||||
description: 开发环境
|
||||
- url: https://api.zzyxyz.com/api
|
||||
description: 生产环境
|
||||
@ -90,8 +90,6 @@ components:
|
||||
properties:
|
||||
token:
|
||||
type: string
|
||||
expires_in:
|
||||
type: integer
|
||||
user_id:
|
||||
type: integer
|
||||
format: int64
|
||||
@ -114,11 +112,14 @@ components:
|
||||
required:
|
||||
- old_password
|
||||
- new_password
|
||||
- user_name
|
||||
properties:
|
||||
old_password:
|
||||
type: string
|
||||
new_password:
|
||||
type: string
|
||||
user_name:
|
||||
type: string
|
||||
|
||||
securitySchemes:
|
||||
ApiKeyAuth:
|
||||
|
@ -4,43 +4,47 @@ info:
|
||||
description: 虚拟文件系统API服务
|
||||
version: '1.0'
|
||||
servers:
|
||||
- url: http://localhost:8083/api
|
||||
- url: http://localhost:8080/api
|
||||
description: 开发环境
|
||||
- url: https://api.zzyxyz.com/api
|
||||
description: 生产环境
|
||||
tags:
|
||||
- name: vfs
|
||||
description: 虚拟文件系统相关操作
|
||||
- name: user
|
||||
description: 用户相关操作
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
|
||||
paths:
|
||||
/vfs/v1/users:
|
||||
/vfs/v1/users/{username}:
|
||||
parameters:
|
||||
- name: username
|
||||
in: path
|
||||
example: user
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
|
||||
post:
|
||||
summary: 创建用户
|
||||
description: 创建一个用户
|
||||
operationId: createUser
|
||||
tags: [vfs]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
description: 用户名
|
||||
tags: [user]
|
||||
responses:
|
||||
'201':
|
||||
description: 创建成功
|
||||
content:
|
||||
application/json:
|
||||
headers:
|
||||
X-VFS-Token:
|
||||
schema:
|
||||
type: string
|
||||
description: X-VFS-TOKEN
|
||||
description: 认证令牌
|
||||
|
||||
delete:
|
||||
summary: 删除用户
|
||||
description: 删除一个用户
|
||||
operationId: deleteUser
|
||||
tags: [vfs]
|
||||
tags: [user]
|
||||
responses:
|
||||
'204':
|
||||
description: 删除成功
|
||||
@ -106,7 +110,7 @@ paths:
|
||||
operationId: createVFSNode
|
||||
tags: [vfs]
|
||||
requestBody:
|
||||
required: true
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
@ -130,19 +134,25 @@ paths:
|
||||
|
||||
patch:
|
||||
summary: 修改文件或修改目录
|
||||
description: 修改/移动/重命名 已存在的 文件/目录
|
||||
description: 修改/移动/重命名/复制 已存在的 文件/目录
|
||||
operationId: updateVFSNode
|
||||
tags: [vfs]
|
||||
parameters:
|
||||
- name: op
|
||||
in: query
|
||||
description: 操作模式
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
enum: [move, rename, change, copy]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
new_name:
|
||||
type: string
|
||||
description: 新名称(用于重命名)
|
||||
type: string
|
||||
description: 目的文件夹路径 / 文件名路径
|
||||
example: "/home/"
|
||||
responses:
|
||||
'200':
|
||||
description: 操作成功
|
||||
@ -182,6 +192,7 @@ components:
|
||||
type: apiKey
|
||||
in: header
|
||||
name: X-VFS-Token
|
||||
|
||||
responses:
|
||||
ServerInternalError:
|
||||
description: 服务器内部错误
|
||||
@ -189,12 +200,14 @@ components:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
Unauthorized:
|
||||
description: 未授权
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
schemas:
|
||||
VFSNodeType:
|
||||
type: string
|
||||
@ -222,6 +235,7 @@ components:
|
||||
- type
|
||||
- created_at
|
||||
- updated_at
|
||||
|
||||
VFSDirectoryEntry:
|
||||
type: object
|
||||
properties:
|
||||
|
23
go.mod
23
go.mod
@ -12,6 +12,29 @@ require (
|
||||
|
||||
require github.com/gin-contrib/cors v1.7.6
|
||||
|
||||
require (
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
|
||||
github.com/casbin/casbin/v2 v2.127.0 // indirect
|
||||
github.com/casbin/govaluate v1.3.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/spf13/viper v1.21.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
|
57
go.sum
57
go.sum
@ -1,13 +1,20 @@
|
||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||
github.com/bmatcuk/doublestar v1.1.1 h1:YroD6BJCZBYx06yYFEWvUuKVWQn3vLLQAVmDmvTSaiQ=
|
||||
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w=
|
||||
github.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/casbin/casbin/v2 v2.127.0 h1:UGK3uO/8cOslnNqFUJ4xzm/bh+N+o45U7cSolaFk38c=
|
||||
github.com/casbin/casbin/v2 v2.127.0/go.mod h1:n4uZK8+tCMvcD6EVQZI90zKAok8iHAvEypcMJVKhGF0=
|
||||
github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc=
|
||||
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
@ -22,6 +29,8 @@ github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5ql
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk=
|
||||
@ -32,6 +41,8 @@ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-openapi/jsonpointer v0.22.0 h1:TmMhghgNef9YXxTu1tOopo+0BGEytxA+okbry0HjZsM=
|
||||
github.com/go-openapi/jsonpointer v0.22.0/go.mod h1:xt3jV88UtExdIkkL7NloURjRQjbeUgcxFblMjq2iaiU=
|
||||
github.com/go-openapi/swag/jsonname v0.24.0 h1:2wKS9bgRV/xB8c62Qg16w4AUiIrqqiniJFtZGi3dg5k=
|
||||
@ -47,10 +58,13 @@ github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAu
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
@ -64,6 +78,9 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@ -92,6 +109,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
@ -134,26 +153,53 @@ github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX
|
||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8=
|
||||
github.com/speakeasy-api/jsonpath v0.6.0/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw=
|
||||
github.com/speakeasy-api/openapi-overlay v0.10.2 h1:VOdQ03eGKeiHnpb1boZCGm7x8Haj6gST0P3SGTX95GU=
|
||||
github.com/speakeasy-api/openapi-overlay v0.10.2/go.mod h1:n0iOU7AqKpNFfEt6tq7qYITC4f0yzVVdFw0S7hukemg=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||
@ -161,6 +207,10 @@ github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2W
|
||||
github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk=
|
||||
github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/arch v0.21.0 h1:iTC9o7+wP6cPWpDWkivCvQFGAHDQ59SrSxsLPcnkArw=
|
||||
golang.org/x/arch v0.21.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@ -172,6 +222,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
@ -189,17 +240,22 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
@ -211,6 +267,7 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
|
@ -1,12 +1,12 @@
|
||||
// internal/handlers/note_link.go
|
||||
|
||||
package handlers
|
||||
package bookmarks
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/bookmarks"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/models"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/bookmarks/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"gorm.io/driver/sqlite"
|
||||
@ -34,7 +34,6 @@ func AuthMiddleware() api.MiddlewareFunc {
|
||||
// 提取 API Key
|
||||
apiKey := c.GetHeader("X-BookMark-Token")
|
||||
|
||||
return
|
||||
// 验证 API Key(您需要实现这个逻辑)
|
||||
if apiKey == "" || !validateApiKey(apiKey) {
|
||||
c.JSON(http.StatusUnauthorized, api.Error{
|
||||
@ -55,26 +54,6 @@ func NewBookMarkPermission() (*api.GinServerOptions, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
func NewBookMarks(dbPath string) (*BookMarksImpl, error) {
|
||||
var err error
|
||||
var db *gorm.DB
|
||||
@ -236,5 +215,25 @@ func (b *BookMarksImpl) UpdateBookmark(c *gin.Context, id int64) {
|
||||
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 {
|
||||
}
|
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
|
||||
}
|
@ -1,19 +1,24 @@
|
||||
// internal/handlers/user_np.go
|
||||
|
||||
package handlers
|
||||
package bookmarks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/user_np"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/models"
|
||||
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
|
||||
db *gorm.DB
|
||||
client *client.ClientWithResponses
|
||||
vfsToken string
|
||||
}
|
||||
|
||||
func NewUserNP(dbPath string) (*UserNPImpl, error) {
|
||||
@ -29,8 +34,62 @@ func NewUserNP(dbPath string) (*UserNPImpl, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := client.NewClientWithResponses("http://localhost:8080/api")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &UserNPImpl{db: db}, nil
|
||||
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 用户登录
|
||||
@ -54,19 +113,8 @@ func (u *UserNPImpl) PostAuthLogin(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 生成JWT token
|
||||
token, err := user.GenerateSimpleJWT()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "无法生成访问令牌"})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新用户token字段(可选)
|
||||
user.Token = &token
|
||||
u.db.Save(&user)
|
||||
|
||||
c.JSON(http.StatusOK, api.LoginResponse{
|
||||
Token: &token,
|
||||
Token: user.Token,
|
||||
UserId: &user.ID,
|
||||
})
|
||||
}
|
||||
@ -101,7 +149,14 @@ func (u *UserNPImpl) PostAuthRegister(c *gin.Context) {
|
||||
// 保存到数据库
|
||||
if err := u.db.Create(&user).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "用户创建失败"})
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
@ -116,13 +171,6 @@ func (u *UserNPImpl) PutAuthPassword(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 验证token并获取用户名
|
||||
username, err := models.CheckSimpleJWT(authHeader)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var req api.PutAuthPasswordJSONRequestBody
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
@ -131,7 +179,7 @@ func (u *UserNPImpl) PutAuthPassword(c *gin.Context) {
|
||||
|
||||
// 查找用户
|
||||
var user models.UserNP
|
||||
if err := u.db.Where("username = ?", username).First(&user).Error; err != nil {
|
||||
if err := u.db.Where("username = ?", req.UserName).First(&user).Error; err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户不存在"})
|
||||
return
|
||||
}
|
||||
@ -154,17 +202,6 @@ func (u *UserNPImpl) PutAuthPassword(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 生成新的JWT token
|
||||
token, err := user.GenerateSimpleJWT()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "无法生成新的访问令牌"})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新用户token字段
|
||||
user.Token = &token
|
||||
u.db.Save(&user)
|
||||
|
||||
c.JSON(http.StatusOK, nil)
|
||||
}
|
||||
|
@ -1,354 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
// internal/models/user_np.go
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"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"`
|
||||
}
|
||||
|
||||
// JWTSecret JWT签名密钥(在实际应用中应该从环境变量或配置文件中读取)
|
||||
var JWTSecret = []byte("your-secret-key-change-in-production")
|
||||
|
||||
// SimpleClaims 简单的JWT声明结构体
|
||||
type SimpleClaims struct {
|
||||
Username string `json:"username"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// GenerateSimpleJWT 生成简单的JWT Token
|
||||
func (u *UserNP) GenerateSimpleJWT() (string, error) {
|
||||
expirationTime := time.Now().Add(24 * time.Hour)
|
||||
|
||||
claims := &SimpleClaims{
|
||||
Username: u.Username,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(expirationTime),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||
Issuer: "zzyxyz_user_np_api",
|
||||
Subject: u.Username,
|
||||
ID: string(rune(u.ID)), // 将用户ID作为JWT ID
|
||||
},
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := token.SignedString(JWTSecret)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tokenString, nil
|
||||
}
|
||||
|
||||
// CheckSimpleJWT 验证JWT Token
|
||||
func CheckSimpleJWT(tokenString string) (string, error) {
|
||||
claims := &SimpleClaims{}
|
||||
|
||||
// 解析token
|
||||
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
|
||||
return JWTSecret, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
// 检查是否是token过期错误
|
||||
if errors.Is(err, jwt.ErrTokenExpired) {
|
||||
return "", errors.New("token已过期")
|
||||
}
|
||||
return "", errors.New("无效的token")
|
||||
}
|
||||
|
||||
// 验证token有效性
|
||||
if !token.Valid {
|
||||
return "", errors.New("无效的token")
|
||||
}
|
||||
|
||||
// 返回用户名
|
||||
return claims.Username, nil
|
||||
}
|
@ -2,8 +2,10 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
@ -32,6 +34,11 @@ type VfsNode struct {
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type VfsUser struct {
|
||||
Name string
|
||||
Token string
|
||||
}
|
||||
|
||||
type VfsDirEntry struct {
|
||||
ID uint64
|
||||
Name string
|
||||
@ -45,7 +52,6 @@ func NewVfs(dbPath string) (*Vfs, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建表
|
||||
_, err = db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS vfs_nodes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@ -60,9 +66,146 @@ func NewVfs(dbPath string) (*Vfs, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS vfs_users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
token TEXT NOT NULL UNIQUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = createInitialDirectories(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Vfs{DB: db}, nil
|
||||
}
|
||||
|
||||
// 创建初始目录结构
|
||||
func createInitialDirectories(db *sql.DB) error {
|
||||
var err error
|
||||
// 创建 /home 目录
|
||||
_, err = db.Exec(`
|
||||
INSERT OR IGNORE INTO vfs_nodes (name, parent_id, type, created_at, updated_at)
|
||||
VALUES ('home', 0, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
|
||||
VfsNodeTypeDirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建 /public 目录
|
||||
_, err = db.Exec(`
|
||||
INSERT OR IGNORE INTO vfs_nodes (name, parent_id, type, created_at, updated_at)
|
||||
VALUES ('public', 0, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
|
||||
VfsNodeTypeDirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateToken() string {
|
||||
bytes := make([]byte, 16)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
// fallback to time-based token
|
||||
return fmt.Sprintf("%x", time.Now().UnixNano())
|
||||
}
|
||||
return fmt.Sprintf("%x", bytes)
|
||||
}
|
||||
|
||||
// 添加用户相关操作
|
||||
func (v *Vfs) CreateUser(username string) (string, error) {
|
||||
// 生成随机token
|
||||
token := generateToken()
|
||||
|
||||
// 检查用户是否已存在
|
||||
_, err := v.GetUserByName(username)
|
||||
if err == nil {
|
||||
return "", errors.New("user already exists")
|
||||
}
|
||||
|
||||
// 插入用户(不使用事务)
|
||||
_, err = v.DB.Exec("INSERT INTO vfs_users (name, token) VALUES (?, ?)", username, token)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 使用 defer 确保出错时能清理已创建的用户
|
||||
defer func() {
|
||||
if err != nil {
|
||||
v.DB.Exec("DELETE FROM vfs_users WHERE name = ? AND token = ?", username, token)
|
||||
}
|
||||
}()
|
||||
|
||||
// 确保 /home 目录存在
|
||||
homeDir, getHomeErr := v.GetNodeByPath("/home/")
|
||||
if getHomeErr != nil {
|
||||
// 如果 /home 不存在,创建它
|
||||
homeDir = &VfsNode{
|
||||
Name: "home",
|
||||
ParentID: 0,
|
||||
Type: VfsNodeTypeDirectory,
|
||||
}
|
||||
if createHomeErr := v.CreateVFSNode(homeDir); createHomeErr != nil {
|
||||
err = createHomeErr
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// 创建用户目录 /home/username
|
||||
userDir := &VfsNode{
|
||||
Name: username,
|
||||
ParentID: homeDir.ID,
|
||||
Type: VfsNodeTypeDirectory,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
if createDirErr := v.CreateVFSNode(userDir); createDirErr != nil {
|
||||
err = createDirErr
|
||||
return "", err
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (v *Vfs) DeleteUser(username string) error {
|
||||
// TODO: 递归删除用户相关文件
|
||||
// 这里暂时只删除用户记录
|
||||
_, err := v.DB.Exec("DELETE FROM vfs_users WHERE name = ?", username)
|
||||
return err
|
||||
}
|
||||
|
||||
func (v *Vfs) GetUserByToken(token string) (*VfsUser, error) {
|
||||
user := &VfsUser{}
|
||||
err := v.DB.QueryRow("SELECT name, token FROM vfs_users WHERE token = ?", token).
|
||||
Scan(&user.Name, &user.Token)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (v *Vfs) GetUserByName(username string) (*VfsUser, error) {
|
||||
user := &VfsUser{}
|
||||
err := v.DB.QueryRow("SELECT name, token FROM vfs_users WHERE name = ?", username).
|
||||
Scan(&user.Name, &user.Token)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// GetChildrenID 获取目录下所有子项的 ID
|
||||
func (v *Vfs) GetChildren(parentID uint64) ([]VfsDirEntry, error) {
|
||||
rows, err := v.DB.Query("SELECT id, name, type FROM vfs_nodes WHERE parent_id = ?", parentID)
|
||||
@ -253,6 +396,47 @@ func ParsePathComponents(pathStr string) (parentPath, nodeName string, nodeType
|
||||
return parentPath, nodeName, nodeType, nil
|
||||
}
|
||||
|
||||
// MoveToPath 将节点移动到指定路径
|
||||
func (v *Vfs) MoveToPath(node *VfsNode, destPath string) error {
|
||||
// 1. 解析目标路径
|
||||
parentPath, nodeName, _, err := ParsePathComponents(destPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid destination path: %w", err)
|
||||
}
|
||||
|
||||
// 2. 查找目标父节点
|
||||
parentID, err := v.GetParentID(parentPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find parent directory '%s': %w", parentPath, err)
|
||||
}
|
||||
|
||||
// 3. 检查目标位置是否已存在同名节点
|
||||
_, err = v.GetNodeByParentIDAndName(parentID, nodeName)
|
||||
if err == nil {
|
||||
return fmt.Errorf("destination path '%s' already exists", destPath)
|
||||
}
|
||||
if err.Error() != "node not found" {
|
||||
return fmt.Errorf("error checking destination path: %w", err)
|
||||
}
|
||||
|
||||
// 4. 更新节点的父节点ID和名称
|
||||
node.ParentID = parentID
|
||||
node.Name = nodeName
|
||||
node.UpdatedAt = time.Now()
|
||||
|
||||
// 5. 更新数据库中的节点信息
|
||||
_, err = v.DB.Exec(`
|
||||
UPDATE vfs_nodes
|
||||
SET name = ?, parent_id = ?, updated_at = ?
|
||||
WHERE id = ?`,
|
||||
node.Name, node.ParentID, node.UpdatedAt, node.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update node: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Vfs) CreateVFSNode(p *VfsNode) error {
|
||||
_, err := v.DB.Exec(`
|
||||
INSERT INTO vfs_nodes (name, parent_id, type, created_at, updated_at)
|
@ -3,7 +3,7 @@ package models_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/models"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs/models"
|
||||
)
|
||||
|
||||
func TestParsePathComponents(t *testing.T) {
|
360
internal/vfs/vfs.go
Normal file
360
internal/vfs/vfs.go
Normal file
@ -0,0 +1,360 @@
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/vfs"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs/models"
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/casbin/casbin/v2/model"
|
||||
fileadapter "github.com/casbin/casbin/v2/persist/file-adapter"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type VfsImpl struct {
|
||||
vfs *models.Vfs
|
||||
enfocer *casbin.Enforcer
|
||||
config VFSConfig
|
||||
proxyTable []*ProxyEntry // 动态代理表
|
||||
proxyMutex sync.RWMutex // 保护代理表的读写锁
|
||||
}
|
||||
|
||||
func NewVfsHandler(config *Config) (*VfsImpl, error) {
|
||||
var err error
|
||||
|
||||
vfs, err := models.NewVfs(config.VFS.DbPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var policyPath = config.VFS.PolicyPath
|
||||
// 检查策略文件是否存在,如果不存在则创建
|
||||
if _, err := os.Stat(policyPath); os.IsNotExist(err) {
|
||||
// 确保目录存在
|
||||
dir := filepath.Dir(policyPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
log.Fatalf("error: failed to create policy directory: %s", err)
|
||||
}
|
||||
|
||||
// 创建空的策略文件
|
||||
file, err := os.Create(policyPath)
|
||||
if err != nil {
|
||||
log.Fatalf("error: failed to create policy file: %s", err)
|
||||
}
|
||||
file.Close()
|
||||
|
||||
log.Printf("Created policy file: %s", policyPath)
|
||||
}
|
||||
|
||||
a := fileadapter.NewAdapter(policyPath)
|
||||
if a == nil {
|
||||
log.Fatalf("error: adapter: %s", err)
|
||||
}
|
||||
|
||||
m, err := model.NewModelFromString(CasbinModel)
|
||||
if err != nil {
|
||||
log.Fatalf("error: model: %s", err)
|
||||
}
|
||||
|
||||
e, err := casbin.NewEnforcer(m, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("Admin Token: %s", config.VFS.AdminToken)
|
||||
log.Printf("Register Token: %s", config.VFS.RegisterToken)
|
||||
|
||||
return &VfsImpl{
|
||||
vfs: vfs,
|
||||
enfocer: e,
|
||||
config: config.VFS,
|
||||
proxyTable: make([]*ProxyEntry, 0),
|
||||
proxyMutex: sync.RWMutex{},
|
||||
}, 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
|
||||
}
|
||||
|
||||
// CreateVFSNode implements api.ServerInterface.
|
||||
func (v *VfsImpl) CreateVFSNode(c *gin.Context, params api.CreateVFSNodeParams) {
|
||||
if !v.CheckPermissionMiddleware(c, params.Path) {
|
||||
return
|
||||
}
|
||||
|
||||
// 解析路径组件
|
||||
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) {
|
||||
// Rollback
|
||||
err := v.vfs.DeleteVFSNode(node)
|
||||
if err != nil {
|
||||
// FIXME: 需要解决这种原子性
|
||||
panic("Maybe Consistency Error")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 返回创建成功的节点
|
||||
c.JSON(http.StatusCreated, api.VFSNodeResponse{
|
||||
Name: node.Name,
|
||||
Type: ModelType2ResponseType(node.Type),
|
||||
CreatedAt: node.CreatedAt,
|
||||
UpdatedAt: node.UpdatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
// GetVFSNode implements api.ServerInterface.
|
||||
func (v *VfsImpl) GetVFSNode(c *gin.Context, params api.GetVFSNodeParams) {
|
||||
if !v.CheckPermissionMiddleware(c, params.Path) {
|
||||
return
|
||||
}
|
||||
|
||||
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 valid node type",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteVFSNode implements api.ServerInterface.
|
||||
func (v *VfsImpl) DeleteVFSNode(c *gin.Context, params api.DeleteVFSNodeParams) {
|
||||
if !v.CheckPermissionMiddleware(c, params.Path) {
|
||||
return
|
||||
}
|
||||
|
||||
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.VfsNodeTypeService:
|
||||
if !v.Proxy2Service(c, node) {
|
||||
return
|
||||
}
|
||||
case models.VfsNodeTypeDirectory:
|
||||
if children, err := v.vfs.GetChildren(node.ID); err != nil || len(children) != 0 {
|
||||
c.JSON(http.StatusInternalServerError, api.Error{
|
||||
Errtype: "error",
|
||||
Message: "the folder is not empty",
|
||||
})
|
||||
return
|
||||
}
|
||||
default:
|
||||
c.JSON(http.StatusInternalServerError, api.Error{
|
||||
Errtype: "error",
|
||||
Message: "node type not supported",
|
||||
})
|
||||
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)
|
||||
}
|
||||
|
||||
// UpdateVFSNode implements api.ServerInterface.
|
||||
func (v *VfsImpl) UpdateVFSNode(c *gin.Context, params api.UpdateVFSNodeParams) {
|
||||
if !v.CheckPermissionMiddleware(c, params.Path) {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
switch params.Op {
|
||||
case api.Rename:
|
||||
if req == "" {
|
||||
c.JSON(http.StatusBadRequest, api.Error{
|
||||
Errtype: "ParameterError",
|
||||
Message: "Invalid request parameters",
|
||||
})
|
||||
return
|
||||
}
|
||||
// FIXME: 对于service,后缀属性需要强制保留
|
||||
if err := v.vfs.UpdateVFSNode(node); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, api.Error{
|
||||
Errtype: "error",
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
case api.Change:
|
||||
if node.Type != models.VfsNodeTypeFile {
|
||||
c.JSON(http.StatusBadRequest, api.Error{
|
||||
Errtype: "ParameterError",
|
||||
Message: "Invalid request parameters, node type must be a service",
|
||||
})
|
||||
return
|
||||
}
|
||||
if !v.Proxy2Service(c, node) {
|
||||
return
|
||||
}
|
||||
case api.Move:
|
||||
// FIXME: 需要添加权限控制
|
||||
v.vfs.MoveToPath(node, req)
|
||||
|
||||
case api.Copy:
|
||||
fallthrough
|
||||
default:
|
||||
c.JSON(http.StatusInternalServerError, api.Error{
|
||||
Errtype: "error",
|
||||
Message: "op type not supported",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// CreateUser implements api.ServerInterface.
|
||||
func (v *VfsImpl) CreateUser(c *gin.Context, username string) {
|
||||
token := c.GetHeader("X-VFS-Token")
|
||||
if token != v.config.RegisterToken || token != v.config.AdminToken {
|
||||
c.JSON(http.StatusForbidden, api.Error{
|
||||
Errtype: "AccessDenied",
|
||||
Message: "Access denied",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
token, err := v.vfs.CreateUser(username)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, api.Error{
|
||||
Errtype: "CreateUserError",
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
_, err = v.enfocer.AddRoleForUser(username, "user")
|
||||
if err != nil {
|
||||
log.Printf("Failed to add role for user %s: %v", username, err)
|
||||
}
|
||||
v.enfocer.SavePolicy()
|
||||
|
||||
// 根据API文档,token应该通过响应头返回
|
||||
c.Header("X-VFS-Token", token)
|
||||
c.Status(http.StatusCreated)
|
||||
}
|
||||
|
||||
// DeleteUser implements api.ServerInterface.
|
||||
func (v *VfsImpl) DeleteUser(c *gin.Context, username string) {
|
||||
token := c.GetHeader("X-VFS-Token")
|
||||
user, err := v.vfs.GetUserByToken(token)
|
||||
if err != nil || user.Name != username {
|
||||
c.JSON(http.StatusForbidden, api.Error{
|
||||
Errtype: "AccessDenied",
|
||||
Message: "Access denied",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err = v.vfs.DeleteUser(username)
|
||||
if err != nil {
|
||||
if err.Error() == "user not found" {
|
||||
c.JSON(http.StatusNotFound, api.Error{
|
||||
Errtype: "UserNotFoundError",
|
||||
Message: "User not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, api.Error{
|
||||
Errtype: "DeleteUserError",
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 根据API文档,删除成功返回204状态码
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// Make sure we conform to ServerInterface
|
||||
var _ api.ServerInterface = (*VfsImpl)(nil)
|
89
internal/vfs/vfs_auth.go
Normal file
89
internal/vfs/vfs_auth.go
Normal file
@ -0,0 +1,89 @@
|
||||
package vfs
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/vfs"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
//go:embed vfs_model.conf
|
||||
var CasbinModel string
|
||||
|
||||
func NewVfsPermission() (*api.GinServerOptions, error) {
|
||||
return &api.GinServerOptions{
|
||||
Middlewares: []api.MiddlewareFunc{VfsMiddleware()},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v *VfsImpl) CheckPermission(token, path, action string) (bool, error) {
|
||||
// 根据 token 获取用户信息
|
||||
user, err := v.vfs.GetUserByToken(token)
|
||||
if err != nil {
|
||||
// 匿名用户
|
||||
user = &models.VfsUser{Name: "", Token: ""}
|
||||
}
|
||||
|
||||
// 特殊处理:admin 用户拥有所有权限
|
||||
if token == v.config.AdminToken && len(token) != 0 { // admin 用户拥有所有权限
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// 允许任何人读取 public 目录
|
||||
if strings.HasPrefix(path, "/public") && action == "GET" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// 如果是普通用户访问自己的主目录,则允许
|
||||
if user.Name != "" && strings.HasPrefix(path, "/home/"+user.Name) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// 构造 Casbin 请求
|
||||
// 对于普通用户,需要将策略中的 {{username}} 替换为实际用户名
|
||||
obj := path
|
||||
sub := user.Name
|
||||
// 使用 Casbin 检查权限
|
||||
allowed, err := v.enfocer.Enforce(sub, obj, action)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return allowed, nil
|
||||
}
|
||||
|
||||
func (v *VfsImpl) CheckPermissionMiddleware(c *gin.Context, path string) bool {
|
||||
token := c.GetHeader("X-VFS-Token")
|
||||
allowed, err := v.CheckPermission(token, path, c.Request.Method)
|
||||
// log.Println("CheckPermission:", allowed, err)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, api.Error{
|
||||
Errtype: "PermissionCheckError",
|
||||
Message: "Failed to check permission: " + err.Error(),
|
||||
})
|
||||
return false
|
||||
}
|
||||
if !allowed {
|
||||
c.JSON(http.StatusForbidden, api.Error{
|
||||
Errtype: "AccessDenied",
|
||||
Message: "Access denied",
|
||||
})
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func VfsMiddleware() api.MiddlewareFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 检查当前请求是否需要认证
|
||||
if _, exists := c.Get(api.ApiKeyAuthScopes); exists {
|
||||
// 提取 API Key
|
||||
apiKey := c.GetHeader("X-VFS-Token")
|
||||
c.Set(api.ApiKeyAuthScopes, apiKey)
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
75
internal/vfs/vfs_config.go
Normal file
75
internal/vfs/vfs_config.go
Normal file
@ -0,0 +1,75 @@
|
||||
// internal/handlers/vfs/vfs_config.go
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
VFS VFSConfig `mapstructure:"vfs"`
|
||||
Services ServicesConfig `mapstructure:"services"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Address string `mapstructure:"address"`
|
||||
Mode string `mapstructure:"mode"`
|
||||
}
|
||||
|
||||
type VFSConfig struct {
|
||||
DbPath string `mapstructure:"db_path"`
|
||||
PolicyPath string `mapstructure:"policy_path"`
|
||||
AdminToken string `mapstructure:"admin_token"`
|
||||
RegisterToken string `mapstructure:"regiser_token"`
|
||||
Debug bool `mapstructure:"debug"`
|
||||
}
|
||||
|
||||
type ServicesConfig struct {
|
||||
Bookmark BookmarkServiceConfig `mapstructure:"bookmark"`
|
||||
}
|
||||
|
||||
type BookmarkServiceConfig struct {
|
||||
URL string `mapstructure:"url"`
|
||||
}
|
||||
|
||||
func genrateRandomToken(length int) string {
|
||||
// 生成随机字符串
|
||||
return "random_token"
|
||||
}
|
||||
|
||||
// LoadConfig 加载配置
|
||||
func LoadConfig() (*Config, error) {
|
||||
viper.SetConfigName("vfs_config")
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AddConfigPath(".")
|
||||
viper.AddConfigPath("./config")
|
||||
viper.AddConfigPath("config")
|
||||
|
||||
// 设置默认值
|
||||
viper.SetDefault("server.address", "localhost:8080")
|
||||
viper.SetDefault("server.mode", "release")
|
||||
viper.SetDefault("vfs.db_path", "./data/vfs.sqlite3")
|
||||
viper.SetDefault("vfs.policy_path", "./data/policy.csv")
|
||||
viper.SetDefault("vfs.debug", false)
|
||||
viper.SetDefault("services.bookmark.url", "http://localhost:8081/api")
|
||||
|
||||
viper.SetDefault("vfs.admin_token", genrateRandomToken(64))
|
||||
viper.SetDefault("vfs.regiser_token", genrateRandomToken(64))
|
||||
// 自动读取环境变量
|
||||
viper.AutomaticEnv()
|
||||
|
||||
// 读取配置文件
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
log.Printf("Warning: unable to read config file: %v. Using defaults and environment variables.", err)
|
||||
}
|
||||
|
||||
var config Config
|
||||
if err := viper.Unmarshal(&config); err != nil {
|
||||
return nil, fmt.Errorf("unable to decode into struct: %v", err)
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
155
internal/vfs/vfs_service.go
Normal file
155
internal/vfs/vfs_service.go
Normal file
@ -0,0 +1,155 @@
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/vfs"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 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
|
||||
MatchExt string
|
||||
Proxy ServiceProxy // 对应的代理实现
|
||||
}
|
||||
|
||||
// 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.MatchExt == 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
|
||||
}
|
||||
}
|
@ -8,8 +8,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/bookmarks"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/handlers"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/models"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@ -17,15 +17,18 @@ type VfsBookMarkService struct {
|
||||
client *api.ClientWithResponses
|
||||
}
|
||||
|
||||
func NewVfsBookMarkService(serverURL string) (*VfsBookMarkService, error) {
|
||||
func NewVfsBookMarkService(serverURL string) (*vfs.ProxyEntry, error) {
|
||||
client, err := api.NewClientWithResponses(serverURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &VfsBookMarkService{
|
||||
client: client,
|
||||
}, nil
|
||||
ret := vfs.ProxyEntry{
|
||||
Name: "bookmark",
|
||||
MatchExt: "bk",
|
||||
Proxy: &VfsBookMarkService{client: client},
|
||||
}
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
// Create implements ServiceProxy.
|
||||
@ -157,4 +160,4 @@ func (v *VfsBookMarkService) Update(c *gin.Context, servicePath string, node *mo
|
||||
return fmt.Errorf("unknown error")
|
||||
}
|
||||
|
||||
var _ handlers.ServiceProxy = (*VfsBookMarkService)(nil)
|
||||
var _ vfs.ServiceProxy = (*VfsBookMarkService)(nil)
|
13
magefile.go
13
magefile.go
@ -10,18 +10,23 @@ import (
|
||||
// mg contains helpful utility functions, like Deps
|
||||
)
|
||||
|
||||
//go:generate go tool oapi-codegen -config config/bookmark/client.yaml config/bookmark/bookmark.yaml
|
||||
//go:generate go tool oapi-codegen -config config/bookmark/server.yaml config/bookmark/bookmark.yaml
|
||||
//go:generate go tool oapi-codegen -config config/vfs/server.yaml config/vfs/vfs.yaml
|
||||
//go:generate go tool oapi-codegen -config config/vfs/client.yaml config/vfs/vfs.yaml
|
||||
//go:generate go tool oapi-codegen -config config/user_np/server.yaml config/user_np/user_np.yaml
|
||||
|
||||
// Default target to run when none is specified
|
||||
// If not set, running mage will list available targets
|
||||
// var Default = Build
|
||||
|
||||
func BuildAll() error {
|
||||
func Build_All() error {
|
||||
services := []struct {
|
||||
Name string
|
||||
Path string
|
||||
}{
|
||||
{"openapi", "."},
|
||||
{"vfs_api", "."},
|
||||
{"bookmark", "./cmd/bookmark"},
|
||||
{"user_np", "./cmd/user_np"},
|
||||
}
|
||||
|
||||
platforms := []struct {
|
||||
@ -53,9 +58,7 @@ func BuildAll() error {
|
||||
|
||||
// 使用 release 模式构建并显示链接信息
|
||||
cmd := exec.Command("go", "build",
|
||||
"-x",
|
||||
"-ldflags", "-s -w -extldflags -static", // 去除调试信息,减小体积
|
||||
"-v", // 显示编译过程中的包信息
|
||||
"-o", outputName,
|
||||
service.Path)
|
||||
cmd.Env = env
|
||||
|
36
main.go
36
main.go
@ -6,25 +6,31 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
vfs "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/vfs"
|
||||
api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/vfs"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/handlers"
|
||||
vfsdriver "git.zzyxyz.com/zzy/zzyxyz_go_api/internal/handlers/vfs_driver"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/models"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs/vfsdriver"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
//go:generate go tool oapi-codegen -config config/bookmark/client.yaml config/bookmark/bookmark.yaml
|
||||
//go:generate go tool oapi-codegen -config config/bookmark/server.yaml config/bookmark/bookmark.yaml
|
||||
//go:generate go tool oapi-codegen -config config/vfs/server.yaml config/vfs/vfs.yaml
|
||||
//go:generate go tool oapi-codegen -config config/vfs/server.yaml config/vfs/vfs.yaml
|
||||
//go:generate go tool oapi-codegen -config config/user_np/server.yaml config/user_np/user_np.yaml
|
||||
|
||||
//go:embed config/* dist/*
|
||||
var staticFiles embed.FS
|
||||
|
||||
func main() {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
config, err := vfs.LoadConfig()
|
||||
if err != nil {
|
||||
log.Fatal("Failed to load config:", err)
|
||||
}
|
||||
|
||||
viper.SafeWriteConfigAs("vfs_config.yaml")
|
||||
|
||||
if config.Server.Mode == "debug" {
|
||||
gin.SetMode(gin.DebugMode)
|
||||
} else {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
router := gin.Default()
|
||||
|
||||
router.GET("/ping", func(c *gin.Context) {
|
||||
@ -35,18 +41,16 @@ func main() {
|
||||
|
||||
api_router := router.Group("/api")
|
||||
{
|
||||
if vfsImpl, err := models.NewVfs("./data/vfs.sqlite3"); err != nil {
|
||||
log.Fatal("Failed to create vfs server:", err)
|
||||
} else if server, err := handlers.NewVfsHandler(*vfsImpl); err != nil {
|
||||
if server, err := vfs.NewVfsHandler(config); err != nil {
|
||||
log.Fatal("Failed to create bookmarks server:", err)
|
||||
} else {
|
||||
vfs.RegisterHandlers(api_router, server)
|
||||
api.RegisterHandlers(api_router, server)
|
||||
// 示例:在你的服务初始化代码中
|
||||
bookmarkService, err := vfsdriver.NewVfsBookMarkService("http://localhost:8081/api") // 替换为实际的 bookmark 服务地址
|
||||
if err != nil {
|
||||
log.Fatal("Failed to create bookmark service client:", err)
|
||||
}
|
||||
server.RegisterProxy(&handlers.ProxyEntry{Name: "bk", Proxy: bookmarkService})
|
||||
server.RegisterProxy(bookmarkService)
|
||||
}
|
||||
handlers.TodoHandler(api_router)
|
||||
}
|
||||
@ -82,7 +86,7 @@ func main() {
|
||||
r.URL.Path = originalPath
|
||||
})
|
||||
|
||||
var listener = "localhost:8080"
|
||||
var listener = config.Server.Address
|
||||
log.Printf("Starting server at http://%s", listener)
|
||||
log.Printf("Swagger UI: http://%s/swagger/index.html", listener)
|
||||
log.Fatal(router.Run(listener))
|
||||
|
Reference in New Issue
Block a user