commit c53df223d1232f548f449d230b5c3b2970c7e6f2 Author: ljw <84855512@qq.com> Date: Fri Sep 13 15:57:29 2024 +0800 first diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d03aeac --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.idea +runtime +!runtime/cache/.gitignore +go.sum +resources +!resources/public/upload/.gitignore +!resources/public/web +release +data \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d2a0865 --- /dev/null +++ b/README.md @@ -0,0 +1,105 @@ +# RustDesk API + +本项目使用 Go 实现了 RustDesk 的 API,并包含了 Web UI 和 Web 客户端。RustDesk 是一个远程桌面软件,提供了自托管的解决方案。 + +## 使用前准备 + +### Rustdesk + +1. PC客户端使用的是 ***1.3.0***,经测试 ***1.2.6+*** 都可以 +2. server端必须指定key,不能用自带的生成的key,否则可能链接不上或者超时 + +```bash +hbbs -r -k 123456789 +hbbr -k 123456789 +``` + +## 功能 + +### **API 服务**: 基本实现了PC端基础的接口。 + +![pc_ab](docs/pc_ab.png) +![pc_gr](docs/pc_gr.png) + +### **Web UI**: 使用前后端分离,提供用户友好的管理界面,主要用来管理和展示。 + +***初次安装管理员为用户名密码为admin admin,请即时更改密码*** + +1. 管理员界面 + ![web_admin](docs/web_admin.png) +2. 普通用户界面 + ![web_user](docs/web_user.png) +3. 更改密码在右上角 + +![web_resetpwd](docs/web_resetpwd.png) + +4. 分组可以自定义,方便管理,暂时支持两种类型: `共享组` 和 `普通组` + ![web_admin_gr](docs/web_admin_gr.png) + +### **Web 客户端**: + +1. 如果已经登录了后台,web client将自动直接登录 +2. 如果没登录后台,点击右上角登录即可,api server已经自动配置好了 +3. 登录后台后,会将地址簿自动保存到web client中,方便使用 + ![webclient_conf](docs/webclient_conf.png) + +### **自动化文档**: 使用 Swag 生成 API 文档,方便开发者理解和使用 API。 + +1. 后台文档 /admin/swagger/index.html +2. PC端文档 /swagger/index.html + ![api_swag](docs/api_swag.png) + +## 安装与运行 + +### 相关配置 + +* 参考`conf/config.yaml`配置文件,修改相关配置。如果`gorm.type`是`sqlite`,则不需要配置mysql相关配置。 + +```yaml +gin: + api-addr: "0.0.0.0:21114" + mode: "release" + resources-path: 'resources' +gorm: + type: "sqlite" + max-idle-conns: 10 + max-open-conns: 100 +mysql: + username: "root" + password: "111111" + addr: "192.168.1.66:3308" + dbname: "rustdesk" +rustdesk: + id-server: "192.168.1.66:21116" + relay-server: "192.168.1.66:21117" + api-server: "http://192.168.1.66:21114" + key: "123456789" +``` + +### 安装步骤 + +#### docker运行 + +#### 下载release直接运行 + +#### 源码安装 + +1. 克隆仓库 + ```bash + git clone https://github.com/lejianwen/rustdesk-api.git + cd rustdesk-api + ``` +2. 安装依赖 + ```bash + go mod tidy + ``` +3. 运行 + ```bash + go run cmd/apimain.go + #或者直接Build + ./build.sh + #或者使用generate_api.go生成api + go generate generate_api.go + ``` +4. 编译,如果想自己编译,先cd到项目根目录,然后windows下直接运行`build.bat`,linux下运行`build.sh`,编译后会在`release` + 目录下生成对应的可执行文件。 diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..0928c96 --- /dev/null +++ b/build.bat @@ -0,0 +1,14 @@ +@echo off +go env -w GO111MODULE=on +go env -w GOPROXY=https://goproxy.cn,direct +go env -w CGO_ENABLED=1 +go env -w GOOS=windows +go env -w GOARCH=amd64 +swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin +swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api +go build -o release/apimain.exe cmd/apimain.go +xcopy resources release\resources /E /I /Y +xcopy docs release\docs /E /I /Y +xcopy data release\data /E /I /Y +xcopy conf release\conf /E /I /Y +xcopy runtime release\runtime /E /I /Y diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..f4876b2 --- /dev/null +++ b/build.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +go env -w GO111MODULE=on +go env -w GOPROXY=https://goproxy.cn,direct +go env -w CGO_ENABLED=1 +go env -w GOOS=linux +go env -w GOARCH=amd64 +swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin +swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api +go build -o release/apimain cmd/apimain.go +cp -ar resources release/ +cp -ar docs release/ +cp -ar data release/ +cp -ar conf release/ +cp -ar runtime release/ diff --git a/cmd/apimain.go b/cmd/apimain.go new file mode 100644 index 0000000..2ba29f2 --- /dev/null +++ b/cmd/apimain.go @@ -0,0 +1,252 @@ +package main + +import ( + "Gwen/config" + "Gwen/global" + "Gwen/http" + "Gwen/lib/cache" + "Gwen/lib/lock" + "Gwen/lib/logger" + "Gwen/lib/orm" + "Gwen/lib/upload" + "Gwen/model" + "Gwen/service" + "fmt" + "github.com/go-playground/locales/en" + "github.com/go-playground/locales/zh_Hans_CN" + ut "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" + zh_translations "github.com/go-playground/validator/v10/translations/zh" + "github.com/go-redis/redis/v8" + "reflect" +) + +// @title 管理系统API +// @version 1.0 +// @description 接口 +// @basePath /api +// @securityDefinitions.apikey token +// @in header +// @name api-token +// @securitydefinitions.apikey BearerAuth +// @in header +// @name Authorization +func main() { + //配置解析 + global.Viper = config.Init(&global.Config, func() { + fmt.Println(global.Config) + }) + + //日志 + global.Logger = logger.New(&logger.Config{ + Path: global.Config.Logger.Path, + Level: global.Config.Logger.Level, + ReportCaller: global.Config.Logger.ReportCaller, + }) + + //redis + global.Redis = redis.NewClient(&redis.Options{ + Addr: global.Config.Redis.Addr, + Password: global.Config.Redis.Password, + DB: global.Config.Redis.Db, + }) + + //cache + if global.Config.Cache.Type == cache.TypeFile { + fc := cache.NewFileCache() + fc.SetDir(global.Config.Cache.FileDir) + global.Cache = fc + } else if global.Config.Cache.Type == cache.TypeRedis { + global.Cache = cache.NewRedis(&redis.Options{ + Addr: global.Config.Cache.RedisAddr, + Password: global.Config.Cache.RedisPwd, + DB: global.Config.Cache.RedisDb, + }) + } + //gorm + if global.Config.Gorm.Type == config.TypeMysql { + dns := global.Config.Mysql.Username + ":" + global.Config.Mysql.Password + "@(" + global.Config.Mysql.Addr + ")/" + global.Config.Mysql.Dbname + "?charset=utf8mb4&parseTime=True&loc=Local" + global.DB = orm.NewMysql(&orm.MysqlConfig{ + Dns: dns, + MaxIdleConns: global.Config.Gorm.MaxIdleConns, + MaxOpenConns: global.Config.Gorm.MaxOpenConns, + }) + } else { + //sqlite + global.DB = orm.NewSqlite(&orm.SqliteConfig{ + MaxIdleConns: global.Config.Gorm.MaxIdleConns, + MaxOpenConns: global.Config.Gorm.MaxOpenConns, + }) + } + DatabaseAutoUpdate() + + //validator + ApiInitValidator() + + //oss + global.Oss = &upload.Oss{ + AccessKeyId: global.Config.Oss.AccessKeyId, + AccessKeySecret: global.Config.Oss.AccessKeySecret, + Host: global.Config.Oss.Host, + CallbackUrl: global.Config.Oss.CallbackUrl, + ExpireTime: global.Config.Oss.ExpireTime, + MaxByte: global.Config.Oss.MaxByte, + } + + //jwt + //fmt.Println(global.Config.Jwt.PrivateKey) + //global.Jwt = jwt.NewJwt(global.Config.Jwt.PrivateKey, global.Config.Jwt.ExpireDuration*time.Second) + + //locker + global.Lock = lock.NewLocal() + + //gin + http.ApiInit() + +} + +func ApiInitValidator() { + validate := validator.New() + enT := en.New() + cn := zh_Hans_CN.New() + uni := ut.New(enT, cn) + trans, _ := uni.GetTranslator("cn") + err := zh_translations.RegisterDefaultTranslations(validate, trans) + if err != nil { + //退出 + panic(err) + } + validate.RegisterTagNameFunc(func(field reflect.StructField) string { + label := field.Tag.Get("label") + if label == "" { + return field.Name + } + return label + }) + global.Validator.Validate = validate + global.Validator.VTrans = trans + + global.Validator.ValidStruct = func(i interface{}) []string { + err := global.Validator.Validate.Struct(i) + errList := make([]string, 0, 10) + if err != nil { + if _, ok := err.(*validator.InvalidValidationError); ok { + errList = append(errList, err.Error()) + return errList + } + for _, err2 := range err.(validator.ValidationErrors) { + errList = append(errList, err2.Translate(global.Validator.VTrans)) + } + } + return errList + } + global.Validator.ValidVar = func(field interface{}, tag string) []string { + err := global.Validator.Validate.Var(field, tag) + fmt.Println(err) + errList := make([]string, 0, 10) + if err != nil { + if _, ok := err.(*validator.InvalidValidationError); ok { + errList = append(errList, err.Error()) + return errList + } + for _, err2 := range err.(validator.ValidationErrors) { + errList = append(errList, err2.Translate(global.Validator.VTrans)) + } + } + return errList + } + +} + +func DatabaseAutoUpdate() { + version := 100 + + db := global.DB + + if global.Config.Gorm.Type == config.TypeMysql { + //检查存不存在数据库,不存在则创建 + dbName := db.Migrator().CurrentDatabase() + fmt.Println("dbName", dbName) + if dbName == "" { + dbName = global.Config.Mysql.Dbname + // 移除 DSN 中的数据库名称,以便初始连接时不指定数据库 + dsnWithoutDB := global.Config.Mysql.Username + ":" + global.Config.Mysql.Password + "@(" + global.Config.Mysql.Addr + ")/?charset=utf8mb4&parseTime=True&loc=Local" + //新链接 + dbWithoutDB := orm.NewMysql(&orm.MysqlConfig{ + Dns: dsnWithoutDB, + }) + // 获取底层的 *sql.DB 对象,并确保在程序退出时关闭连接 + sqlDBWithoutDB, err := dbWithoutDB.DB() + if err != nil { + fmt.Printf("获取底层 *sql.DB 对象失败: %v\n", err) + return + } + defer func() { + if err := sqlDBWithoutDB.Close(); err != nil { + fmt.Printf("关闭连接失败: %v\n", err) + } + }() + + err = dbWithoutDB.Exec("CREATE DATABASE IF NOT EXISTS " + dbName + " DEFAULT CHARSET utf8mb4").Error + if err != nil { + fmt.Println(err) + return + } + } + } + + if !db.Migrator().HasTable(&model.Version{}) { + Migrate(uint(version)) + } else { + //查找最后一个version + var v model.Version + db.Last(&v) + if v.Version < uint(version) { + Migrate(uint(version)) + } + } + +} +func Migrate(version uint) { + fmt.Println("migrating....", version) + err := global.DB.AutoMigrate( + &model.Version{}, + &model.User{}, + &model.UserToken{}, + &model.Tag{}, + &model.AddressBook{}, + &model.Peer{}, + &model.Group{}, + ) + if err != nil { + fmt.Println("migrate err :=>", err) + } + global.DB.Create(&model.Version{Version: version}) + //如果是初次则创建一个默认用户 + var vc int64 + global.DB.Model(&model.Version{}).Count(&vc) + if vc == 1 { + group := &model.Group{ + Name: "默认组", + Type: model.GroupTypeDefault, + } + service.AllService.GroupService.Create(group) + groupShare := &model.Group{ + Name: "共享组", + Type: model.GroupTypeShare, + } + service.AllService.GroupService.Create(groupShare) + //是true + is_admin := true + admin := &model.User{ + Username: "admin", + Nickname: "管理员", + Status: model.COMMON_STATUS_ENABLE, + IsAdmin: &is_admin, + GroupId: 1, + } + admin.Password = service.AllService.UserService.EncryptPassword("admin") + global.DB.Create(admin) + } + +} diff --git a/conf/config.yaml b/conf/config.yaml new file mode 100644 index 0000000..2c52163 --- /dev/null +++ b/conf/config.yaml @@ -0,0 +1,42 @@ +gin: + api-addr: "0.0.0.0:21114" + mode: "release" #release,debug,test + resources-path: 'resources' #对外静态文件目录 +gorm: + type: "sqlite" + max-idle-conns: 10 + max-open-conns: 100 +mysql: + username: "root" + password: "111111" + addr: "192.168.1.66:3308" + dbname: "rustdesk2" +rustdesk: + id-server: "124.220.134.240:21116" + relay-server: "124.220.134.240:21117" + api-server: "http://127.0.0.1:21114" + key: "ljw19891989" +redis: + addr: "127.0.0.1:6379" + password: "" + db: 0 +logger: + path: "./runtime/log.txt" + level: "error" #trace,debug,info,warn,error,fatal + report-caller: true +cache: + type: "file" + file-dir: "./runtime/cache" + redis-addr: "127.0.0.1:6379" + redis-pwd: "ljw19891989" + redis-db: 0 +oss: + access-key-id: "" + access-key-secret: "" + host: "" + callback-url: "" + expire-time: 30 + max-byte: 10240 +jwt: + private-key: "./conf/jwt_pri.pem" + expire-duration: 360000 \ No newline at end of file diff --git a/conf/jwt_pri.pem b/conf/jwt_pri.pem new file mode 100644 index 0000000..e69de29 diff --git a/config/cache.go b/config/cache.go new file mode 100644 index 0000000..1485e11 --- /dev/null +++ b/config/cache.go @@ -0,0 +1,9 @@ +package config + +type Cache struct { + Type string + RedisAddr string `mapstructure:"redis-addr"` + RedisPwd string `mapstructure:"redis-pwd"` + RedisDb int `mapstructure:"redis-db"` + FileDir string `mapstructure:"file-dir"` +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..7c789ff --- /dev/null +++ b/config/config.go @@ -0,0 +1,56 @@ +package config + +import ( + "flag" + "fmt" + "github.com/fsnotify/fsnotify" + "github.com/spf13/viper" +) + +const ( + DebugMode = "debug" + ReleaseMode = "release" + DefaultConfig = "conf/config.yaml" +) + +type Config struct { + Gorm Gorm + Mysql Mysql + Gin Gin + Logger Logger + Redis Redis + Cache Cache + Oss Oss + Jwt Jwt + Rustdesk Rustdesk +} + +// Init 初始化配置 +func Init(rowVal interface{}, cb func()) *viper.Viper { + var config string + flag.StringVar(&config, "c", "", "choose config file.") + flag.Parse() + if config == "" { // 优先级: 命令行 > 默认值 + config = DefaultConfig + } + v := viper.New() + v.SetConfigFile(config) + v.SetConfigType("yaml") + err := v.ReadInConfig() + if err != nil { + panic(fmt.Errorf("Fatal error config file: %s \n", err)) + } + v.WatchConfig() + v.OnConfigChange(func(e fsnotify.Event) { + //配置文件修改监听 + fmt.Println("config file changed:", e.Name) + if err2 := v.Unmarshal(rowVal); err2 != nil { + fmt.Println(err2) + } + cb() + }) + if err := v.Unmarshal(rowVal); err != nil { + fmt.Println(err) + } + return v +} diff --git a/config/gin.go b/config/gin.go new file mode 100644 index 0000000..eac5788 --- /dev/null +++ b/config/gin.go @@ -0,0 +1,8 @@ +package config + +type Gin struct { + ApiAddr string `mapstructure:"api-addr"` + AdminAddr string `mapstructure:"admin-addr"` + Mode string + ResourcesPath string `mapstructure:"resources-path"` +} diff --git a/config/gorm.go b/config/gorm.go new file mode 100644 index 0000000..1d01de2 --- /dev/null +++ b/config/gorm.go @@ -0,0 +1,19 @@ +package config + +const ( + TypeSqlite = "sqlite" + TypeMysql = "mysql" +) + +type Gorm struct { + Type string `mapstructure:"type"` + MaxIdleConns int `mapstructure:"max-idle-conns"` + MaxOpenConns int `mapstructure:"max-open-conns"` +} + +type Mysql struct { + Addr string `mapstructure:"addr"` + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` + Dbname string `mapstructure:"dbname"` +} diff --git a/config/jwt.go b/config/jwt.go new file mode 100644 index 0000000..1f6f0d4 --- /dev/null +++ b/config/jwt.go @@ -0,0 +1,8 @@ +package config + +import "time" + +type Jwt struct { + PrivateKey string `mapstructure:"private-key"` + ExpireDuration time.Duration `mapstructure:"expire-duration"` +} diff --git a/config/logger.go b/config/logger.go new file mode 100644 index 0000000..8c51c8b --- /dev/null +++ b/config/logger.go @@ -0,0 +1,7 @@ +package config + +type Logger struct { + Path string + Level string + ReportCaller bool `mapstructure:"report-caller"` +} diff --git a/config/oss.go b/config/oss.go new file mode 100644 index 0000000..6f92f69 --- /dev/null +++ b/config/oss.go @@ -0,0 +1,10 @@ +package config + +type Oss struct { + AccessKeyId string `mapstructure:"access-key-id"` + AccessKeySecret string `mapstructure:"access-key-secret"` + Host string `mapstructure:"host"` + CallbackUrl string `mapstructure:"callback-url"` + ExpireTime int64 `mapstructure:"expire-time"` + MaxByte int64 `mapstructure:"max-byte"` +} diff --git a/config/redis.go b/config/redis.go new file mode 100644 index 0000000..9ff1f2c --- /dev/null +++ b/config/redis.go @@ -0,0 +1,7 @@ +package config + +type Redis struct { + Addr string + Password string + Db int +} diff --git a/config/rustdesk.go b/config/rustdesk.go new file mode 100644 index 0000000..d3ffdf5 --- /dev/null +++ b/config/rustdesk.go @@ -0,0 +1,8 @@ +package config + +type Rustdesk struct { + IdServer string `mapstructure:"id-server"` + RelayServer string `mapstructure:"relay-server"` + ApiServer string `mapstructure:"api-server"` + Key string `mapstructure:"key"` +} diff --git a/docs/admin/admin_docs.go b/docs/admin/admin_docs.go new file mode 100644 index 0000000..80db6ec --- /dev/null +++ b/docs/admin/admin_docs.go @@ -0,0 +1,2331 @@ +// Package admin Code generated by swaggo/swag. DO NOT EDIT +package admin + +import "github.com/swaggo/swag" + +const docTemplateadmin = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/admin/address_book/create": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "创建地址簿", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址簿" + ], + "summary": "创建地址簿", + "parameters": [ + { + "description": "地址簿信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.AddressBookForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.AddressBook" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/address_book/delete": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "地址簿删除", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址簿" + ], + "summary": "地址簿删除", + "parameters": [ + { + "description": "地址簿信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.AddressBookForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/address_book/detail/{id}": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "地址簿详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址簿" + ], + "summary": "地址簿详情", + "parameters": [ + { + "type": "integer", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.AddressBook" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/address_book/list": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "地址簿列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址簿" + ], + "summary": "地址簿列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "页大小", + "name": "page_size", + "in": "query" + }, + { + "type": "integer", + "description": "用户id", + "name": "user_id", + "in": "query" + }, + { + "type": "integer", + "description": "是否是我的", + "name": "is_my", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.AddressBookList" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/address_book/update": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "地址簿编辑", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址簿" + ], + "summary": "地址簿编辑", + "parameters": [ + { + "description": "地址簿信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.AddressBookForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.AddressBook" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/file/oss_token": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "获取ossToken", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "文件" + ], + "summary": "获取ossToken", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/file/upload": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "上传文件到本地", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "文件" + ], + "summary": "上传文件到本地", + "parameters": [ + { + "type": "file", + "description": "上传文件示例", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/group/create": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "创建群组", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "群组" + ], + "summary": "创建群组", + "parameters": [ + { + "description": "群组信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.GroupForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Group" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/group/delete": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "群组删除", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "群组" + ], + "summary": "群组删除", + "parameters": [ + { + "description": "群组信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.GroupForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/group/detail/{id}": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "群组详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "群组" + ], + "summary": "群组详情", + "parameters": [ + { + "type": "integer", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Group" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/group/list": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "群组列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "群组" + ], + "summary": "群组列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "页大小", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.GroupList" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/group/update": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "群组编辑", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "群组" + ], + "summary": "群组编辑", + "parameters": [ + { + "description": "群组信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.GroupForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Group" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/login": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "登录", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "登录" + ], + "summary": "登录", + "parameters": [ + { + "description": "登录信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Gwen_http_request_admin.Login" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/admin.LoginPayload" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/logout": { + "post": { + "description": "登出", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "登录" + ], + "summary": "登出", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/peer/create": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "创建机器", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "机器" + ], + "summary": "创建机器", + "parameters": [ + { + "description": "机器信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.PeerForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Peer" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/peer/delete": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "机器删除", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "机器" + ], + "summary": "机器删除", + "parameters": [ + { + "description": "机器信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.PeerForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/peer/detail/{id}": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "机器详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "机器" + ], + "summary": "机器详情", + "parameters": [ + { + "type": "integer", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Peer" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/peer/list": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "机器列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "机器" + ], + "summary": "机器列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "页大小", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.PeerList" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/peer/update": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "机器编辑", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "机器" + ], + "summary": "机器编辑", + "parameters": [ + { + "description": "机器信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.PeerForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Peer" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/server-config": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "服务配置,给webclient提供api-server", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ADMIN" + ], + "summary": "服务配置", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/tag/create": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "创建标签", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "标签" + ], + "summary": "创建标签", + "parameters": [ + { + "description": "标签信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.TagForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Tag" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/tag/delete": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "标签删除", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "标签" + ], + "summary": "标签删除", + "parameters": [ + { + "description": "标签信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.TagForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/tag/detail/{id}": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "标签详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "标签" + ], + "summary": "标签详情", + "parameters": [ + { + "type": "integer", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Tag" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/tag/list": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "标签列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "标签" + ], + "summary": "标签列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "页大小", + "name": "page_size", + "in": "query" + }, + { + "type": "integer", + "description": "是否是我的", + "name": "is_my", + "in": "query" + }, + { + "type": "integer", + "description": "用户id", + "name": "user_id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.TagList" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/tag/update": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "标签编辑", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "标签" + ], + "summary": "标签编辑", + "parameters": [ + { + "description": "标签信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.TagForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Tag" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/user/changeCurPwd": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "修改当前用户密码", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "修改当前用户密码", + "parameters": [ + { + "description": "用户信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.ChangeCurPasswordForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/user/create": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "创建管理员", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "创建管理员", + "parameters": [ + { + "description": "管理员信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.UserForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.User" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/user/current": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "当前用户", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "当前用户", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/admin.LoginPayload" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/user/delete": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "管理员编删除", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "管理员删除", + "parameters": [ + { + "description": "用户信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.UserForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/user/detail/{id}": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "管理员详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "管理员详情", + "parameters": [ + { + "type": "integer", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.User" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/user/list": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "管理员列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "管理员列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "页大小", + "name": "page_size", + "in": "query" + }, + { + "type": "integer", + "description": "账户", + "name": "username", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.UserList" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/user/update": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "管理员编辑", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "管理员编辑", + "parameters": [ + { + "description": "用户信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.UserForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.User" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/user/updatePassword": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "修改密码", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "修改密码", + "parameters": [ + { + "description": "用户信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.UserPasswordForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + } + }, + "definitions": { + "Gwen_http_request_admin.Login": { + "type": "object", + "required": [ + "password", + "username" + ], + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "admin.AddressBookForm": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "alias": { + "type": "string" + }, + "force_always_relay": { + "type": "boolean" + }, + "hash": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "id": { + "type": "string" + }, + "login_name": { + "type": "string" + }, + "online": { + "type": "boolean" + }, + "password": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "rdp_port": { + "type": "string" + }, + "rdp_username": { + "type": "string" + }, + "row_id": { + "type": "integer" + }, + "same_server": { + "type": "boolean" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "user_id": { + "type": "integer" + }, + "username": { + "type": "string" + } + } + }, + "admin.ChangeCurPasswordForm": { + "type": "object", + "required": [ + "new_password", + "old_password" + ], + "properties": { + "new_password": { + "type": "string", + "maxLength": 20, + "minLength": 4 + }, + "old_password": { + "type": "string", + "maxLength": 20, + "minLength": 4 + } + } + }, + "admin.GroupForm": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, + "admin.LoginPayload": { + "type": "object", + "properties": { + "nickname": { + "type": "string" + }, + "route_names": { + "type": "array", + "items": { + "type": "string" + } + }, + "token": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "admin.PeerForm": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "id": { + "type": "string" + }, + "memory": { + "type": "string" + }, + "os": { + "type": "string" + }, + "row_id": { + "type": "integer" + }, + "username": { + "type": "string" + }, + "uuid": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "admin.TagForm": { + "type": "object", + "required": [ + "color", + "name", + "user_id" + ], + "properties": { + "color": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "user_id": { + "type": "integer" + } + } + }, + "admin.UserForm": { + "type": "object", + "required": [ + "group_id", + "nickname", + "status", + "username" + ], + "properties": { + "avatar": { + "type": "string" + }, + "group_id": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "is_admin": { + "type": "boolean" + }, + "nickname": { + "description": "Password string ` + "`" + `json:\"password\" validate:\"required,gte=4,lte=20\"` + "`" + `", + "type": "string" + }, + "status": { + "minimum": 0, + "allOf": [ + { + "$ref": "#/definitions/model.StatusCode" + } + ] + }, + "username": { + "type": "string", + "maxLength": 10, + "minLength": 4 + } + } + }, + "admin.UserPasswordForm": { + "type": "object", + "required": [ + "id", + "password" + ], + "properties": { + "id": { + "type": "integer" + }, + "password": { + "type": "string", + "maxLength": 20, + "minLength": 4 + } + } + }, + "model.AddressBook": { + "type": "object", + "properties": { + "alias": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "forceAlwaysRelay": { + "type": "boolean" + }, + "hash": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "id": { + "type": "string" + }, + "loginName": { + "type": "string" + }, + "online": { + "type": "boolean" + }, + "password": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "rdpPort": { + "type": "string" + }, + "rdpUsername": { + "type": "string" + }, + "row_id": { + "type": "integer" + }, + "sameServer": { + "type": "boolean" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "updated_at": { + "type": "string" + }, + "user_id": { + "type": "integer" + }, + "username": { + "type": "string" + } + } + }, + "model.AddressBookList": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/model.AddressBook" + } + }, + "page": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "model.Group": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "type": { + "type": "integer" + }, + "updated_at": { + "type": "string" + } + } + }, + "model.GroupList": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/model.Group" + } + }, + "page": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "model.Peer": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "id": { + "type": "string" + }, + "memory": { + "type": "string" + }, + "os": { + "type": "string" + }, + "row_id": { + "type": "integer" + }, + "updated_at": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/model.User" + }, + "user_id": { + "type": "integer" + }, + "username": { + "type": "string" + }, + "uuid": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "model.PeerList": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/model.Peer" + } + }, + "page": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "model.StatusCode": { + "type": "integer", + "enum": [ + 1, + 2 + ], + "x-enum-comments": { + "COMMON_STATUS_DISABLED": "通用状态 禁用", + "COMMON_STATUS_ENABLE": "通用状态 启用" + }, + "x-enum-varnames": [ + "COMMON_STATUS_ENABLE", + "COMMON_STATUS_DISABLED" + ] + }, + "model.Tag": { + "type": "object", + "properties": { + "color": { + "description": "color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度,后面6位表示颜色, 可以转成rgba", + "type": "integer" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "user_id": { + "type": "integer" + } + } + }, + "model.TagList": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/model.Tag" + } + }, + "page": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "model.User": { + "type": "object", + "properties": { + "avatar": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "group_id": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "is_admin": { + "type": "boolean" + }, + "nickname": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/model.StatusCode" + }, + "updated_at": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "model.UserList": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/model.User" + } + }, + "page": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "response.Response": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "data": {}, + "message": { + "type": "string" + } + } + } + }, + "securityDefinitions": { + "BearerAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + }, + "token": { + "type": "apiKey", + "name": "api-token", + "in": "header" + } + } +}` + +// SwaggerInfoadmin holds exported Swagger Info so clients can modify it +var SwaggerInfoadmin = &swag.Spec{ + Version: "1.0", + Host: "", + BasePath: "/api", + Schemes: []string{}, + Title: "管理系统API", + Description: "接口", + InfoInstanceName: "admin", + SwaggerTemplate: docTemplateadmin, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfoadmin.InstanceName(), SwaggerInfoadmin) +} diff --git a/docs/admin/admin_swagger.json b/docs/admin/admin_swagger.json new file mode 100644 index 0000000..c711955 --- /dev/null +++ b/docs/admin/admin_swagger.json @@ -0,0 +1,2306 @@ +{ + "swagger": "2.0", + "info": { + "description": "接口", + "title": "管理系统API", + "contact": {}, + "version": "1.0" + }, + "basePath": "/api", + "paths": { + "/admin/address_book/create": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "创建地址簿", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址簿" + ], + "summary": "创建地址簿", + "parameters": [ + { + "description": "地址簿信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.AddressBookForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.AddressBook" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/address_book/delete": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "地址簿删除", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址簿" + ], + "summary": "地址簿删除", + "parameters": [ + { + "description": "地址簿信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.AddressBookForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/address_book/detail/{id}": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "地址簿详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址簿" + ], + "summary": "地址簿详情", + "parameters": [ + { + "type": "integer", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.AddressBook" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/address_book/list": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "地址簿列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址簿" + ], + "summary": "地址簿列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "页大小", + "name": "page_size", + "in": "query" + }, + { + "type": "integer", + "description": "用户id", + "name": "user_id", + "in": "query" + }, + { + "type": "integer", + "description": "是否是我的", + "name": "is_my", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.AddressBookList" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/address_book/update": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "地址簿编辑", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址簿" + ], + "summary": "地址簿编辑", + "parameters": [ + { + "description": "地址簿信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.AddressBookForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.AddressBook" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/file/oss_token": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "获取ossToken", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "文件" + ], + "summary": "获取ossToken", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/file/upload": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "上传文件到本地", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "文件" + ], + "summary": "上传文件到本地", + "parameters": [ + { + "type": "file", + "description": "上传文件示例", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/group/create": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "创建群组", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "群组" + ], + "summary": "创建群组", + "parameters": [ + { + "description": "群组信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.GroupForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Group" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/group/delete": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "群组删除", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "群组" + ], + "summary": "群组删除", + "parameters": [ + { + "description": "群组信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.GroupForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/group/detail/{id}": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "群组详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "群组" + ], + "summary": "群组详情", + "parameters": [ + { + "type": "integer", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Group" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/group/list": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "群组列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "群组" + ], + "summary": "群组列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "页大小", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.GroupList" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/group/update": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "群组编辑", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "群组" + ], + "summary": "群组编辑", + "parameters": [ + { + "description": "群组信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.GroupForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Group" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/login": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "登录", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "登录" + ], + "summary": "登录", + "parameters": [ + { + "description": "登录信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Gwen_http_request_admin.Login" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/admin.LoginPayload" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/logout": { + "post": { + "description": "登出", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "登录" + ], + "summary": "登出", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/peer/create": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "创建机器", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "机器" + ], + "summary": "创建机器", + "parameters": [ + { + "description": "机器信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.PeerForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Peer" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/peer/delete": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "机器删除", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "机器" + ], + "summary": "机器删除", + "parameters": [ + { + "description": "机器信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.PeerForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/peer/detail/{id}": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "机器详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "机器" + ], + "summary": "机器详情", + "parameters": [ + { + "type": "integer", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Peer" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/peer/list": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "机器列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "机器" + ], + "summary": "机器列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "页大小", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.PeerList" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/peer/update": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "机器编辑", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "机器" + ], + "summary": "机器编辑", + "parameters": [ + { + "description": "机器信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.PeerForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Peer" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/server-config": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "服务配置,给webclient提供api-server", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ADMIN" + ], + "summary": "服务配置", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/tag/create": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "创建标签", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "标签" + ], + "summary": "创建标签", + "parameters": [ + { + "description": "标签信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.TagForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Tag" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/tag/delete": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "标签删除", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "标签" + ], + "summary": "标签删除", + "parameters": [ + { + "description": "标签信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.TagForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/tag/detail/{id}": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "标签详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "标签" + ], + "summary": "标签详情", + "parameters": [ + { + "type": "integer", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Tag" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/tag/list": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "标签列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "标签" + ], + "summary": "标签列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "页大小", + "name": "page_size", + "in": "query" + }, + { + "type": "integer", + "description": "是否是我的", + "name": "is_my", + "in": "query" + }, + { + "type": "integer", + "description": "用户id", + "name": "user_id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.TagList" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/tag/update": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "标签编辑", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "标签" + ], + "summary": "标签编辑", + "parameters": [ + { + "description": "标签信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.TagForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Tag" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/user/changeCurPwd": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "修改当前用户密码", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "修改当前用户密码", + "parameters": [ + { + "description": "用户信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.ChangeCurPasswordForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/user/create": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "创建管理员", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "创建管理员", + "parameters": [ + { + "description": "管理员信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.UserForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.User" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/user/current": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "当前用户", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "当前用户", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/admin.LoginPayload" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/user/delete": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "管理员编删除", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "管理员删除", + "parameters": [ + { + "description": "用户信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.UserForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/user/detail/{id}": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "管理员详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "管理员详情", + "parameters": [ + { + "type": "integer", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.User" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/user/list": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "管理员列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "管理员列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "页大小", + "name": "page_size", + "in": "query" + }, + { + "type": "integer", + "description": "账户", + "name": "username", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.UserList" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/user/update": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "管理员编辑", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "管理员编辑", + "parameters": [ + { + "description": "用户信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.UserForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.User" + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/admin/user/updatePassword": { + "post": { + "security": [ + { + "token": [] + } + ], + "description": "修改密码", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "修改密码", + "parameters": [ + { + "description": "用户信息", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.UserPasswordForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + } + }, + "definitions": { + "Gwen_http_request_admin.Login": { + "type": "object", + "required": [ + "password", + "username" + ], + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "admin.AddressBookForm": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "alias": { + "type": "string" + }, + "force_always_relay": { + "type": "boolean" + }, + "hash": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "id": { + "type": "string" + }, + "login_name": { + "type": "string" + }, + "online": { + "type": "boolean" + }, + "password": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "rdp_port": { + "type": "string" + }, + "rdp_username": { + "type": "string" + }, + "row_id": { + "type": "integer" + }, + "same_server": { + "type": "boolean" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "user_id": { + "type": "integer" + }, + "username": { + "type": "string" + } + } + }, + "admin.ChangeCurPasswordForm": { + "type": "object", + "required": [ + "new_password", + "old_password" + ], + "properties": { + "new_password": { + "type": "string", + "maxLength": 20, + "minLength": 4 + }, + "old_password": { + "type": "string", + "maxLength": 20, + "minLength": 4 + } + } + }, + "admin.GroupForm": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, + "admin.LoginPayload": { + "type": "object", + "properties": { + "nickname": { + "type": "string" + }, + "route_names": { + "type": "array", + "items": { + "type": "string" + } + }, + "token": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "admin.PeerForm": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "id": { + "type": "string" + }, + "memory": { + "type": "string" + }, + "os": { + "type": "string" + }, + "row_id": { + "type": "integer" + }, + "username": { + "type": "string" + }, + "uuid": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "admin.TagForm": { + "type": "object", + "required": [ + "color", + "name", + "user_id" + ], + "properties": { + "color": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "user_id": { + "type": "integer" + } + } + }, + "admin.UserForm": { + "type": "object", + "required": [ + "group_id", + "nickname", + "status", + "username" + ], + "properties": { + "avatar": { + "type": "string" + }, + "group_id": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "is_admin": { + "type": "boolean" + }, + "nickname": { + "description": "Password string `json:\"password\" validate:\"required,gte=4,lte=20\"`", + "type": "string" + }, + "status": { + "minimum": 0, + "allOf": [ + { + "$ref": "#/definitions/model.StatusCode" + } + ] + }, + "username": { + "type": "string", + "maxLength": 10, + "minLength": 4 + } + } + }, + "admin.UserPasswordForm": { + "type": "object", + "required": [ + "id", + "password" + ], + "properties": { + "id": { + "type": "integer" + }, + "password": { + "type": "string", + "maxLength": 20, + "minLength": 4 + } + } + }, + "model.AddressBook": { + "type": "object", + "properties": { + "alias": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "forceAlwaysRelay": { + "type": "boolean" + }, + "hash": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "id": { + "type": "string" + }, + "loginName": { + "type": "string" + }, + "online": { + "type": "boolean" + }, + "password": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "rdpPort": { + "type": "string" + }, + "rdpUsername": { + "type": "string" + }, + "row_id": { + "type": "integer" + }, + "sameServer": { + "type": "boolean" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "updated_at": { + "type": "string" + }, + "user_id": { + "type": "integer" + }, + "username": { + "type": "string" + } + } + }, + "model.AddressBookList": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/model.AddressBook" + } + }, + "page": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "model.Group": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "type": { + "type": "integer" + }, + "updated_at": { + "type": "string" + } + } + }, + "model.GroupList": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/model.Group" + } + }, + "page": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "model.Peer": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "id": { + "type": "string" + }, + "memory": { + "type": "string" + }, + "os": { + "type": "string" + }, + "row_id": { + "type": "integer" + }, + "updated_at": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/model.User" + }, + "user_id": { + "type": "integer" + }, + "username": { + "type": "string" + }, + "uuid": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "model.PeerList": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/model.Peer" + } + }, + "page": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "model.StatusCode": { + "type": "integer", + "enum": [ + 1, + 2 + ], + "x-enum-comments": { + "COMMON_STATUS_DISABLED": "通用状态 禁用", + "COMMON_STATUS_ENABLE": "通用状态 启用" + }, + "x-enum-varnames": [ + "COMMON_STATUS_ENABLE", + "COMMON_STATUS_DISABLED" + ] + }, + "model.Tag": { + "type": "object", + "properties": { + "color": { + "description": "color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度,后面6位表示颜色, 可以转成rgba", + "type": "integer" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "user_id": { + "type": "integer" + } + } + }, + "model.TagList": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/model.Tag" + } + }, + "page": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "model.User": { + "type": "object", + "properties": { + "avatar": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "group_id": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "is_admin": { + "type": "boolean" + }, + "nickname": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/model.StatusCode" + }, + "updated_at": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "model.UserList": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/model.User" + } + }, + "page": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "response.Response": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "data": {}, + "message": { + "type": "string" + } + } + } + }, + "securityDefinitions": { + "BearerAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + }, + "token": { + "type": "apiKey", + "name": "api-token", + "in": "header" + } + } +} \ No newline at end of file diff --git a/docs/admin/admin_swagger.yaml b/docs/admin/admin_swagger.yaml new file mode 100644 index 0000000..0a0584b --- /dev/null +++ b/docs/admin/admin_swagger.yaml @@ -0,0 +1,1411 @@ +basePath: /api +definitions: + Gwen_http_request_admin.Login: + properties: + password: + type: string + username: + type: string + required: + - password + - username + type: object + admin.AddressBookForm: + properties: + alias: + type: string + force_always_relay: + type: boolean + hash: + type: string + hostname: + type: string + id: + type: string + login_name: + type: string + online: + type: boolean + password: + type: string + platform: + type: string + rdp_port: + type: string + rdp_username: + type: string + row_id: + type: integer + same_server: + type: boolean + tags: + items: + type: string + type: array + user_id: + type: integer + username: + type: string + required: + - id + type: object + admin.ChangeCurPasswordForm: + properties: + new_password: + maxLength: 20 + minLength: 4 + type: string + old_password: + maxLength: 20 + minLength: 4 + type: string + required: + - new_password + - old_password + type: object + admin.GroupForm: + properties: + id: + type: integer + name: + type: string + required: + - name + type: object + admin.LoginPayload: + properties: + nickname: + type: string + route_names: + items: + type: string + type: array + token: + type: string + username: + type: string + type: object + admin.PeerForm: + properties: + cpu: + type: string + hostname: + type: string + id: + type: string + memory: + type: string + os: + type: string + row_id: + type: integer + username: + type: string + uuid: + type: string + version: + type: string + type: object + admin.TagForm: + properties: + color: + type: integer + id: + type: integer + name: + type: string + user_id: + type: integer + required: + - color + - name + - user_id + type: object + admin.UserForm: + properties: + avatar: + type: string + group_id: + type: integer + id: + type: integer + is_admin: + type: boolean + nickname: + description: Password string `json:"password" validate:"required,gte=4,lte=20"` + type: string + status: + allOf: + - $ref: '#/definitions/model.StatusCode' + minimum: 0 + username: + maxLength: 10 + minLength: 4 + type: string + required: + - group_id + - nickname + - status + - username + type: object + admin.UserPasswordForm: + properties: + id: + type: integer + password: + maxLength: 20 + minLength: 4 + type: string + required: + - id + - password + type: object + model.AddressBook: + properties: + alias: + type: string + created_at: + type: string + forceAlwaysRelay: + type: boolean + hash: + type: string + hostname: + type: string + id: + type: string + loginName: + type: string + online: + type: boolean + password: + type: string + platform: + type: string + rdpPort: + type: string + rdpUsername: + type: string + row_id: + type: integer + sameServer: + type: boolean + tags: + items: + type: string + type: array + updated_at: + type: string + user_id: + type: integer + username: + type: string + type: object + model.AddressBookList: + properties: + list: + items: + $ref: '#/definitions/model.AddressBook' + type: array + page: + type: integer + page_size: + type: integer + total: + type: integer + type: object + model.Group: + properties: + created_at: + type: string + id: + type: integer + name: + type: string + type: + type: integer + updated_at: + type: string + type: object + model.GroupList: + properties: + list: + items: + $ref: '#/definitions/model.Group' + type: array + page: + type: integer + page_size: + type: integer + total: + type: integer + type: object + model.Peer: + properties: + cpu: + type: string + created_at: + type: string + hostname: + type: string + id: + type: string + memory: + type: string + os: + type: string + row_id: + type: integer + updated_at: + type: string + user: + $ref: '#/definitions/model.User' + user_id: + type: integer + username: + type: string + uuid: + type: string + version: + type: string + type: object + model.PeerList: + properties: + list: + items: + $ref: '#/definitions/model.Peer' + type: array + page: + type: integer + page_size: + type: integer + total: + type: integer + type: object + model.StatusCode: + enum: + - 1 + - 2 + type: integer + x-enum-comments: + COMMON_STATUS_DISABLED: 通用状态 禁用 + COMMON_STATUS_ENABLE: 通用状态 启用 + x-enum-varnames: + - COMMON_STATUS_ENABLE + - COMMON_STATUS_DISABLED + model.Tag: + properties: + color: + description: color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度,后面6位表示颜色, + 可以转成rgba + type: integer + created_at: + type: string + id: + type: integer + name: + type: string + updated_at: + type: string + user_id: + type: integer + type: object + model.TagList: + properties: + list: + items: + $ref: '#/definitions/model.Tag' + type: array + page: + type: integer + page_size: + type: integer + total: + type: integer + type: object + model.User: + properties: + avatar: + type: string + created_at: + type: string + group_id: + type: integer + id: + type: integer + is_admin: + type: boolean + nickname: + type: string + status: + $ref: '#/definitions/model.StatusCode' + updated_at: + type: string + username: + type: string + type: object + model.UserList: + properties: + list: + items: + $ref: '#/definitions/model.User' + type: array + page: + type: integer + page_size: + type: integer + total: + type: integer + type: object + response.Response: + properties: + code: + type: integer + data: {} + message: + type: string + type: object +info: + contact: {} + description: 接口 + title: 管理系统API + version: "1.0" +paths: + /admin/address_book/create: + post: + consumes: + - application/json + description: 创建地址簿 + parameters: + - description: 地址簿信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/admin.AddressBookForm' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.AddressBook' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 创建地址簿 + tags: + - 地址簿 + /admin/address_book/delete: + post: + consumes: + - application/json + description: 地址簿删除 + parameters: + - description: 地址簿信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/admin.AddressBookForm' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 地址簿删除 + tags: + - 地址簿 + /admin/address_book/detail/{id}: + get: + consumes: + - application/json + description: 地址簿详情 + parameters: + - description: ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.AddressBook' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 地址簿详情 + tags: + - 地址簿 + /admin/address_book/list: + get: + consumes: + - application/json + description: 地址簿列表 + parameters: + - description: 页码 + in: query + name: page + type: integer + - description: 页大小 + in: query + name: page_size + type: integer + - description: 用户id + in: query + name: user_id + type: integer + - description: 是否是我的 + in: query + name: is_my + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.AddressBookList' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 地址簿列表 + tags: + - 地址簿 + /admin/address_book/update: + post: + consumes: + - application/json + description: 地址簿编辑 + parameters: + - description: 地址簿信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/admin.AddressBookForm' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.AddressBook' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 地址簿编辑 + tags: + - 地址簿 + /admin/file/oss_token: + get: + consumes: + - application/json + description: 获取ossToken + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 获取ossToken + tags: + - 文件 + /admin/file/upload: + post: + consumes: + - multipart/form-data + description: 上传文件到本地 + parameters: + - description: 上传文件示例 + in: formData + name: file + required: true + type: file + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 上传文件到本地 + tags: + - 文件 + /admin/group/create: + post: + consumes: + - application/json + description: 创建群组 + parameters: + - description: 群组信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/admin.GroupForm' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.Group' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 创建群组 + tags: + - 群组 + /admin/group/delete: + post: + consumes: + - application/json + description: 群组删除 + parameters: + - description: 群组信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/admin.GroupForm' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 群组删除 + tags: + - 群组 + /admin/group/detail/{id}: + get: + consumes: + - application/json + description: 群组详情 + parameters: + - description: ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.Group' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 群组详情 + tags: + - 群组 + /admin/group/list: + get: + consumes: + - application/json + description: 群组列表 + parameters: + - description: 页码 + in: query + name: page + type: integer + - description: 页大小 + in: query + name: page_size + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.GroupList' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 群组列表 + tags: + - 群组 + /admin/group/update: + post: + consumes: + - application/json + description: 群组编辑 + parameters: + - description: 群组信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/admin.GroupForm' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.Group' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 群组编辑 + tags: + - 群组 + /admin/login: + post: + consumes: + - application/json + description: 登录 + parameters: + - description: 登录信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/Gwen_http_request_admin.Login' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/admin.LoginPayload' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 登录 + tags: + - 登录 + /admin/logout: + post: + consumes: + - application/json + description: 登出 + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + summary: 登出 + tags: + - 登录 + /admin/peer/create: + post: + consumes: + - application/json + description: 创建机器 + parameters: + - description: 机器信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/admin.PeerForm' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.Peer' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 创建机器 + tags: + - 机器 + /admin/peer/delete: + post: + consumes: + - application/json + description: 机器删除 + parameters: + - description: 机器信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/admin.PeerForm' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 机器删除 + tags: + - 机器 + /admin/peer/detail/{id}: + get: + consumes: + - application/json + description: 机器详情 + parameters: + - description: ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.Peer' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 机器详情 + tags: + - 机器 + /admin/peer/list: + get: + consumes: + - application/json + description: 机器列表 + parameters: + - description: 页码 + in: query + name: page + type: integer + - description: 页大小 + in: query + name: page_size + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.PeerList' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 机器列表 + tags: + - 机器 + /admin/peer/update: + post: + consumes: + - application/json + description: 机器编辑 + parameters: + - description: 机器信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/admin.PeerForm' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.Peer' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 机器编辑 + tags: + - 机器 + /admin/server-config: + get: + consumes: + - application/json + description: 服务配置,给webclient提供api-server + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 服务配置 + tags: + - ADMIN + /admin/tag/create: + post: + consumes: + - application/json + description: 创建标签 + parameters: + - description: 标签信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/admin.TagForm' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.Tag' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 创建标签 + tags: + - 标签 + /admin/tag/delete: + post: + consumes: + - application/json + description: 标签删除 + parameters: + - description: 标签信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/admin.TagForm' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 标签删除 + tags: + - 标签 + /admin/tag/detail/{id}: + get: + consumes: + - application/json + description: 标签详情 + parameters: + - description: ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.Tag' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 标签详情 + tags: + - 标签 + /admin/tag/list: + get: + consumes: + - application/json + description: 标签列表 + parameters: + - description: 页码 + in: query + name: page + type: integer + - description: 页大小 + in: query + name: page_size + type: integer + - description: 是否是我的 + in: query + name: is_my + type: integer + - description: 用户id + in: query + name: user_id + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.TagList' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 标签列表 + tags: + - 标签 + /admin/tag/update: + post: + consumes: + - application/json + description: 标签编辑 + parameters: + - description: 标签信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/admin.TagForm' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.Tag' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 标签编辑 + tags: + - 标签 + /admin/user/changeCurPwd: + post: + consumes: + - application/json + description: 修改当前用户密码 + parameters: + - description: 用户信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/admin.ChangeCurPasswordForm' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 修改当前用户密码 + tags: + - 用户 + /admin/user/create: + post: + consumes: + - application/json + description: 创建管理员 + parameters: + - description: 管理员信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/admin.UserForm' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.User' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 创建管理员 + tags: + - 用户 + /admin/user/current: + get: + consumes: + - application/json + description: 当前用户 + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/admin.LoginPayload' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 当前用户 + tags: + - 用户 + /admin/user/delete: + post: + consumes: + - application/json + description: 管理员编删除 + parameters: + - description: 用户信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/admin.UserForm' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 管理员删除 + tags: + - 用户 + /admin/user/detail/{id}: + get: + consumes: + - application/json + description: 管理员详情 + parameters: + - description: ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.User' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 管理员详情 + tags: + - 用户 + /admin/user/list: + get: + consumes: + - application/json + description: 管理员列表 + parameters: + - description: 页码 + in: query + name: page + type: integer + - description: 页大小 + in: query + name: page_size + type: integer + - description: 账户 + in: query + name: username + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.UserList' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 管理员列表 + tags: + - 用户 + /admin/user/update: + post: + consumes: + - application/json + description: 管理员编辑 + parameters: + - description: 用户信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/admin.UserForm' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.User' + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 管理员编辑 + tags: + - 用户 + /admin/user/updatePassword: + post: + consumes: + - application/json + description: 修改密码 + parameters: + - description: 用户信息 + in: body + name: body + required: true + schema: + $ref: '#/definitions/admin.UserPasswordForm' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 修改密码 + tags: + - 用户 +securityDefinitions: + BearerAuth: + in: header + name: Authorization + type: apiKey + token: + in: header + name: api-token + type: apiKey +swagger: "2.0" diff --git a/docs/api/api_docs.go b/docs/api/api_docs.go new file mode 100644 index 0000000..57ecdef --- /dev/null +++ b/docs/api/api_docs.go @@ -0,0 +1,830 @@ +// Package api Code generated by swaggo/swag. DO NOT EDIT +package api + +import "github.com/swaggo/swag" + +const docTemplateapi = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/": { + "get": { + "description": "首页", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "首页" + ], + "summary": "首页", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/ab": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "地址列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址" + ], + "summary": "地址列表", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "地址更新", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址" + ], + "summary": "地址更新", + "parameters": [ + { + "description": "地址表单", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.AddressBookForm" + } + } + ], + "responses": { + "200": { + "description": "null", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/ab/add": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "标签", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址" + ], + "summary": "标签添加", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/ab/personal": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "个人信息", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "个人信息", + "parameters": [ + { + "description": "string valid", + "name": "string", + "in": "body", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "用户信息", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "用户信息", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.UserPayload" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/currentUser": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "用户信息", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "用户信息", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.UserPayload" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/heartbeat": { + "post": { + "description": "心跳", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "首页" + ], + "summary": "心跳", + "responses": { + "200": { + "description": "OK" + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/login": { + "post": { + "description": "登录", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "登录" + ], + "summary": "登录", + "parameters": [ + { + "description": "登录表单", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.LoginForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LoginRes" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/login-options": { + "post": { + "description": "登录选项", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "登录" + ], + "summary": "登录选项", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/logout": { + "post": { + "description": "登出", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "登录" + ], + "summary": "登出", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/peers": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "机器", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "群组" + ], + "summary": "机器", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页数量", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "状态", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "accessible", + "name": "accessible", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.DataResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/server-config": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "服务配置,给webclient提供api-server", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "WEBCLIENT" + ], + "summary": "服务配置", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/sysinfo": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "提交系统信息", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址" + ], + "summary": "提交系统信息", + "parameters": [ + { + "description": "系统信息表单", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.PeerForm" + } + } + ], + "responses": { + "200": { + "description": "SYSINFO_UPDATED,ID_NOT_FOUND", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/tags": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "标签", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址" + ], + "summary": "标签", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/model.Tag" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/users": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "用户列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "群组" + ], + "summary": "用户列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页数量", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "状态", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "accessible", + "name": "accessible", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.DataResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/api.UserPayload" + } + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + } + }, + "definitions": { + "api.AddressBookForm": { + "type": "object", + "properties": { + "data": { + "type": "string", + "example": "{\"tags\":[\"tag1\",\"tag2\",\"tag3\"],\"peers\":[{\"id\":\"abc\",\"username\":\"abv-l\",\"hostname\":\"\",\"platform\":\"Windows\",\"alias\":\"\",\"tags\":[\"tag1\",\"tag2\"],\"hash\":\"hash\"}],\"tag_colors\":\"{\\\"tag1\\\":4288585374,\\\"tag2\\\":4278238420,\\\"tag3\\\":4291681337}\"}" + } + } + }, + "api.LoginForm": { + "type": "object", + "required": [ + "username" + ], + "properties": { + "password": { + "type": "string", + "maxLength": 20, + "minLength": 4 + }, + "username": { + "type": "string", + "maxLength": 10, + "minLength": 4 + } + } + }, + "api.LoginRes": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "tfa_type": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/api.UserPayload" + } + } + }, + "api.PeerForm": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "id": { + "type": "string" + }, + "memory": { + "type": "string" + }, + "os": { + "type": "string" + }, + "username": { + "type": "string" + }, + "uuid": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "api.UserPayload": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "is_admin": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "note": { + "type": "string" + }, + "status": { + "type": "integer" + } + } + }, + "model.Tag": { + "type": "object", + "properties": { + "color": { + "description": "color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度,后面6位表示颜色, 可以转成rgba", + "type": "integer" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "user_id": { + "type": "integer" + } + } + }, + "response.DataResponse": { + "type": "object", + "properties": { + "data": {}, + "total": { + "type": "integer" + } + } + }, + "response.ErrorResponse": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + }, + "response.Response": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "data": {}, + "message": { + "type": "string" + } + } + } + }, + "securityDefinitions": { + "BearerAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + }, + "token": { + "type": "apiKey", + "name": "api-token", + "in": "header" + } + } +}` + +// SwaggerInfoapi holds exported Swagger Info so clients can modify it +var SwaggerInfoapi = &swag.Spec{ + Version: "1.0", + Host: "", + BasePath: "/api", + Schemes: []string{}, + Title: "管理系统API", + Description: "接口", + InfoInstanceName: "api", + SwaggerTemplate: docTemplateapi, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfoapi.InstanceName(), SwaggerInfoapi) +} diff --git a/docs/api/api_swagger.json b/docs/api/api_swagger.json new file mode 100644 index 0000000..ffed862 --- /dev/null +++ b/docs/api/api_swagger.json @@ -0,0 +1,805 @@ +{ + "swagger": "2.0", + "info": { + "description": "接口", + "title": "管理系统API", + "contact": {}, + "version": "1.0" + }, + "basePath": "/api", + "paths": { + "/": { + "get": { + "description": "首页", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "首页" + ], + "summary": "首页", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/ab": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "地址列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址" + ], + "summary": "地址列表", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "地址更新", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址" + ], + "summary": "地址更新", + "parameters": [ + { + "description": "地址表单", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.AddressBookForm" + } + } + ], + "responses": { + "200": { + "description": "null", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/ab/add": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "标签", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址" + ], + "summary": "标签添加", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/ab/personal": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "个人信息", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "个人信息", + "parameters": [ + { + "description": "string valid", + "name": "string", + "in": "body", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "用户信息", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "用户信息", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.UserPayload" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/currentUser": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "用户信息", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "用户信息", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.UserPayload" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/heartbeat": { + "post": { + "description": "心跳", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "首页" + ], + "summary": "心跳", + "responses": { + "200": { + "description": "OK" + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/login": { + "post": { + "description": "登录", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "登录" + ], + "summary": "登录", + "parameters": [ + { + "description": "登录表单", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.LoginForm" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.LoginRes" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/login-options": { + "post": { + "description": "登录选项", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "登录" + ], + "summary": "登录选项", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/logout": { + "post": { + "description": "登出", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "登录" + ], + "summary": "登出", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/peers": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "机器", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "群组" + ], + "summary": "机器", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页数量", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "状态", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "accessible", + "name": "accessible", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.DataResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/server-config": { + "get": { + "security": [ + { + "token": [] + } + ], + "description": "服务配置,给webclient提供api-server", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "WEBCLIENT" + ], + "summary": "服务配置", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/sysinfo": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "提交系统信息", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址" + ], + "summary": "提交系统信息", + "parameters": [ + { + "description": "系统信息表单", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.PeerForm" + } + } + ], + "responses": { + "200": { + "description": "SYSINFO_UPDATED,ID_NOT_FOUND", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/tags": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "标签", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址" + ], + "summary": "标签", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/model.Tag" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/users": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "用户列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "群组" + ], + "summary": "用户列表", + "parameters": [ + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页数量", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "状态", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "accessible", + "name": "accessible", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.DataResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/api.UserPayload" + } + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + } + }, + "definitions": { + "api.AddressBookForm": { + "type": "object", + "properties": { + "data": { + "type": "string", + "example": "{\"tags\":[\"tag1\",\"tag2\",\"tag3\"],\"peers\":[{\"id\":\"abc\",\"username\":\"abv-l\",\"hostname\":\"\",\"platform\":\"Windows\",\"alias\":\"\",\"tags\":[\"tag1\",\"tag2\"],\"hash\":\"hash\"}],\"tag_colors\":\"{\\\"tag1\\\":4288585374,\\\"tag2\\\":4278238420,\\\"tag3\\\":4291681337}\"}" + } + } + }, + "api.LoginForm": { + "type": "object", + "required": [ + "username" + ], + "properties": { + "password": { + "type": "string", + "maxLength": 20, + "minLength": 4 + }, + "username": { + "type": "string", + "maxLength": 10, + "minLength": 4 + } + } + }, + "api.LoginRes": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "tfa_type": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/api.UserPayload" + } + } + }, + "api.PeerForm": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "id": { + "type": "string" + }, + "memory": { + "type": "string" + }, + "os": { + "type": "string" + }, + "username": { + "type": "string" + }, + "uuid": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "api.UserPayload": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "is_admin": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "note": { + "type": "string" + }, + "status": { + "type": "integer" + } + } + }, + "model.Tag": { + "type": "object", + "properties": { + "color": { + "description": "color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度,后面6位表示颜色, 可以转成rgba", + "type": "integer" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "user_id": { + "type": "integer" + } + } + }, + "response.DataResponse": { + "type": "object", + "properties": { + "data": {}, + "total": { + "type": "integer" + } + } + }, + "response.ErrorResponse": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + }, + "response.Response": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "data": {}, + "message": { + "type": "string" + } + } + } + }, + "securityDefinitions": { + "BearerAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + }, + "token": { + "type": "apiKey", + "name": "api-token", + "in": "header" + } + } +} \ No newline at end of file diff --git a/docs/api/api_swagger.yaml b/docs/api/api_swagger.yaml new file mode 100644 index 0000000..e5f718f --- /dev/null +++ b/docs/api/api_swagger.yaml @@ -0,0 +1,513 @@ +basePath: /api +definitions: + api.AddressBookForm: + properties: + data: + example: '{"tags":["tag1","tag2","tag3"],"peers":[{"id":"abc","username":"abv-l","hostname":"","platform":"Windows","alias":"","tags":["tag1","tag2"],"hash":"hash"}],"tag_colors":"{\"tag1\":4288585374,\"tag2\":4278238420,\"tag3\":4291681337}"}' + type: string + type: object + api.LoginForm: + properties: + password: + maxLength: 20 + minLength: 4 + type: string + username: + maxLength: 10 + minLength: 4 + type: string + required: + - username + type: object + api.LoginRes: + properties: + access_token: + type: string + secret: + type: string + tfa_type: + type: string + type: + type: string + user: + $ref: '#/definitions/api.UserPayload' + type: object + api.PeerForm: + properties: + cpu: + type: string + hostname: + type: string + id: + type: string + memory: + type: string + os: + type: string + username: + type: string + uuid: + type: string + version: + type: string + type: object + api.UserPayload: + properties: + email: + type: string + is_admin: + type: boolean + name: + type: string + note: + type: string + status: + type: integer + type: object + model.Tag: + properties: + color: + description: color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度,后面6位表示颜色, + 可以转成rgba + type: integer + created_at: + type: string + id: + type: integer + name: + type: string + updated_at: + type: string + user_id: + type: integer + type: object + response.DataResponse: + properties: + data: {} + total: + type: integer + type: object + response.ErrorResponse: + properties: + error: + type: string + type: object + response.Response: + properties: + code: + type: integer + data: {} + message: + type: string + type: object +info: + contact: {} + description: 接口 + title: 管理系统API + version: "1.0" +paths: + /: + get: + consumes: + - application/json + description: 首页 + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + summary: 首页 + tags: + - 首页 + /ab: + get: + consumes: + - application/json + description: 地址列表 + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + security: + - BearerAuth: [] + summary: 地址列表 + tags: + - 地址 + post: + consumes: + - application/json + description: 地址更新 + parameters: + - description: 地址表单 + in: body + name: body + required: true + schema: + $ref: '#/definitions/api.AddressBookForm' + produces: + - application/json + responses: + "200": + description: "null" + schema: + type: string + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + security: + - BearerAuth: [] + summary: 地址更新 + tags: + - 地址 + /ab/add: + post: + consumes: + - application/json + description: 标签 + produces: + - application/json + responses: + "200": + description: OK + schema: + type: string + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + security: + - BearerAuth: [] + summary: 标签添加 + tags: + - 地址 + /ab/personal: + post: + consumes: + - application/json + description: 个人信息 + parameters: + - description: string valid + in: body + name: string + schema: + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - BearerAuth: [] + summary: 个人信息 + tags: + - 用户 + /api: + get: + consumes: + - application/json + description: 用户信息 + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.UserPayload' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 用户信息 + tags: + - 用户 + /currentUser: + get: + consumes: + - application/json + description: 用户信息 + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.UserPayload' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 用户信息 + tags: + - 用户 + /heartbeat: + post: + consumes: + - application/json + description: 心跳 + produces: + - application/json + responses: + "200": + description: OK + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + summary: 心跳 + tags: + - 首页 + /login: + post: + consumes: + - application/json + description: 登录 + parameters: + - description: 登录表单 + in: body + name: body + required: true + schema: + $ref: '#/definitions/api.LoginForm' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.LoginRes' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + summary: 登录 + tags: + - 登录 + /login-options: + post: + consumes: + - application/json + description: 登录选项 + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + type: string + type: array + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + summary: 登录选项 + tags: + - 登录 + /logout: + post: + consumes: + - application/json + description: 登出 + produces: + - application/json + responses: + "200": + description: OK + schema: + type: string + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + summary: 登出 + tags: + - 登录 + /peers: + get: + consumes: + - application/json + description: 机器 + parameters: + - description: 页码 + in: query + name: page + type: integer + - description: 每页数量 + in: query + name: pageSize + type: integer + - description: 状态 + in: query + name: status + type: integer + - description: accessible + in: query + name: accessible + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.DataResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - BearerAuth: [] + summary: 机器 + tags: + - 群组 + /server-config: + get: + consumes: + - application/json + description: 服务配置,给webclient提供api-server + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - token: [] + summary: 服务配置 + tags: + - WEBCLIENT + /sysinfo: + post: + consumes: + - application/json + description: 提交系统信息 + parameters: + - description: 系统信息表单 + in: body + name: body + required: true + schema: + $ref: '#/definitions/api.PeerForm' + produces: + - application/json + responses: + "200": + description: SYSINFO_UPDATED,ID_NOT_FOUND + schema: + type: string + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + security: + - BearerAuth: [] + summary: 提交系统信息 + tags: + - 地址 + /tags: + post: + consumes: + - application/json + description: 标签 + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/model.Tag' + type: array + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + security: + - BearerAuth: [] + summary: 标签 + tags: + - 地址 + /users: + get: + consumes: + - application/json + description: 用户列表 + parameters: + - description: 页码 + in: query + name: page + type: integer + - description: 每页数量 + in: query + name: pageSize + type: integer + - description: 状态 + in: query + name: status + type: integer + - description: accessible + in: query + name: accessible + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.DataResponse' + - properties: + data: + items: + $ref: '#/definitions/api.UserPayload' + type: array + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + security: + - BearerAuth: [] + summary: 用户列表 + tags: + - 群组 +securityDefinitions: + BearerAuth: + in: header + name: Authorization + type: apiKey + token: + in: header + name: api-token + type: apiKey +swagger: "2.0" diff --git a/docs/api_swag.png b/docs/api_swag.png new file mode 100644 index 0000000..9d7fe74 Binary files /dev/null and b/docs/api_swag.png differ diff --git a/docs/pc_ab.png b/docs/pc_ab.png new file mode 100644 index 0000000..fb8928a Binary files /dev/null and b/docs/pc_ab.png differ diff --git a/docs/pc_gr.png b/docs/pc_gr.png new file mode 100644 index 0000000..d23cc47 Binary files /dev/null and b/docs/pc_gr.png differ diff --git a/docs/web_admin.png b/docs/web_admin.png new file mode 100644 index 0000000..81c0f62 Binary files /dev/null and b/docs/web_admin.png differ diff --git a/docs/web_admin_gr.png b/docs/web_admin_gr.png new file mode 100644 index 0000000..868f6e5 Binary files /dev/null and b/docs/web_admin_gr.png differ diff --git a/docs/web_resetpwd.png b/docs/web_resetpwd.png new file mode 100644 index 0000000..3f5c679 Binary files /dev/null and b/docs/web_resetpwd.png differ diff --git a/docs/web_user.png b/docs/web_user.png new file mode 100644 index 0000000..dd405d8 Binary files /dev/null and b/docs/web_user.png differ diff --git a/docs/webclient_conf.png b/docs/webclient_conf.png new file mode 100644 index 0000000..a6e0526 Binary files /dev/null and b/docs/webclient_conf.png differ diff --git a/generate_api.go b/generate_api.go new file mode 100644 index 0000000..2146013 --- /dev/null +++ b/generate_api.go @@ -0,0 +1,5 @@ +package Gwen + +//go:generate swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin +//go:generate swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api +//go:generate go run cmd/apimain.go diff --git a/global/global.go b/global/global.go new file mode 100644 index 0000000..5459305 --- /dev/null +++ b/global/global.go @@ -0,0 +1,33 @@ +package global + +import ( + "Gwen/config" + "Gwen/lib/cache" + "Gwen/lib/jwt" + "Gwen/lib/lock" + "Gwen/lib/upload" + ut "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" + "github.com/go-redis/redis/v8" + "github.com/sirupsen/logrus" + "github.com/spf13/viper" + "gorm.io/gorm" +) + +var ( + DB *gorm.DB + Logger *logrus.Logger + Config config.Config + Viper *viper.Viper + Redis *redis.Client + Cache cache.Handler + Validator struct { + Validate *validator.Validate + VTrans ut.Translator + ValidStruct func(interface{}) []string + ValidVar func(field interface{}, tag string) []string + } + Oss *upload.Oss + Jwt *jwt.Jwt + Lock lock.Locker +) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8dda0cd --- /dev/null +++ b/go.mod @@ -0,0 +1,73 @@ +module Gwen + +go 1.22 + +require ( + github.com/antonfisher/nested-logrus-formatter v1.3.1 + github.com/fsnotify/fsnotify v1.5.1 + github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 + github.com/gin-gonic/gin v1.9.0 + github.com/go-playground/locales v0.14.1 + github.com/go-playground/universal-translator v0.18.1 + github.com/go-playground/validator/v10 v10.11.2 + github.com/go-redis/redis/v8 v8.11.4 + github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/sirupsen/logrus v1.8.1 + github.com/spf13/viper v1.9.0 + github.com/swaggo/files v1.0.1 + github.com/swaggo/gin-swagger v1.6.0 + github.com/swaggo/swag v1.16.3 + gorm.io/driver/mysql v1.5.7 + gorm.io/driver/sqlite v1.5.6 + gorm.io/gorm v1.25.7 +) + +require ( + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/bytedance/sonic v1.8.0 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/goccy/go-json v0.10.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/magiconair/properties v1.8.5 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-sqlite3 v1.14.23 // indirect + github.com/mitchellh/mapstructure v1.4.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml v1.9.4 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/spf13/afero v1.6.0 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.2.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.9 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.7.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/ini.v1 v1.63.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/http/controller/admin/addressBook.go b/http/controller/admin/addressBook.go new file mode 100644 index 0000000..e16c6f8 --- /dev/null +++ b/http/controller/admin/addressBook.go @@ -0,0 +1,191 @@ +package admin + +import ( + "Gwen/global" + "Gwen/http/request/admin" + "Gwen/http/response" + "Gwen/service" + _ "encoding/json" + "github.com/gin-gonic/gin" + "gorm.io/gorm" + "strconv" +) + +type AddressBook struct { +} + +// Detail 地址簿 +// @Tags 地址簿 +// @Summary 地址簿详情 +// @Description 地址簿详情 +// @Accept json +// @Produce json +// @Param id path int true "ID" +// @Success 200 {object} response.Response{data=model.AddressBook} +// @Failure 500 {object} response.Response +// @Router /admin/address_book/detail/{id} [get] +// @Security token +func (ct *AddressBook) Detail(c *gin.Context) { + id := c.Param("id") + iid, _ := strconv.Atoi(id) + t := service.AllService.AddressBookService.InfoByRowId(uint(iid)) + u := service.AllService.UserService.CurUser(c) + if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id { + response.Fail(c, 101, "无权限") + return + } + if t.RowId > 0 { + response.Success(c, t) + return + } + response.Fail(c, 101, "信息不存在") + return +} + +// Create 创建地址簿 +// @Tags 地址簿 +// @Summary 创建地址簿 +// @Description 创建地址簿 +// @Accept json +// @Produce json +// @Param body body admin.AddressBookForm true "地址簿信息" +// @Success 200 {object} response.Response{data=model.AddressBook} +// @Failure 500 {object} response.Response +// @Router /admin/address_book/create [post] +// @Security token +func (ct *AddressBook) Create(c *gin.Context) { + f := &admin.AddressBookForm{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "参数错误") + return + } + errList := global.Validator.ValidStruct(f) + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + t := f.ToAddressBook() + u := service.AllService.UserService.CurUser(c) + if !service.AllService.UserService.IsAdmin(u) || t.UserId == 0 { + t.UserId = u.Id + } + err := service.AllService.AddressBookService.Create(t) + if err != nil { + response.Fail(c, 101, "创建失败") + return + } + response.Success(c, u) +} + +// List 列表 +// @Tags 地址簿 +// @Summary 地址簿列表 +// @Description 地址簿列表 +// @Accept json +// @Produce json +// @Param page query int false "页码" +// @Param page_size query int false "页大小" +// @Param user_id query int false "用户id" +// @Param is_my query int false "是否是我的" +// @Success 200 {object} response.Response{data=model.AddressBookList} +// @Failure 500 {object} response.Response +// @Router /admin/address_book/list [get] +// @Security token +func (ct *AddressBook) List(c *gin.Context) { + query := &admin.AddressBookQuery{} + if err := c.ShouldBindQuery(query); err != nil { + response.Fail(c, 101, "参数错误") + return + } + u := service.AllService.UserService.CurUser(c) + if !service.AllService.UserService.IsAdmin(u) || query.IsMy == 1 { + query.UserId = int(u.Id) + } + res := service.AllService.AddressBookService.List(query.Page, query.PageSize, func(tx *gorm.DB) { + if query.UserId > 0 { + tx.Where("user_id = ?", query.UserId) + } + }) + response.Success(c, res) +} + +// Update 编辑 +// @Tags 地址簿 +// @Summary 地址簿编辑 +// @Description 地址簿编辑 +// @Accept json +// @Produce json +// @Param body body admin.AddressBookForm true "地址簿信息" +// @Success 200 {object} response.Response{data=model.AddressBook} +// @Failure 500 {object} response.Response +// @Router /admin/address_book/update [post] +// @Security token +func (ct *AddressBook) Update(c *gin.Context) { + f := &admin.AddressBookForm{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "参数错误") + return + } + errList := global.Validator.ValidStruct(f) + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + if f.RowId == 0 { + response.Fail(c, 101, "参数错误") + return + } + t := f.ToAddressBook() + u := service.AllService.UserService.CurUser(c) + if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id { + response.Fail(c, 101, "无权限") + return + } + err := service.AllService.AddressBookService.Update(t) + if err != nil { + response.Fail(c, 101, "更新失败") + return + } + response.Success(c, nil) +} + +// Delete 删除 +// @Tags 地址簿 +// @Summary 地址簿删除 +// @Description 地址簿删除 +// @Accept json +// @Produce json +// @Param body body admin.AddressBookForm true "地址簿信息" +// @Success 200 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /admin/address_book/delete [post] +// @Security token +func (ct *AddressBook) Delete(c *gin.Context) { + f := &admin.AddressBookForm{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "系统错误") + return + } + id := f.RowId + errList := global.Validator.ValidVar(id, "required,gt=0") + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + t := service.AllService.AddressBookService.InfoByRowId(f.RowId) + u := service.AllService.UserService.CurUser(c) + if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id { + response.Fail(c, 101, "无权限") + return + } + if u.Id > 0 { + err := service.AllService.AddressBookService.Delete(t) + if err == nil { + response.Success(c, nil) + return + } + response.Fail(c, 101, err.Error()) + return + } + response.Fail(c, 101, "信息不存在") +} diff --git a/http/controller/admin/file.go b/http/controller/admin/file.go new file mode 100644 index 0000000..62564e8 --- /dev/null +++ b/http/controller/admin/file.go @@ -0,0 +1,83 @@ +package admin + +import ( + "Gwen/global" + "Gwen/http/response" + "Gwen/lib/upload" + "fmt" + "github.com/gin-gonic/gin" + "os" + "time" +) + +type File struct { +} + +// OssToken 文件 +// @Tags 文件 +// @Summary 获取ossToken +// @Description 获取ossToken +// @Accept json +// @Produce json +// @Success 200 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /admin/file/oss_token [get] +// @Security token +func (f *File) OssToken(c *gin.Context) { + token := global.Oss.GetPolicyToken("") + response.Success(c, token) +} + +type FileBack struct { + upload.CallbackBaseForm + Url string `json:"url"` +} + +// Notify 上传成功后回调 +func (f *File) Notify(c *gin.Context) { + + res := global.Oss.Verify(c.Request) + if !res { + response.Fail(c, 101, "权限错误") + return + } + fm := &FileBack{} + if err := c.ShouldBind(fm); err != nil { + fmt.Println(err) + } + fm.Url = global.Config.Oss.Host + "/" + fm.Filename + response.Success(c, fm) + +} + +// Upload 上传文件到本地 +// @Tags 文件 +// @Summary 上传文件到本地 +// @Description 上传文件到本地 +// @Accept multipart/form-data +// @Produce json +// @Param file formData file true "上传文件示例" +// @Success 200 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /admin/file/upload [post] +// @Security token +func (f *File) Upload(c *gin.Context) { + file, _ := c.FormFile("file") + timePath := time.Now().Format("20060102") + "/" + webPath := "/upload/" + timePath + path := global.Config.Gin.ResourcesPath + webPath + dst := path + file.Filename + err := os.MkdirAll(path, os.ModePerm) + if err != nil { + return + } + // 上传文件至指定目录 + err = c.SaveUploadedFile(file, dst) + if err != nil { + return + } + // 返回文件web地址 + response.Success(c, gin.H{ + "url": webPath + file.Filename, + }) +} diff --git a/http/controller/admin/group.go b/http/controller/admin/group.go new file mode 100644 index 0000000..ae87edf --- /dev/null +++ b/http/controller/admin/group.go @@ -0,0 +1,160 @@ +package admin + +import ( + "Gwen/global" + "Gwen/http/request/admin" + "Gwen/http/response" + "Gwen/service" + "github.com/gin-gonic/gin" + "strconv" +) + +type Group struct { +} + +// Detail 群组 +// @Tags 群组 +// @Summary 群组详情 +// @Description 群组详情 +// @Accept json +// @Produce json +// @Param id path int true "ID" +// @Success 200 {object} response.Response{data=model.Group} +// @Failure 500 {object} response.Response +// @Router /admin/group/detail/{id} [get] +// @Security token +func (ct *Group) Detail(c *gin.Context) { + id := c.Param("id") + iid, _ := strconv.Atoi(id) + u := service.AllService.GroupService.InfoById(uint(iid)) + if u.Id > 0 { + response.Success(c, u) + return + } + response.Fail(c, 101, "信息不存在") + return +} + +// Create 创建群组 +// @Tags 群组 +// @Summary 创建群组 +// @Description 创建群组 +// @Accept json +// @Produce json +// @Param body body admin.GroupForm true "群组信息" +// @Success 200 {object} response.Response{data=model.Group} +// @Failure 500 {object} response.Response +// @Router /admin/group/create [post] +// @Security token +func (ct *Group) Create(c *gin.Context) { + f := &admin.GroupForm{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "参数错误") + return + } + errList := global.Validator.ValidStruct(f) + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + u := f.ToGroup() + err := service.AllService.GroupService.Create(u) + if err != nil { + response.Fail(c, 101, "创建失败") + return + } + response.Success(c, u) +} + +// List 列表 +// @Tags 群组 +// @Summary 群组列表 +// @Description 群组列表 +// @Accept json +// @Produce json +// @Param page query int false "页码" +// @Param page_size query int false "页大小" +// @Success 200 {object} response.Response{data=model.GroupList} +// @Failure 500 {object} response.Response +// @Router /admin/group/list [get] +// @Security token +func (ct *Group) List(c *gin.Context) { + query := &admin.PageQuery{} + if err := c.ShouldBindQuery(query); err != nil { + response.Fail(c, 101, "参数错误") + return + } + res := service.AllService.GroupService.List(query.Page, query.PageSize, nil) + response.Success(c, res) +} + +// Update 编辑 +// @Tags 群组 +// @Summary 群组编辑 +// @Description 群组编辑 +// @Accept json +// @Produce json +// @Param body body admin.GroupForm true "群组信息" +// @Success 200 {object} response.Response{data=model.Group} +// @Failure 500 {object} response.Response +// @Router /admin/group/update [post] +// @Security token +func (ct *Group) Update(c *gin.Context) { + f := &admin.GroupForm{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "参数错误") + return + } + if f.Id == 0 { + response.Fail(c, 101, "参数错误") + return + } + errList := global.Validator.ValidStruct(f) + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + u := f.ToGroup() + err := service.AllService.GroupService.Update(u) + if err != nil { + response.Fail(c, 101, "更新失败") + return + } + response.Success(c, nil) +} + +// Delete 删除 +// @Tags 群组 +// @Summary 群组删除 +// @Description 群组删除 +// @Accept json +// @Produce json +// @Param body body admin.GroupForm true "群组信息" +// @Success 200 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /admin/group/delete [post] +// @Security token +func (ct *Group) Delete(c *gin.Context) { + f := &admin.GroupForm{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "系统错误") + return + } + id := f.Id + errList := global.Validator.ValidVar(id, "required,gt=0") + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + u := service.AllService.GroupService.InfoById(f.Id) + if u.Id > 0 { + err := service.AllService.GroupService.Delete(u) + if err == nil { + response.Success(c, nil) + return + } + response.Fail(c, 101, err.Error()) + return + } + response.Fail(c, 101, "信息不存在") +} diff --git a/http/controller/admin/login.go b/http/controller/admin/login.go new file mode 100644 index 0000000..dba0c6d --- /dev/null +++ b/http/controller/admin/login.go @@ -0,0 +1,74 @@ +package admin + +import ( + "Gwen/global" + "Gwen/http/request/admin" + "Gwen/http/response" + adResp "Gwen/http/response/admin" + "Gwen/service" + "fmt" + "github.com/gin-gonic/gin" +) + +type Login struct { +} + +// Login 登录 +// @Tags 登录 +// @Summary 登录 +// @Description 登录 +// @Accept json +// @Produce json +// @Param body body admin.Login true "登录信息" +// @Success 200 {object} response.Response{data=adResp.LoginPayload} +// @Failure 500 {object} response.Response +// @Router /admin/login [post] +// @Security token +func (ct *Login) Login(c *gin.Context) { + fmt.Println("login") + f := &admin.Login{} + err := c.ShouldBindJSON(f) + if err != nil { + response.Fail(c, 101, "参数错误") + return + } + + errList := global.Validator.ValidStruct(f) + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + u := service.AllService.UserService.InfoByUsernamePassword(f.Username, f.Password) + + if u.Id == 0 { + response.Fail(c, 101, "用户名或密码错误") + return + } + + ut := service.AllService.UserService.Login(u) + + response.Success(c, &adResp.LoginPayload{ + Token: ut.Token, + Username: u.Username, + RouteNames: service.AllService.UserService.RouteNames(u), + Nickname: u.Nickname, + }) +} + +// Logout 登出 +// @Tags 登录 +// @Summary 登出 +// @Description 登出 +// @Accept json +// @Produce json +// @Success 200 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /admin/logout [post] +func (ct *Login) Logout(c *gin.Context) { + u := service.AllService.UserService.CurUser(c) + token, ok := c.Get("token") + if ok { + service.AllService.UserService.Logout(u, token.(string)) + } + response.Success(c, nil) +} diff --git a/http/controller/admin/peer.go b/http/controller/admin/peer.go new file mode 100644 index 0000000..5ab048d --- /dev/null +++ b/http/controller/admin/peer.go @@ -0,0 +1,160 @@ +package admin + +import ( + "Gwen/global" + "Gwen/http/request/admin" + "Gwen/http/response" + "Gwen/service" + "github.com/gin-gonic/gin" + "strconv" +) + +type Peer struct { +} + +// Detail 机器 +// @Tags 机器 +// @Summary 机器详情 +// @Description 机器详情 +// @Accept json +// @Produce json +// @Param id path int true "ID" +// @Success 200 {object} response.Response{data=model.Peer} +// @Failure 500 {object} response.Response +// @Router /admin/peer/detail/{id} [get] +// @Security token +func (ct *Peer) Detail(c *gin.Context) { + id := c.Param("id") + iid, _ := strconv.Atoi(id) + u := service.AllService.PeerService.InfoByRowId(uint(iid)) + if u.RowId > 0 { + response.Success(c, u) + return + } + response.Fail(c, 101, "信息不存在") + return +} + +// Create 创建机器 +// @Tags 机器 +// @Summary 创建机器 +// @Description 创建机器 +// @Accept json +// @Produce json +// @Param body body admin.PeerForm true "机器信息" +// @Success 200 {object} response.Response{data=model.Peer} +// @Failure 500 {object} response.Response +// @Router /admin/peer/create [post] +// @Security token +func (ct *Peer) Create(c *gin.Context) { + f := &admin.PeerForm{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "参数错误") + return + } + errList := global.Validator.ValidStruct(f) + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + u := f.ToPeer() + err := service.AllService.PeerService.Create(u) + if err != nil { + response.Fail(c, 101, "创建失败") + return + } + response.Success(c, u) +} + +// List 列表 +// @Tags 机器 +// @Summary 机器列表 +// @Description 机器列表 +// @Accept json +// @Produce json +// @Param page query int false "页码" +// @Param page_size query int false "页大小" +// @Success 200 {object} response.Response{data=model.PeerList} +// @Failure 500 {object} response.Response +// @Router /admin/peer/list [get] +// @Security token +func (ct *Peer) List(c *gin.Context) { + query := &admin.PageQuery{} + if err := c.ShouldBindQuery(query); err != nil { + response.Fail(c, 101, "参数错误") + return + } + res := service.AllService.PeerService.List(query.Page, query.PageSize, nil) + response.Success(c, res) +} + +// Update 编辑 +// @Tags 机器 +// @Summary 机器编辑 +// @Description 机器编辑 +// @Accept json +// @Produce json +// @Param body body admin.PeerForm true "机器信息" +// @Success 200 {object} response.Response{data=model.Peer} +// @Failure 500 {object} response.Response +// @Router /admin/peer/update [post] +// @Security token +func (ct *Peer) Update(c *gin.Context) { + f := &admin.PeerForm{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "参数错误") + return + } + if f.RowId == 0 { + response.Fail(c, 101, "参数错误") + return + } + errList := global.Validator.ValidStruct(f) + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + u := f.ToPeer() + err := service.AllService.PeerService.Update(u) + if err != nil { + response.Fail(c, 101, "更新失败") + return + } + response.Success(c, nil) +} + +// Delete 删除 +// @Tags 机器 +// @Summary 机器删除 +// @Description 机器删除 +// @Accept json +// @Produce json +// @Param body body admin.PeerForm true "机器信息" +// @Success 200 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /admin/peer/delete [post] +// @Security token +func (ct *Peer) Delete(c *gin.Context) { + f := &admin.PeerForm{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "系统错误") + return + } + id := f.RowId + errList := global.Validator.ValidVar(id, "required,gt=0") + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + u := service.AllService.PeerService.InfoByRowId(f.RowId) + if u.RowId > 0 { + err := service.AllService.PeerService.Delete(u) + if err == nil { + response.Success(c, nil) + return + } + response.Fail(c, 101, err.Error()) + return + } + response.Fail(c, 101, "信息不存在") +} diff --git a/http/controller/admin/rustdesk.go b/http/controller/admin/rustdesk.go new file mode 100644 index 0000000..382190b --- /dev/null +++ b/http/controller/admin/rustdesk.go @@ -0,0 +1,30 @@ +package admin + +import ( + "Gwen/global" + "Gwen/http/response" + "github.com/gin-gonic/gin" +) + +type Rustdesk struct { +} + +// ServerConfig 服务配置 +// @Tags ADMIN +// @Summary 服务配置 +// @Description 服务配置,给webclient提供api-server +// @Accept json +// @Produce json +// @Success 200 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /admin/server-config [get] +// @Security token +func (r *Rustdesk) ServerConfig(c *gin.Context) { + cf := &response.ServerConfigResponse{ + IdServer: global.Config.Rustdesk.IdServer, + Key: global.Config.Rustdesk.Key, + RelayServer: global.Config.Rustdesk.RelayServer, + ApiServer: global.Config.Rustdesk.ApiServer, + } + response.Success(c, cf) +} diff --git a/http/controller/admin/tag.go b/http/controller/admin/tag.go new file mode 100644 index 0000000..305ef0b --- /dev/null +++ b/http/controller/admin/tag.go @@ -0,0 +1,190 @@ +package admin + +import ( + "Gwen/global" + "Gwen/http/request/admin" + "Gwen/http/response" + "Gwen/service" + "github.com/gin-gonic/gin" + "gorm.io/gorm" + "strconv" +) + +type Tag struct { +} + +// Detail 标签 +// @Tags 标签 +// @Summary 标签详情 +// @Description 标签详情 +// @Accept json +// @Produce json +// @Param id path int true "ID" +// @Success 200 {object} response.Response{data=model.Tag} +// @Failure 500 {object} response.Response +// @Router /admin/tag/detail/{id} [get] +// @Security token +func (ct *Tag) Detail(c *gin.Context) { + id := c.Param("id") + iid, _ := strconv.Atoi(id) + t := service.AllService.TagService.InfoById(uint(iid)) + u := service.AllService.UserService.CurUser(c) + if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id { + response.Fail(c, 101, "无权限") + return + } + if t.Id > 0 { + response.Success(c, t) + return + } + response.Fail(c, 101, "信息不存在") + return +} + +// Create 创建标签 +// @Tags 标签 +// @Summary 创建标签 +// @Description 创建标签 +// @Accept json +// @Produce json +// @Param body body admin.TagForm true "标签信息" +// @Success 200 {object} response.Response{data=model.Tag} +// @Failure 500 {object} response.Response +// @Router /admin/tag/create [post] +// @Security token +func (ct *Tag) Create(c *gin.Context) { + f := &admin.TagForm{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "参数错误") + return + } + errList := global.Validator.ValidStruct(f) + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + t := f.ToTag() + u := service.AllService.UserService.CurUser(c) + if !service.AllService.UserService.IsAdmin(u) { + t.UserId = u.Id + } + err := service.AllService.TagService.Create(t) + if err != nil { + response.Fail(c, 101, "创建失败") + return + } + response.Success(c, u) +} + +// List 列表 +// @Tags 标签 +// @Summary 标签列表 +// @Description 标签列表 +// @Accept json +// @Produce json +// @Param page query int false "页码" +// @Param page_size query int false "页大小" +// @Param is_my query int false "是否是我的" +// @Param user_id query int false "用户id" +// @Success 200 {object} response.Response{data=model.TagList} +// @Failure 500 {object} response.Response +// @Router /admin/tag/list [get] +// @Security token +func (ct *Tag) List(c *gin.Context) { + query := &admin.TagQuery{} + if err := c.ShouldBindQuery(query); err != nil { + response.Fail(c, 101, "参数错误") + return + } + u := service.AllService.UserService.CurUser(c) + if !service.AllService.UserService.IsAdmin(u) || query.IsMy == 1 { + query.UserId = int(u.Id) + } + res := service.AllService.TagService.List(query.Page, query.PageSize, func(tx *gorm.DB) { + if query.UserId > 0 { + tx.Where("user_id = ?", query.UserId) + } + }) + response.Success(c, res) +} + +// Update 编辑 +// @Tags 标签 +// @Summary 标签编辑 +// @Description 标签编辑 +// @Accept json +// @Produce json +// @Param body body admin.TagForm true "标签信息" +// @Success 200 {object} response.Response{data=model.Tag} +// @Failure 500 {object} response.Response +// @Router /admin/tag/update [post] +// @Security token +func (ct *Tag) Update(c *gin.Context) { + f := &admin.TagForm{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "参数错误") + return + } + errList := global.Validator.ValidStruct(f) + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + if f.Id == 0 { + response.Fail(c, 101, "参数错误") + return + } + t := f.ToTag() + u := service.AllService.UserService.CurUser(c) + if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id { + response.Fail(c, 101, "无权限") + return + } + err := service.AllService.TagService.Update(t) + if err != nil { + response.Fail(c, 101, "更新失败") + return + } + response.Success(c, nil) +} + +// Delete 删除 +// @Tags 标签 +// @Summary 标签删除 +// @Description 标签删除 +// @Accept json +// @Produce json +// @Param body body admin.TagForm true "标签信息" +// @Success 200 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /admin/tag/delete [post] +// @Security token +func (ct *Tag) Delete(c *gin.Context) { + f := &admin.TagForm{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "系统错误") + return + } + id := f.Id + errList := global.Validator.ValidVar(id, "required,gt=0") + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + t := service.AllService.TagService.InfoById(f.Id) + u := service.AllService.UserService.CurUser(c) + if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id { + response.Fail(c, 101, "无权限") + return + } + if u.Id > 0 { + err := service.AllService.TagService.Delete(t) + if err == nil { + response.Success(c, nil) + return + } + response.Fail(c, 101, err.Error()) + return + } + response.Fail(c, 101, "信息不存在") +} diff --git a/http/controller/admin/user.go b/http/controller/admin/user.go new file mode 100644 index 0000000..45bcce6 --- /dev/null +++ b/http/controller/admin/user.go @@ -0,0 +1,261 @@ +package admin + +import ( + "Gwen/global" + "Gwen/http/request/admin" + "Gwen/http/response" + adResp "Gwen/http/response/admin" + "Gwen/service" + "github.com/gin-gonic/gin" + "gorm.io/gorm" + "strconv" +) + +type User struct { +} + +// Detail 管理员 +// @Tags 用户 +// @Summary 管理员详情 +// @Description 管理员详情 +// @Accept json +// @Produce json +// @Param id path int true "ID" +// @Success 200 {object} response.Response{data=model.User} +// @Failure 500 {object} response.Response +// @Router /admin/user/detail/{id} [get] +// @Security token +func (ct *User) Detail(c *gin.Context) { + id := c.Param("id") + iid, _ := strconv.Atoi(id) + u := service.AllService.UserService.InfoById(uint(iid)) + if u.Id > 0 { + response.Success(c, u) + return + } + response.Fail(c, 101, "信息不存在") + return +} + +// Create 管理员 +// @Tags 用户 +// @Summary 创建管理员 +// @Description 创建管理员 +// @Accept json +// @Produce json +// @Param body body admin.UserForm true "管理员信息" +// @Success 200 {object} response.Response{data=model.User} +// @Failure 500 {object} response.Response +// @Router /admin/user/create [post] +// @Security token +func (ct *User) Create(c *gin.Context) { + f := &admin.UserForm{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "参数错误") + return + } + errList := global.Validator.ValidStruct(f) + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + u := f.ToUser() + err := service.AllService.UserService.Create(u) + if err != nil { + response.Fail(c, 101, "创建失败") + return + } + response.Success(c, u) +} + +// List 列表 +// @Tags 用户 +// @Summary 管理员列表 +// @Description 管理员列表 +// @Accept json +// @Produce json +// @Param page query int false "页码" +// @Param page_size query int false "页大小" +// @Param username query int false "账户" +// @Success 200 {object} response.Response{data=model.UserList} +// @Failure 500 {object} response.Response +// @Router /admin/user/list [get] +// @Security token +func (ct *User) List(c *gin.Context) { + query := &admin.UserQuery{} + if err := c.ShouldBindQuery(query); err != nil { + response.Fail(c, 101, "参数错误") + return + } + res := service.AllService.UserService.List(query.Page, query.PageSize, func(tx *gorm.DB) { + if query.Username != "" { + tx.Where("username like ?", "%"+query.Username+"%") + } + }) + response.Success(c, res) +} + +// Update 编辑 +// @Tags 用户 +// @Summary 管理员编辑 +// @Description 管理员编辑 +// @Accept json +// @Produce json +// @Param body body admin.UserForm true "用户信息" +// @Success 200 {object} response.Response{data=model.User} +// @Failure 500 {object} response.Response +// @Router /admin/user/update [post] +// @Security token +func (ct *User) Update(c *gin.Context) { + f := &admin.UserForm{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "参数错误:"+err.Error()) + return + } + if f.Id == 0 { + response.Fail(c, 101, "参数错误") + return + } + errList := global.Validator.ValidStruct(f) + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + u := f.ToUser() + err := service.AllService.UserService.Update(u) + if err != nil { + response.Fail(c, 101, "更新失败") + return + } + response.Success(c, nil) +} + +// Delete 删除 +// @Tags 用户 +// @Summary 管理员删除 +// @Description 管理员编删除 +// @Accept json +// @Produce json +// @Param body body admin.UserForm true "用户信息" +// @Success 200 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /admin/user/delete [post] +// @Security token +func (ct *User) Delete(c *gin.Context) { + f := &admin.UserForm{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "系统错误") + return + } + id := f.Id + errList := global.Validator.ValidVar(id, "required,gt=0") + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + u := service.AllService.UserService.InfoById(f.Id) + if u.Id > 0 { + err := service.AllService.UserService.Delete(u) + if err == nil { + response.Success(c, nil) + return + } + response.Fail(c, 101, err.Error()) + return + } + response.Fail(c, 101, "信息不存在") +} + +// UpdatePassword 修改密码 +// @Tags 用户 +// @Summary 修改密码 +// @Description 修改密码 +// @Accept json +// @Produce json +// @Param body body admin.UserPasswordForm true "用户信息" +// @Success 200 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /admin/user/updatePassword [post] +// @Security token +func (ct *User) UpdatePassword(c *gin.Context) { + f := &admin.UserPasswordForm{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "参数错误") + return + } + errList := global.Validator.ValidStruct(f) + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + u := service.AllService.UserService.InfoById(f.Id) + if u.Id == 0 { + response.Fail(c, 101, "信息不存在") + return + } + err := service.AllService.UserService.UpdatePassword(u, f.Password) + if err != nil { + response.Fail(c, 101, "更新失败") + return + } + response.Success(c, nil) +} + +// Current 当前用户 +// @Tags 用户 +// @Summary 当前用户 +// @Description 当前用户 +// @Accept json +// @Produce json +// @Success 200 {object} response.Response{data=adResp.LoginPayload} +// @Failure 500 {object} response.Response +// @Router /admin/user/current [get] +// @Security token +func (ct *User) Current(c *gin.Context) { + u := service.AllService.UserService.CurUser(c) + token, _ := c.Get("token") + t := token.(string) + response.Success(c, &adResp.LoginPayload{ + Token: t, + Username: u.Username, + RouteNames: service.AllService.UserService.RouteNames(u), + Nickname: u.Nickname, + }) +} + +// ChangeCurPwd 修改当前用户密码 +// @Tags 用户 +// @Summary 修改当前用户密码 +// @Description 修改当前用户密码 +// @Accept json +// @Produce json +// @Param body body admin.ChangeCurPasswordForm true "用户信息" +// @Success 200 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /admin/user/changeCurPwd [post] +// @Security token +func (ct *User) ChangeCurPwd(c *gin.Context) { + f := &admin.ChangeCurPasswordForm{} + if err := c.ShouldBindJSON(f); err != nil { + response.Fail(c, 101, "参数错误") + return + } + + errList := global.Validator.ValidStruct(f) + if len(errList) > 0 { + response.Fail(c, 101, errList[0]) + return + } + u := service.AllService.UserService.CurUser(c) + oldPwd := service.AllService.UserService.EncryptPassword(f.OldPassword) + if u.Password != oldPwd { + response.Fail(c, 101, "旧密码错误") + return + } + err := service.AllService.UserService.UpdatePassword(u, f.NewPassword) + if err != nil { + response.Fail(c, 101, "更新失败") + return + } + response.Success(c, nil) +} diff --git a/http/controller/api/ab.go b/http/controller/api/ab.go new file mode 100644 index 0000000..ddfb026 --- /dev/null +++ b/http/controller/api/ab.go @@ -0,0 +1,150 @@ +package api + +import ( + requstform "Gwen/http/request/api" + "Gwen/http/response" + "Gwen/http/response/api" + "Gwen/model" + "Gwen/service" + "encoding/json" + "fmt" + "github.com/gin-gonic/gin" + "net/http" +) + +type Ab struct { +} + +// Ab +// @Tags 地址 +// @Summary 地址列表 +// @Description 地址列表 +// @Accept json +// @Produce json +// @Success 200 {object} response.Response +// @Failure 500 {object} response.ErrorResponse +// @Router /ab [get] +// @Security BearerAuth +func (a *Ab) Ab(c *gin.Context) { + user := service.AllService.UserService.CurUser(c) + + al := service.AllService.AddressBookService.ListByUserId(user.Id, 1, 1000) + tags := service.AllService.TagService.ListByUserId(user.Id) + + tagColors := map[string]uint{} + //将tags中的name转成一个以逗号分割的字符串 + var tagNames []string + for _, tag := range tags.Tags { + tagNames = append(tagNames, tag.Name) + tagColors[tag.Name] = tag.Color + } + tgc, _ := json.Marshal(tagColors) + res := &api.AbList{ + Peers: al.AddressBooks, + Tags: tagNames, + TagColors: string(tgc), + } + data, _ := json.Marshal(res) + c.JSON(http.StatusOK, gin.H{ + "data": string(data), + //"licensed_devices": 999, + }) +} + +// UpAb +// @Tags 地址 +// @Summary 地址更新 +// @Description 地址更新 +// @Accept json +// @Produce json +// @Param body body requstform.AddressBookForm true "地址表单" +// @Success 200 {string} string "null" +// @Failure 500 {object} response.ErrorResponse +// @Router /ab [post] +// @Security BearerAuth +func (a *Ab) UpAb(c *gin.Context) { + abf := &requstform.AddressBookForm{} + err := c.ShouldBindJSON(&abf) + if err != nil { + fmt.Println(err) + response.Error(c, "参数错误") + return + } + abd := &requstform.AddressBookFormData{} + err = json.Unmarshal([]byte(abf.Data), abd) + if err != nil { + response.Error(c, "系统错误") + return + } + + //fmt.Println(abd) + //for _, peer := range abd.Peers { + // fmt.Println(peer) + //} + + user := service.AllService.UserService.CurUser(c) + + err = service.AllService.AddressBookService.UpdateAddressBook(abd.Peers, user.Id) + if err != nil { + c.Abort() + return + } + + tc := map[string]uint{} + err = json.Unmarshal([]byte(abd.TagColors), &tc) + if err != nil { + fmt.Println(err) + response.Error(c, "系统错误") + return + } else { + service.AllService.TagService.UpdateTags(user.Id, tc) + } + + c.JSON(http.StatusOK, nil) +} + +// Tags +// @Tags 地址 +// @Summary 标签 +// @Description 标签 +// @Accept json +// @Produce json +// @Success 200 {object} []model.Tag +// @Failure 500 {object} response.ErrorResponse +// @Router /tags [post] +// @Security BearerAuth +func (a *Ab) Tags(c *gin.Context) { + user := service.AllService.UserService.CurUser(c) + + tags := service.AllService.TagService.ListByUserId(user.Id) + c.JSON(http.StatusOK, tags.Tags) +} + +// TagAdd +// @Tags 地址 +// @Summary 标签添加 +// @Description 标签 +// @Accept json +// @Produce json +// @Success 200 {string} string +// @Failure 500 {object} response.ErrorResponse +// @Router /ab/add [post] +// @Security BearerAuth +func (a *Ab) TagAdd(c *gin.Context) { + t := &model.Tag{} + err := c.ShouldBindJSON(t) + if err != nil { + fmt.Println(err) + response.Error(c, "参数错误") + return + + } + //u := service.AllService.UserService.CurUser(c) + + //err = service.AllService.TagService.UpdateTags(t.Name, t.Color, user.Id) + //if err != nil { + // response.Error(c, "操作失败") + // return + //} + c.JSON(http.StatusOK, "") +} diff --git a/http/controller/api/group.go b/http/controller/api/group.go new file mode 100644 index 0000000..31c3c0f --- /dev/null +++ b/http/controller/api/group.go @@ -0,0 +1,115 @@ +package api + +import ( + apiReq "Gwen/http/request/api" + "Gwen/http/response" + apiResp "Gwen/http/response/api" + "Gwen/model" + "Gwen/service" + "github.com/gin-gonic/gin" + "net/http" +) + +type Group struct { +} + +// Users 用户列表 +// @Tags 群组 +// @Summary 用户列表 +// @Description 用户列表 +// @Accept json +// @Produce json +// @Param page query int false "页码" +// @Param pageSize query int false "每页数量" +// @Param status query int false "状态" +// @Param accessible query string false "accessible" +// @Success 200 {object} response.DataResponse{data=[]apiResp.UserPayload} +// @Failure 500 {object} response.ErrorResponse +// @Router /users [get] +// @Security BearerAuth +func (g *Group) Users(c *gin.Context) { + u := service.AllService.UserService.CurUser(c) + + if !*u.IsAdmin { + gr := service.AllService.GroupService.InfoById(u.GroupId) + if gr.Type != model.GroupTypeShare { + response.Error(c, "不是管理员也不在分享组") + return + } + } + + q := &apiReq.UserListQuery{} + err := c.ShouldBindQuery(&q) + if err != nil { + response.Error(c, err.Error()) + return + } + userList := service.AllService.UserService.ListByGroupId(u.GroupId, q.Page, q.PageSize) + var data []*apiResp.UserPayload + for _, user := range userList.Users { + up := &apiResp.UserPayload{} + up.FromUser(user) + data = append(data, up) + } + c.JSON(http.StatusOK, response.DataResponse{ + Total: uint(userList.Total), + Data: data, + }) +} + +// Peers +// @Tags 群组 +// @Summary 机器 +// @Description 机器 +// @Accept json +// @Produce json +// @Param page query int false "页码" +// @Param pageSize query int false "每页数量" +// @Param status query int false "状态" +// @Param accessible query string false "accessible" +// @Success 200 {object} response.DataResponse +// @Failure 500 {object} response.Response +// @Router /peers [get] +// @Security BearerAuth +func (g *Group) Peers(c *gin.Context) { + u := service.AllService.UserService.CurUser(c) + + if !*u.IsAdmin { + gr := service.AllService.GroupService.InfoById(u.GroupId) + if gr.Type != model.GroupTypeShare { + response.Error(c, "不是管理员也不在分享组") + return + } + } + + q := &apiReq.PeerListQuery{} + err := c.ShouldBindQuery(&q) + if err != nil { + response.Error(c, err.Error()) + return + } + + users := service.AllService.UserService.ListIdAndNameByGroupId(u.GroupId) + namesById := make(map[uint]string) + userIds := make([]uint, 0) + for _, user := range users { + namesById[user.Id] = user.Username + userIds = append(userIds, user.Id) + } + peerList := service.AllService.AddressBookService.ListByUserIds(userIds, q.Page, q.PageSize) + var data []*apiResp.GroupPeerPayload + for _, ab := range peerList.AddressBooks { + uname, ok := namesById[ab.UserId] + if !ok { + uname = "" + } + pp := &apiResp.GroupPeerPayload{} + pp.FromAddressBook(ab, uname) + data = append(data, pp) + + } + c.JSON(http.StatusOK, response.DataResponse{ + Total: uint(peerList.Total), + Data: data, + }) +} diff --git a/http/controller/api/index.go b/http/controller/api/index.go new file mode 100644 index 0000000..e58f2bc --- /dev/null +++ b/http/controller/api/index.go @@ -0,0 +1,39 @@ +package api + +import ( + "Gwen/http/response" + "github.com/gin-gonic/gin" + "net/http" +) + +type Index struct { +} + +// Index 首页 +// @Tags 首页 +// @Summary 首页 +// @Description 首页 +// @Accept json +// @Produce json +// @Success 200 {object} response.Response +// @Failure 500 {object} response.Response +// @Router / [get] +func (i *Index) Index(c *gin.Context) { + response.Success( + c, + "Hello Gwen", + ) +} + +// Heartbeat 心跳 +// @Tags 首页 +// @Summary 心跳 +// @Description 心跳 +// @Accept json +// @Produce json +// @Success 200 {object} nil +// @Failure 500 {object} response.Response +// @Router /heartbeat [post] +func (i *Index) Heartbeat(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{}) +} diff --git a/http/controller/api/login.go b/http/controller/api/login.go new file mode 100644 index 0000000..5fd1c65 --- /dev/null +++ b/http/controller/api/login.go @@ -0,0 +1,90 @@ +package api + +import ( + "Gwen/global" + "Gwen/http/request/api" + "Gwen/http/response" + apiResp "Gwen/http/response/api" + "Gwen/service" + "github.com/gin-gonic/gin" + "net/http" +) + +type Login struct { +} + +// Login 登录 +// @Tags 登录 +// @Summary 登录 +// @Description 登录 +// @Accept json +// @Produce json +// @Param body body api.LoginForm true "登录表单" +// @Success 200 {object} apiResp.LoginRes +// @Failure 500 {object} response.ErrorResponse +// @Router /login [post] +func (l *Login) Login(c *gin.Context) { + f := &api.LoginForm{} + err := c.ShouldBindJSON(f) + if err != nil { + response.Error(c, "系统错误") + return + } + + errList := global.Validator.ValidStruct(f) + if len(errList) > 0 { + response.Error(c, errList[0]) + return + } + + u := service.AllService.UserService.InfoByUsernamePassword(f.Username, f.Password) + + if u.Id == 0 { + response.Error(c, "用户名或密码错误") + return + } + + ut := service.AllService.UserService.Login(u) + + c.JSON(http.StatusOK, apiResp.LoginRes{ + AccessToken: ut.Token, + Type: "access_token", + User: *(&apiResp.UserPayload{}).FromUser(u), + }) +} + +// LoginOptions +// @Tags 登录 +// @Summary 登录选项 +// @Description 登录选项 +// @Accept json +// @Produce json +// @Success 200 {object} []string +// @Failure 500 {object} response.ErrorResponse +// @Router /login-options [post] +func (l *Login) LoginOptions(c *gin.Context) { + test := []string{ + //"common-oidc/[{\"name\":\"google\"},{\"name\":\"github\"},{\"name\":\"facebook\"},{\"name\":\"网页授权登录\",\"icon\":\"\"}]", + //"oidc/myapp", + } + c.JSON(http.StatusOK, test) +} + +// Logout +// @Tags 登录 +// @Summary 登出 +// @Description 登出 +// @Accept json +// @Produce json +// @Success 200 {string} string +// @Failure 500 {object} response.ErrorResponse +// @Router /logout [post] +func (l *Login) Logout(c *gin.Context) { + u := service.AllService.UserService.CurUser(c) + token, ok := c.Get("token") + if ok { + service.AllService.UserService.Logout(u, token.(string)) + } + c.JSON(http.StatusOK, nil) + +} diff --git a/http/controller/api/peer.go b/http/controller/api/peer.go new file mode 100644 index 0000000..985ddd8 --- /dev/null +++ b/http/controller/api/peer.go @@ -0,0 +1,48 @@ +package api + +import ( + requstform "Gwen/http/request/api" + "Gwen/http/response" + "Gwen/service" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "net/http" +) + +type Peer struct { +} + +// SysInfo +// @Tags 地址 +// @Summary 提交系统信息 +// @Description 提交系统信息 +// @Accept json +// @Produce json +// @Param body body requstform.PeerForm true "系统信息表单" +// @Success 200 {string} string "SYSINFO_UPDATED,ID_NOT_FOUND" +// @Failure 500 {object} response.ErrorResponse +// @Router /sysinfo [post] +// @Security BearerAuth +func (p *Peer) SysInfo(c *gin.Context) { + f := &requstform.PeerForm{} + err := c.ShouldBindBodyWith(f, binding.JSON) + if err != nil { + response.Error(c, err.Error()) + return + } + + pe := service.AllService.PeerService.FindById(f.Id) + if pe == nil || pe.RowId == 0 { + pe = f.ToPeer() + err = service.AllService.PeerService.Create(pe) + if err != nil { + response.Error(c, err.Error()) + return + } + } + + //SYSINFO_UPDATED 上传成功 + //ID_NOT_FOUND 下次心跳会上传 + //直接响应文本 + c.String(http.StatusOK, "") +} diff --git a/http/controller/api/user.go b/http/controller/api/user.go new file mode 100644 index 0000000..a8ccc20 --- /dev/null +++ b/http/controller/api/user.go @@ -0,0 +1,74 @@ +package api + +import ( + apiResp "Gwen/http/response/api" + "Gwen/service" + "fmt" + "github.com/gin-gonic/gin" + "net/http" +) + +type User struct { +} + +// currentUser 当前用户 +// @Tags 用户 +// @Summary 用户信息 +// @Description 用户信息 +// @Accept json +// @Produce json +// @Success 200 {object} apiResp.UserPayload +// @Failure 500 {object} response.Response +// @Router /currentUser [get] +// @Security token +func (u *User) currentUser(c *gin.Context) { + user := service.AllService.UserService.CurUser(c) + up := (&apiResp.UserPayload{}).FromUser(user) + c.JSON(http.StatusOK, up) +} + +// Info 用户信息 +// @Tags 用户 +// @Summary 用户信息 +// @Description 用户信息 +// @Accept json +// @Produce json +// @Success 200 {object} apiResp.UserPayload +// @Failure 500 {object} response.Response +// @Router /api [get] +// @Security token +func (u *User) Info(c *gin.Context) { + user := service.AllService.UserService.CurUser(c) + up := (&apiResp.UserPayload{}).FromUser(user) + c.JSON(http.StatusOK, up) +} + +// Personal +// @Tags 用户 +// @Summary 个人信息 +// @Description 个人信息 +// @Accept json +// @Produce json +// @Param string body string false "string valid" +// @Success 200 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /ab/personal [post] +// @Security BearerAuth +func (u *User) Personal(c *gin.Context) { + //打印全部body + fmt.Println(c.Request.Body) + + /** + guid = json['guid'] ?? '', + name = json['name'] ?? '', + owner = json['owner'] ?? '', + note = json['note'] ?? '', + rule = json['rule'] ?? 0; + */ + //如果返回了guid,后面的请求会有变化 + c.JSON(http.StatusOK, gin.H{ + //"guid": "123456", + //"name": "admindddd", + //"rule": 1, + }) +} diff --git a/http/controller/api/webClient.go b/http/controller/api/webClient.go new file mode 100644 index 0000000..34c73c0 --- /dev/null +++ b/http/controller/api/webClient.go @@ -0,0 +1,42 @@ +package api + +import ( + "Gwen/global" + "Gwen/http/response" + "Gwen/http/response/api" + "Gwen/service" + "github.com/gin-gonic/gin" +) + +type WebClient struct { +} + +// ServerConfig 服务配置 +// @Tags WEBCLIENT +// @Summary 服务配置 +// @Description 服务配置,给webclient提供api-server +// @Accept json +// @Produce json +// @Success 200 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /server-config [get] +// @Security token +func (i *WebClient) ServerConfig(c *gin.Context) { + u := service.AllService.UserService.CurUser(c) + + peers := map[string]*api.WebClientPeerPayload{} + abs := service.AllService.AddressBookService.ListByUserId(u.Id, 1, 100) + for _, ab := range abs.AddressBooks { + pp := &api.WebClientPeerPayload{} + pp.FromAddressBook(ab) + peers[ab.Id] = pp + } + response.Success( + c, + gin.H{ + "id_server": global.Config.Rustdesk.IdServer, + "key": global.Config.Rustdesk.Key, + //"peers": peers, + }, + ) +} diff --git a/http/controller/web/index.go b/http/controller/web/index.go new file mode 100644 index 0000000..7e549ec --- /dev/null +++ b/http/controller/web/index.go @@ -0,0 +1,68 @@ +package web + +import ( + "Gwen/global" + "github.com/gin-gonic/gin" +) + +type Index struct { +} + +func (i *Index) ConfigJs(c *gin.Context) { + apiServer := global.Config.Rustdesk.ApiServer + + tmp := ` + window._gwen = {} + window._gwen.kv = {} + function getQueryVariable() { + const query = window.location.hash.substring(3); + const vars = query.split("&"); + for (var i = 0; i < vars.length; i++) { + var pair = vars[i].split("="); + window._gwen.kv[pair[0]] = pair[1] + } + } + getQueryVariable() + const id = window._gwen.kv.id || '' + if (id) { + localStorage.setItem('remote-id', id) + } + window._gwen.hosts = [ + "rs-sg.rustdesk.com", + "rs-cn.rustdesk.com", + "rs-us.rustdesk.com", + ] +localStorage.setItem('api-server', "` + apiServer + `") +const autoWriteServer = () => { + return setTimeout(() => { + const token = localStorage.getItem('access_token') + const apiserver = localStorage.getItem('api-server') + if (token && apiserver) { + fetch(apiserver + "/api/server-config", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer ' + token + } + } + ).then(res => res.json()).then(res => { + if (res.code === 0) { + if(!localStorage.getItem('custom-rendezvous-server') || !localStorage.getItem('key') ) { + localStorage.setItem('custom-rendezvous-server', res.data.id_server) + localStorage.setItem('key', res.data.key) + } + + if (res.data.peers) { + localStorage.setItem('peers', JSON.stringify(res.data.peers)) + } + } + }) + } else { + autoWriteServer() + } + }, 1000) + } + autoWriteServer() +` + c.String(200, tmp) +} diff --git a/http/http.go b/http/http.go new file mode 100644 index 0000000..3908339 --- /dev/null +++ b/http/http.go @@ -0,0 +1,30 @@ +package http + +import ( + "Gwen/global" + "Gwen/http/middleware" + "Gwen/http/router" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "net/http" +) + +func ApiInit() { + gin.SetMode(global.Config.Gin.Mode) + g := gin.New() + + if global.Config.Gin.Mode == gin.ReleaseMode { + //修改gin Recovery日志 输出为logger的输出点 + if global.Logger != nil { + gin.DefaultErrorWriter = global.Logger.WriterLevel(logrus.ErrorLevel) + } + } + g.NoRoute(func(c *gin.Context) { + c.String(http.StatusNotFound, "404 not found") + }) + g.Use(middleware.Logger(), gin.Recovery()) + router.WebInit(g) + router.Init(g) + router.ApiInit(g) + Run(g, global.Config.Gin.ApiAddr) +} diff --git a/http/middleware/admin.go b/http/middleware/admin.go new file mode 100644 index 0000000..f312f9b --- /dev/null +++ b/http/middleware/admin.go @@ -0,0 +1,32 @@ +package middleware + +import ( + "Gwen/http/response" + "Gwen/service" + "github.com/gin-gonic/gin" +) + +// AdminAuth 后台权限验证中间件 +func AdminAuth() gin.HandlerFunc { + return func(c *gin.Context) { + + //测试先关闭 + token := c.GetHeader("api-token") + if token == "" { + response.Fail(c, 403, "请先登录") + c.Abort() + return + } + user := service.AllService.UserService.InfoByAccessToken(token) + if user.Id == 0 { + response.Fail(c, 403, "请先登录") + c.Abort() + return + } + + c.Set("curUser", user) + c.Set("token", token) + + c.Next() + } +} diff --git a/http/middleware/admin_privilege.go b/http/middleware/admin_privilege.go new file mode 100644 index 0000000..bd1267d --- /dev/null +++ b/http/middleware/admin_privilege.go @@ -0,0 +1,22 @@ +package middleware + +import ( + "Gwen/http/response" + "Gwen/service" + "github.com/gin-gonic/gin" +) + +// AdminPrivilege ... +func AdminPrivilege() gin.HandlerFunc { + return func(c *gin.Context) { + u := service.AllService.UserService.CurUser(c) + + if !service.AllService.UserService.IsAdmin(u) { + response.Fail(c, 403, "无权限") + c.Abort() + return + } + + c.Next() + } +} diff --git a/http/middleware/cors.go b/http/middleware/cors.go new file mode 100644 index 0000000..f60a462 --- /dev/null +++ b/http/middleware/cors.go @@ -0,0 +1,23 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "net/http" +) + +// Cors 跨域 +func Cors() gin.HandlerFunc { + return func(c *gin.Context) { + origin := c.GetHeader("Origin") + //fmt.Println("origin", origin) + c.Header("Access-Control-Allow-Origin", origin) + c.Header("Access-Control-Allow-Headers", "api-token,content-type,authorization ") + c.Header("Access-Control-Allow-Methods", c.Request.Method) + c.Header("Access-Control-Allow-Credentials", "true") + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(http.StatusNoContent) + return + } + c.Next() + } +} diff --git a/http/middleware/jwt.go b/http/middleware/jwt.go new file mode 100644 index 0000000..b9b0599 --- /dev/null +++ b/http/middleware/jwt.go @@ -0,0 +1,50 @@ +package middleware + +import ( + "Gwen/global" + "Gwen/http/response" + "Gwen/service" + "github.com/gin-gonic/gin" +) + +func JwtAuth() gin.HandlerFunc { + return func(c *gin.Context) { + //测试先关闭 + token := c.GetHeader("api-token") + if token == "" { + response.Fail(c, 403, "请先登录") + c.Abort() + return + } + uid, err := global.Jwt.ParseToken(token) + if err != nil { + response.Fail(c, 403, "请先登录") + c.Abort() + return + } + if uid == 0 { + response.Fail(c, 403, "请先登录") + c.Abort() + return + } + + user := service.AllService.UserService.InfoById(uid) + //user := &model.User{ + // Id: uid, + // Username: "测试用户", + //} + if user.Id == 0 { + response.Fail(c, 403, "请先登录") + c.Abort() + return + } + if !service.AllService.UserService.CheckUserEnable(user) { + response.Fail(c, 101, "你已被禁用") + c.Abort() + return + } + c.Set("curUser", user) + + c.Next() + } +} diff --git a/http/middleware/logger.go b/http/middleware/logger.go new file mode 100644 index 0000000..53af2e9 --- /dev/null +++ b/http/middleware/logger.go @@ -0,0 +1,20 @@ +package middleware + +import ( + "Gwen/global" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" +) + +// Logger 日志中间件 +func Logger() gin.HandlerFunc { + return func(c *gin.Context) { + global.Logger.WithFields( + logrus.Fields{ + "uri": c.Request.URL, + "ip": c.ClientIP(), + "method": c.Request.Method, + }).Debug("Request") + c.Next() + } +} diff --git a/http/middleware/rustauth.go b/http/middleware/rustauth.go new file mode 100644 index 0000000..a627968 --- /dev/null +++ b/http/middleware/rustauth.go @@ -0,0 +1,44 @@ +package middleware + +import ( + "Gwen/service" + "github.com/gin-gonic/gin" +) + +func RustAuth() gin.HandlerFunc { + return func(c *gin.Context) { + + //获取HTTP_AUTHORIZATION + token := c.GetHeader("Authorization") + if token == "" { + c.JSON(401, gin.H{ + "error": "Unauthorized", + }) + c.Abort() + return + } + //提取token,格式是Bearer {token} + //这里只是简单的提取 + token = token[7:] + //验证token + user := service.AllService.UserService.InfoByAccessToken(token) + if user.Id == 0 { + c.JSON(401, gin.H{ + "error": "Unauthorized", + }) + c.Abort() + return + } + if !service.AllService.UserService.CheckUserEnable(user) { + c.JSON(401, gin.H{ + "error": "账号已被禁用", + }) + c.Abort() + return + } + + c.Set("curUser", user) + c.Set("token", token) + c.Next() + } +} diff --git a/http/request/admin/addressBook.go b/http/request/admin/addressBook.go new file mode 100644 index 0000000..5a25fbc --- /dev/null +++ b/http/request/admin/addressBook.go @@ -0,0 +1,56 @@ +package admin + +import ( + "Gwen/model" + "encoding/json" +) + +type AddressBookForm struct { + RowId uint `json:"row_id"` + Id string `json:"id" validate:"required"` + Username string `json:"username" ` + Password string `json:"password" ` + Hostname string `json:"hostname" ` + Alias string `json:"alias" ` + Platform string `json:"platform" ` + Tags []string `json:"tags"` + Hash string `json:"hash"` + UserId uint `json:"user_id"` + ForceAlwaysRelay bool `json:"force_always_relay"` + RdpPort string `json:"rdp_port"` + RdpUsername string `json:"rdp_username"` + Online bool `json:"online"` + LoginName string `json:"login_name" ` + SameServer bool `json:"same_server"` +} + +func (a AddressBookForm) ToAddressBook() *model.AddressBook { + //tags转换 + tags, _ := json.Marshal(a.Tags) + + return &model.AddressBook{ + RowId: a.RowId, + Id: a.Id, + Username: a.Username, + Password: a.Password, + Hostname: a.Hostname, + Alias: a.Alias, + Platform: a.Platform, + Tags: tags, + Hash: a.Hash, + UserId: a.UserId, + ForceAlwaysRelay: a.ForceAlwaysRelay, + RdpPort: a.RdpPort, + RdpUsername: a.RdpUsername, + Online: a.Online, + LoginName: a.LoginName, + SameServer: a.SameServer, + } + +} + +type AddressBookQuery struct { + UserId int `form:"user_id"` + IsMy int `form:"is_my"` + PageQuery +} diff --git a/http/request/admin/group.go b/http/request/admin/group.go new file mode 100644 index 0000000..dc2d35d --- /dev/null +++ b/http/request/admin/group.go @@ -0,0 +1,21 @@ +package admin + +import "Gwen/model" + +type GroupForm struct { + Id uint `json:"id"` + Name string `json:"name" validate:"required"` +} + +func (gf *GroupForm) FromGroup(group *model.Group) *GroupForm { + gf.Id = group.Id + gf.Name = group.Name + return gf +} + +func (gf *GroupForm) ToGroup() *model.Group { + group := &model.Group{} + group.Id = gf.Id + group.Name = gf.Name + return group +} diff --git a/http/request/admin/login.go b/http/request/admin/login.go new file mode 100644 index 0000000..691f7a7 --- /dev/null +++ b/http/request/admin/login.go @@ -0,0 +1,6 @@ +package admin + +type Login struct { + Username string `json:"username" validate:"required" label:"用户名"` + Password string `json:"password,omitempty" validate:"required" label:"密码"` +} diff --git a/http/request/admin/peer.go b/http/request/admin/peer.go new file mode 100644 index 0000000..6b35411 --- /dev/null +++ b/http/request/admin/peer.go @@ -0,0 +1,30 @@ +package admin + +import "Gwen/model" + +type PeerForm struct { + RowId uint `json:"row_id" ` + Id string `json:"id"` + Cpu string `json:"cpu"` + Hostname string `json:"hostname"` + Memory string `json:"memory"` + Os string `json:"os"` + Username string `json:"username"` + Uuid string `json:"uuid"` + Version string `json:"version"` +} + +// ToPeer +func (f *PeerForm) ToPeer() *model.Peer { + return &model.Peer{ + RowId: f.RowId, + Id: f.Id, + Cpu: f.Cpu, + Hostname: f.Hostname, + Memory: f.Memory, + Os: f.Os, + Username: f.Username, + Uuid: f.Uuid, + Version: f.Version, + } +} diff --git a/http/request/admin/tag.go b/http/request/admin/tag.go new file mode 100644 index 0000000..3a07377 --- /dev/null +++ b/http/request/admin/tag.go @@ -0,0 +1,33 @@ +package admin + +import "Gwen/model" + +type TagForm struct { + Id uint `json:"id"` + Name string `json:"name" validate:"required"` + Color uint `json:"color" validate:"required"` + UserId uint `json:"user_id" validate:"required"` +} + +func (f *TagForm) FromTag(group *model.Tag) *TagForm { + f.Id = group.Id + f.Name = group.Name + f.Color = group.Color + f.UserId = group.UserId + return f +} + +func (f *TagForm) ToTag() *model.Tag { + i := &model.Tag{} + i.Id = f.Id + i.Name = f.Name + i.Color = f.Color + i.UserId = f.UserId + return i +} + +type TagQuery struct { + UserId int `form:"user_id"` + IsMy int `form:"is_my"` + PageQuery +} diff --git a/http/request/admin/user.go b/http/request/admin/user.go new file mode 100644 index 0000000..6e09796 --- /dev/null +++ b/http/request/admin/user.go @@ -0,0 +1,57 @@ +package admin + +import ( + "Gwen/model" +) + +type UserForm struct { + Id uint `json:"id"` + Username string `json:"username" validate:"required,gte=4,lte=10"` + //Password string `json:"password" validate:"required,gte=4,lte=20"` + Nickname string `json:"nickname" validate:"required"` + Avatar string `json:"avatar"` + GroupId uint `json:"group_id" validate:"required"` + IsAdmin *bool `json:"is_admin" ` + Status model.StatusCode `json:"status" validate:"required,gte=0"` +} + +func (uf *UserForm) FromUser(user *model.User) *UserForm { + uf.Id = user.Id + uf.Username = user.Username + uf.Nickname = user.Nickname + uf.Avatar = user.Avatar + uf.GroupId = user.GroupId + uf.IsAdmin = user.IsAdmin + uf.Status = user.Status + return uf +} +func (uf *UserForm) ToUser() *model.User { + user := &model.User{} + user.Id = uf.Id + user.Username = uf.Username + user.Nickname = uf.Nickname + user.Avatar = uf.Avatar + user.GroupId = uf.GroupId + user.IsAdmin = uf.IsAdmin + user.Status = uf.Status + return user +} + +type PageQuery struct { + Page uint `form:"page"` + PageSize uint `form:"page_size"` +} + +type UserQuery struct { + PageQuery + Username string `form:"username"` +} +type UserPasswordForm struct { + Id uint `json:"id" validate:"required"` + Password string `json:"password" validate:"required,gte=4,lte=20"` +} + +type ChangeCurPasswordForm struct { + OldPassword string `json:"old_password" validate:"required,gte=4,lte=20"` + NewPassword string `json:"new_password" validate:"required,gte=4,lte=20"` +} diff --git a/http/request/api/peer.go b/http/request/api/peer.go new file mode 100644 index 0000000..00955e4 --- /dev/null +++ b/http/request/api/peer.go @@ -0,0 +1,37 @@ +package api + +import "Gwen/model" + +type AddressBookFormData struct { + Tags []string `json:"tags"` + Peers []*model.AddressBook `json:"peers"` + TagColors string `json:"tag_colors"` +} + +type AddressBookForm struct { + Data string `json:"data" example:"{\"tags\":[\"tag1\",\"tag2\",\"tag3\"],\"peers\":[{\"id\":\"abc\",\"username\":\"abv-l\",\"hostname\":\"\",\"platform\":\"Windows\",\"alias\":\"\",\"tags\":[\"tag1\",\"tag2\"],\"hash\":\"hash\"}],\"tag_colors\":\"{\\\"tag1\\\":4288585374,\\\"tag2\\\":4278238420,\\\"tag3\\\":4291681337}\"}"` +} + +type PeerForm struct { + Cpu string `json:"cpu"` + Hostname string `json:"hostname"` + Id string `json:"id"` + Memory string `json:"memory"` + Os string `json:"os"` + Username string `json:"username"` + Uuid string `json:"uuid"` + Version string `json:"version"` +} + +func (pf *PeerForm) ToPeer() *model.Peer { + return &model.Peer{ + Cpu: pf.Cpu, + Hostname: pf.Hostname, + Id: pf.Id, + Memory: pf.Memory, + Os: pf.Os, + Username: pf.Username, + Uuid: pf.Uuid, + Version: pf.Version, + } +} diff --git a/http/request/api/user.go b/http/request/api/user.go new file mode 100644 index 0000000..13a0eb4 --- /dev/null +++ b/http/request/api/user.go @@ -0,0 +1,41 @@ +package api + +/* +* + + message LoginRequest { + string username = 1; + bytes password = 2; + string my_id = 4; + string my_name = 5; + OptionMessage option = 6; + oneof union { + FileTransfer file_transfer = 7; + PortForward port_forward = 8; + } + bool video_ack_required = 9; + uint64 session_id = 10; + string version = 11; + OSLogin os_login = 12; + string my_platform = 13; + bytes hwid = 14; + } +*/ +type LoginForm struct { + Username string `json:"username" validate:"required,gte=4,lte=10" label:"用户名"` + Password string `json:"password,omitempty" validate:"gte=4,lte=20" label:"密码"` +} + +type UserListQuery struct { + Page uint `json:"page" form:"page" validate:"required" label:"页码"` + PageSize uint `json:"page_size" form:"page_size" validate:"required" label:"每页数量"` + Status int `json:"status" form:"status" label:"状态"` + Accessible string `json:"accessible" form:"accessible"` +} + +type PeerListQuery struct { + Page uint `json:"page" form:"page" validate:"required" label:"页码"` + PageSize uint `json:"page_size" form:"page_size" validate:"required" label:"每页数量"` + Status int `json:"status" form:"status" label:"状态"` + Accessible string `json:"accessible" form:"accessible"` +} diff --git a/http/response/admin/user.go b/http/response/admin/user.go new file mode 100644 index 0000000..27e92a9 --- /dev/null +++ b/http/response/admin/user.go @@ -0,0 +1,13 @@ +package admin + +type LoginPayload struct { + Username string `json:"username"` + Token string `json:"token"` + RouteNames []string `json:"route_names"` + Nickname string `json:"nickname"` +} + +var UserRouteNames = []string{ + "MyTagList", "MyAddressBookList", +} +var AdminRouteNames = []string{"*"} diff --git a/http/response/api/ab.go b/http/response/api/ab.go new file mode 100644 index 0000000..47134e0 --- /dev/null +++ b/http/response/api/ab.go @@ -0,0 +1,9 @@ +package api + +import "Gwen/model" + +type AbList struct { + Peers []*model.AddressBook `json:"peers,omitempty"` + Tags []string `json:"tags,omitempty"` + TagColors string `json:"tag_colors,omitempty"` +} diff --git a/http/response/api/peer.go b/http/response/api/peer.go new file mode 100644 index 0000000..3ad06a4 --- /dev/null +++ b/http/response/api/peer.go @@ -0,0 +1,74 @@ +package api + +import "Gwen/model" + +/* +GroupPeerPayload +https://github.com/rustdesk/rustdesk/blob/master/flutter/lib/common/hbbs/hbbs.dart#L64 + + String id = ''; + Map info = {}; + int? status; + String user = ''; + String user_name = ''; + String note = ''; + + PeerPayload.fromJson(Map json) + : id = json['id'] ?? '', + info = (json['info'] is Map) ? json['info'] : {}, + status = json['status'], + user = json['user'] ?? '', + user_name = json['user_name'] ?? '', + note = json['note'] ?? ''; + + static Peer toPeer(GroupPeerPayload p) { + return Peer.fromJson({ + "id": p.id, + 'loginName': p.user_name, + "username": p.info['username'] ?? '', + "platform": _platform(p.info['os']), + "hostname": p.info['device_name'], + }); + } +*/ +type GroupPeerPayload struct { + Id string `json:"id"` + Info *PeerPayloadInfo `json:"info"` + Status int `json:"status"` + User string `json:"user"` + UserName string `json:"user_name"` + Note string `json:"note"` +} +type PeerPayloadInfo struct { + DeviceName string `json:"device_name"` + Os string `json:"os"` + Username string `json:"username"` +} + +func (gpp *GroupPeerPayload) FromAddressBook(a *model.AddressBook, username string) { + gpp.Id = a.Id + os := a.Platform + if a.Platform == "Mac OS" { + os = "MacOS" + } + gpp.Info = &PeerPayloadInfo{ + DeviceName: a.Hostname, + Os: os, + Username: a.Username, + } + gpp.UserName = username +} + +//func (gpp *GroupPeerPayload) FromPeer(p *model.Peer) { +// gpp.Id = p.Id +// gpp.Info = &PeerPayloadInfo{ +// DeviceName: p.Hostname, +// Os: p.Os, +// Username: p.Username, +// } +// gpp.Note = "" +// if p.User.Id != 0 { +// //gpp.User = p.User.Username +// gpp.UserName = p.User.Username +// } +//} diff --git a/http/response/api/user.go b/http/response/api/user.go new file mode 100644 index 0000000..dcea9b7 --- /dev/null +++ b/http/response/api/user.go @@ -0,0 +1,55 @@ +package api + +import "Gwen/model" + +/* + pub enum UserStatus { + Disabled = 0, + Normal = 1, + Unverified = -1, + } +*/ + +/* +UserPayload +String name = ”; +String email = ”; +String note = ”; +UserStatus status; +bool isAdmin = false; +*/ +type UserPayload struct { + Name string `json:"name"` + Email string `json:"email"` + Note string `json:"note"` + IsAdmin *bool `json:"is_admin"` + Status int `json:"status"` +} + +func (up *UserPayload) FromUser(user *model.User) *UserPayload { + up.Name = user.Username + up.IsAdmin = user.IsAdmin + up.Status = int(user.Status) + return up +} + +/* + class HttpType { + static const kAuthReqTypeAccount = "account"; + static const kAuthReqTypeMobile = "mobile"; + static const kAuthReqTypeSMSCode = "sms_code"; + static const kAuthReqTypeEmailCode = "email_code"; + static const kAuthReqTypeTfaCode = "tfa_code"; + + static const kAuthResTypeToken = "access_token"; + static const kAuthResTypeEmailCheck = "email_check"; + static const kAuthResTypeTfaCheck = "tfa_check"; + } +*/ +type LoginRes struct { + Type string `json:"type"` + AccessToken string `json:"access_token"` + User UserPayload `json:"user"` + Secret string `json:"secret"` + TfaType string `json:"tfa_type"` +} diff --git a/http/response/api/webClient.go b/http/response/api/webClient.go new file mode 100644 index 0000000..9245e91 --- /dev/null +++ b/http/response/api/webClient.go @@ -0,0 +1,55 @@ +package api + +import ( + "Gwen/model" + "time" +) + +// type T struct { +// Field1 struct { +// ViewStyle string `json:"view-style"` +// Tm int64 `json:"tm"` +// Info struct { +// Username string `json:"username"` +// Hostname string `json:"hostname"` +// Platform string `json:"platform"` +// Displays []struct { +// X int `json:"x"` +// Y int `json:"y"` +// Width int `json:"width"` +// Height int `json:"height"` +// Name string `json:"name"` +// Online bool `json:"online"` +// } `json:"displays"` +// CurrentDisplay int `json:"current_display"` +// SasEnabled bool `json:"sas_enabled"` +// Version string `json:"version"` +// ConnId int `json:"conn_id"` +// Features struct { +// PrivacyMode bool `json:"privacy_mode"` +// } `json:"features"` +// } `json:"info"` +// } `json:"1799928825"` +// } + +type WebClientPeerPayload struct { + ViewStyle string `json:"view-style"` + Tm int64 `json:"tm"` + Info WebClientPeerInfoPayload `json:"info"` +} + +type WebClientPeerInfoPayload struct { + Username string `json:"username"` + Hostname string `json:"hostname"` + Platform string `json:"platform"` +} + +func (wcpp *WebClientPeerPayload) FromAddressBook(a *model.AddressBook) { + wcpp.ViewStyle = "shrink" + wcpp.Tm = time.Now().UnixNano() + wcpp.Info = WebClientPeerInfoPayload{ + Username: a.Username, + Hostname: a.Hostname, + Platform: a.Platform, + } +} diff --git a/http/response/response.go b/http/response/response.go new file mode 100644 index 0000000..9f51c26 --- /dev/null +++ b/http/response/response.go @@ -0,0 +1,53 @@ +package response + +import ( + "github.com/gin-gonic/gin" + "net/http" +) + +type Response struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data"` +} +type PageData struct { + Page int `json:"page"` + Total int `json:"total"` + List interface{} `json:"list"` +} + +type DataResponse struct { + Total uint `json:"total"` + Data interface{} `json:"data"` +} + +type ErrorResponse struct { + Error string `json:"error"` +} + +func SendResponse(c *gin.Context, code int, message string, data interface{}) { + c.JSON(http.StatusOK, Response{ + code, message, data, + }) +} + +func Success(c *gin.Context, data interface{}) { + SendResponse(c, 0, "success", data) +} + +func Fail(c *gin.Context, code int, message string) { + SendResponse(c, code, message, nil) +} + +func Error(c *gin.Context, message string) { + c.JSON(http.StatusBadRequest, ErrorResponse{ + Error: message, + }) +} + +type ServerConfigResponse struct { + IdServer string `json:"id_server"` + Key string `json:"key"` + RelayServer string `json:"relay_server"` + ApiServer string `json:"api_server"` +} diff --git a/http/router/admin.go b/http/router/admin.go new file mode 100644 index 0000000..df93210 --- /dev/null +++ b/http/router/admin.go @@ -0,0 +1,118 @@ +package router + +import ( + _ "Gwen/docs/admin" + "Gwen/http/controller/admin" + "Gwen/http/middleware" + "github.com/gin-gonic/gin" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" +) + +func Init(g *gin.Engine) { + + //swagger + //g.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + g.GET("/admin/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.InstanceName("admin"))) + + adg := g.Group("/api/admin") + LoginBind(adg) + + adg.Use(middleware.AdminAuth()) + //FileBind(adg) + UserBind(adg) + GroupBind(adg) + TagBind(adg) + AddressBookBind(adg) + PeerBind(adg) + + rs := &admin.Rustdesk{} + adg.GET("/server-config", rs.ServerConfig) + + //访问静态文件 + //g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/upload")) +} +func LoginBind(rg *gin.RouterGroup) { + cont := &admin.Login{} + rg.POST("/login", cont.Login) + rg.POST("/logout", cont.Logout) +} + +func UserBind(rg *gin.RouterGroup) { + aR := rg.Group("/user") + { + cont := &admin.User{} + aR.GET("/current", cont.Current) + aR.POST("/changeCurPwd", cont.ChangeCurPwd) + } + aRP := rg.Group("/user").Use(middleware.AdminPrivilege()) + { + cont := &admin.User{} + aRP.GET("/list", cont.List) + aRP.GET("/detail/:id", cont.Detail) + aRP.POST("/create", cont.Create) + aRP.POST("/update", cont.Update) + aRP.POST("/delete", cont.Delete) + aRP.POST("/changePwd", cont.UpdatePassword) + } +} + +func GroupBind(rg *gin.RouterGroup) { + aR := rg.Group("/group").Use(middleware.AdminPrivilege()) + { + cont := &admin.Group{} + aR.GET("/list", cont.List) + aR.GET("/detail/:id", cont.Detail) + aR.POST("/create", cont.Create) + aR.POST("/update", cont.Update) + aR.POST("/delete", cont.Delete) + } +} + +func TagBind(rg *gin.RouterGroup) { + aR := rg.Group("/tag") + { + cont := &admin.Tag{} + aR.GET("/list", cont.List) + aR.GET("/detail/:id", cont.Detail) + aR.POST("/create", cont.Create) + aR.POST("/update", cont.Update) + aR.POST("/delete", cont.Delete) + } +} + +func AddressBookBind(rg *gin.RouterGroup) { + aR := rg.Group("/address_book") + { + cont := &admin.AddressBook{} + aR.GET("/list", cont.List) + aR.GET("/detail/:id", cont.Detail) + aR.POST("/create", cont.Create) + aR.POST("/update", cont.Update) + aR.POST("/delete", cont.Delete) + } +} +func PeerBind(rg *gin.RouterGroup) { + aR := rg.Group("/peer") + { + cont := &admin.Peer{} + aR.GET("/list", cont.List) + aR.GET("/detail/:id", cont.Detail) + aR.POST("/create", cont.Create) + aR.POST("/update", cont.Update) + aR.POST("/delete", cont.Delete) + } +} + +/* +func FileBind(rg *gin.RouterGroup) { + aR := rg.Group("/file") + { + cont := &admin.File{} + aR.POST("/notify", cont.Notify) + aR.OPTIONS("/oss_token", nil) + aR.OPTIONS("/upload", nil) + aR.GET("/oss_token", cont.OssToken) + aR.POST("/upload", cont.Upload) + } +}*/ diff --git a/http/router/api.go b/http/router/api.go new file mode 100644 index 0000000..aa4695f --- /dev/null +++ b/http/router/api.go @@ -0,0 +1,73 @@ +package router + +import ( + _ "Gwen/docs/api" + "Gwen/global" + "Gwen/http/controller/api" + "Gwen/http/middleware" + "github.com/gin-gonic/gin" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" + "net/http" +) + +func ApiInit(g *gin.Engine) { + + //g.Use(middleware.Cors()) + //swagger + g.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.InstanceName("api"))) + + frg := g.Group("/api") + + frg.Use(middleware.Cors()) + frg.OPTIONS("/*any", nil) + + i := &api.Index{} + frg.GET("/", i.Index) + + frg.POST("/heartbeat", i.Heartbeat) + + { + l := &api.Login{} + // 如果返回oidc则可以通过oidc登录 + frg.GET("/login-options", l.LoginOptions) + frg.POST("/login", l.Login) + + } + { + pe := &api.Peer{} + //提交系统信息 + frg.POST("/sysinfo", pe.SysInfo) + } + frg.Use(middleware.RustAuth()) + { + w := &api.WebClient{} + frg.POST("/server-config", w.ServerConfig) + } + + { + u := &api.User{} + frg.GET("/user/info", u.Info) + frg.POST("/currentUser", u.Info) + } + { + l := &api.Login{} + frg.POST("/logout", l.Logout) + } + { + gr := &api.Group{} + frg.GET("/users", gr.Users) + frg.GET("/peers", gr.Peers) + } + + { + ab := &api.Ab{} + //获取地址 + frg.GET("/ab", ab.Ab) + //更新地址 + frg.POST("/ab", ab.UpAb) + } + + //访问静态文件 + g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/public/upload")) +} diff --git a/http/router/router.go b/http/router/router.go new file mode 100644 index 0000000..2d108cb --- /dev/null +++ b/http/router/router.go @@ -0,0 +1,15 @@ +package router + +import ( + "Gwen/global" + "Gwen/http/controller/web" + "github.com/gin-gonic/gin" + "net/http" +) + +func WebInit(g *gin.Engine) { + i := &web.Index{} + g.GET("/webclient-config/index.js", i.ConfigJs) + g.StaticFS("/webclient", http.Dir(global.Config.Gin.ResourcesPath+"/web")) + g.StaticFS("/_admin", http.Dir(global.Config.Gin.ResourcesPath+"/admin")) +} diff --git a/http/run.go b/http/run.go new file mode 100644 index 0000000..a4e2e13 --- /dev/null +++ b/http/run.go @@ -0,0 +1,12 @@ +//go:build !windows + +package http + +import ( + "github.com/fvbock/endless" + "github.com/gin-gonic/gin" +) + +func Run(g *gin.Engine, addr string) { + endless.ListenAndServe(addr, g) +} diff --git a/http/run_win.go b/http/run_win.go new file mode 100644 index 0000000..c6339b5 --- /dev/null +++ b/http/run_win.go @@ -0,0 +1,11 @@ +//go:build windows + +package http + +import ( + "github.com/gin-gonic/gin" +) + +func Run(g *gin.Engine, addr string) { + g.Run(addr) +} diff --git a/lib/cache/cache.go b/lib/cache/cache.go new file mode 100644 index 0000000..dbe2c56 --- /dev/null +++ b/lib/cache/cache.go @@ -0,0 +1,71 @@ +package cache + +import ( + "encoding/json" +) + +type Handler interface { + Get(key string, value interface{}) error + Set(key string, value interface{}, exp int) error + Gc() error +} + +// MaxTimeOut 最大超时时间 + +const ( + TypeMem = "memory" + TypeRedis = "redis" + TypeFile = "file" + MaxTimeOut = 365 * 24 * 3600 +) + +func New(typ string) Handler { + var cache Handler + switch typ { + case TypeFile: + cache = NewFileCache() + case TypeRedis: + cache = new(RedisCache) + case TypeMem: // memory + cache = NewMemoryCache(0) + default: + cache = NewMemoryCache(0) + } + return cache +} + +func EncodeValue(value interface{}) (string, error) { + /*if v, ok := value.(string); ok { + return v, nil + } + if v, ok := value.([]byte); ok { + return string(v), nil + }*/ + b, err := json.Marshal(value) + if err != nil { + return "", err + } + return string(b), nil +} + +func DecodeValue(value string, rtv interface{}) error { + //判断rtv的类型是否是string,如果是string,直接赋值并返回 + /*switch rtv.(type) { + case *string: + *(rtv.(*string)) = value + return nil + case *[]byte: + *(rtv.(*[]byte)) = []byte(value) + return nil + //struct + case *interface{}: + err := json.Unmarshal(([]byte)(value), rtv) + return err + default: + err := json.Unmarshal(([]byte)(value), rtv) + return err + } + */ + err := json.Unmarshal(([]byte)(value), rtv) + return err +} diff --git a/lib/cache/cache_test.go b/lib/cache/cache_test.go new file mode 100644 index 0000000..34ca2ab --- /dev/null +++ b/lib/cache/cache_test.go @@ -0,0 +1,92 @@ +package cache + +import ( + "fmt" + "github.com/go-redis/redis/v8" + "reflect" + "testing" +) + +func TestSimpleCache(t *testing.T) { + + type st struct { + A string + B string + } + + items := map[string]interface{}{} + items["a"] = "b" + items["b"] = "c" + + ab := &st{ + A: "a", + B: "b", + } + items["ab"] = *ab + + a := items["a"] + fmt.Println(a) + + b := items["b"] + fmt.Println(b) + + ab.A = "aa" + ab2 := st{} + ab2 = (items["ab"]).(st) + fmt.Println(ab2, reflect.TypeOf(ab2)) + +} + +func TestFileCacheSet(t *testing.T) { + fc := New("file") + err := fc.Set("123", "ddd", 0) + if err != nil { + fmt.Println(err.Error()) + t.Fatalf("写入失败") + } +} + +func TestFileCacheGet(t *testing.T) { + fc := New("file") + err := fc.Set("123", "45156", 300) + if err != nil { + t.Fatalf("写入失败") + } + res := "" + err = fc.Get("123", &res) + if err != nil { + t.Fatalf("读取失败") + } + fmt.Println("res", res) +} + +func TestRedisCacheSet(t *testing.T) { + rc := NewRedis(&redis.Options{ + Addr: "192.168.1.168:6379", + Password: "", // no password set + DB: 0, // use default DB + }) + err := rc.Set("123", "ddd", 0) + if err != nil { + fmt.Println(err.Error()) + t.Fatalf("写入失败") + } +} + +func TestRedisCacheGet(t *testing.T) { + rc := NewRedis(&redis.Options{ + Addr: "192.168.1.168:6379", + Password: "", // no password set + DB: 0, // use default DB + }) + err := rc.Set("123", "451156", 300) + if err != nil { + t.Fatalf("写入失败") + } + res := "" + err = rc.Get("123", &res) + if err != nil { + t.Fatalf("读取失败") + } + fmt.Println("res", res) +} diff --git a/lib/cache/file.go b/lib/cache/file.go new file mode 100644 index 0000000..5fb57fd --- /dev/null +++ b/lib/cache/file.go @@ -0,0 +1,103 @@ +package cache + +import ( + "crypto/md5" + "fmt" + "os" + "sync" + "time" +) + +type FileCache struct { + mu sync.Mutex + locks map[string]*sync.Mutex + Dir string +} + +func (fc *FileCache) getLock(key string) *sync.Mutex { + fc.mu.Lock() + defer fc.mu.Unlock() + if fc.locks == nil { + fc.locks = make(map[string]*sync.Mutex) + } + if _, ok := fc.locks[key]; !ok { + fc.locks[key] = new(sync.Mutex) + } + return fc.locks[key] +} + +func (c *FileCache) Get(key string, value interface{}) error { + data, _ := c.getValue(key) + err := DecodeValue(data, value) + return err +} + +// 获取值,如果文件不存在或者过期,返回空,过滤掉错误 +func (c *FileCache) getValue(key string) (string, error) { + f := c.fileName(key) + fileInfo, err := os.Stat(f) + if err != nil { + //文件不存在 + return "", nil + } + difT := time.Now().Sub(fileInfo.ModTime()) + if difT >= 0 { + os.Remove(f) + return "", nil + } + data, err := os.ReadFile(f) + if err != nil { + return "", nil + } + return string(data), nil +} + +// 保存值 +func (c *FileCache) saveValue(key string, value string, exp int) error { + f := c.fileName(key) + lock := c.getLock(f) + lock.Lock() + defer lock.Unlock() + + err := os.WriteFile(f, ([]byte)(value), 0644) + if err != nil { + return err + } + if exp <= 0 { + exp = MaxTimeOut + } + expFromNow := time.Now().Add(time.Duration(exp) * time.Second) + err = os.Chtimes(f, expFromNow, expFromNow) + return err +} + +func (c *FileCache) Set(key string, value interface{}, exp int) error { + str, err := EncodeValue(value) + if err != nil { + return err + } + + err = c.saveValue(key, str, exp) + return err +} + +func (c *FileCache) SetDir(path string) { + c.Dir = path +} + +func (c *FileCache) fileName(key string) string { + f := c.Dir + string(os.PathSeparator) + fmt.Sprintf("%x", md5.Sum([]byte(key))) + return f +} + +func (c *FileCache) Gc() error { + //检查文件过期时间,并删除 + return nil +} + +func NewFileCache() *FileCache { + return &FileCache{ + locks: make(map[string]*sync.Mutex), + Dir: os.TempDir(), + } +} diff --git a/lib/cache/file_test.go b/lib/cache/file_test.go new file mode 100644 index 0000000..b8446f5 --- /dev/null +++ b/lib/cache/file_test.go @@ -0,0 +1,94 @@ +package cache + +import ( + "fmt" + "reflect" + "testing" +) + +func TestFileSet(t *testing.T) { + fc := NewFileCache() + err := fc.Set("123", "ddd", 0) + if err != nil { + fmt.Println(err.Error()) + t.Fatalf("写入失败") + } +} + +func TestFileGet(t *testing.T) { + fc := NewFileCache() + res := "" + err := fc.Get("123", &res) + if err != nil { + fmt.Println(err.Error()) + t.Fatalf("读取失败") + } + fmt.Println("res", res) +} +func TestFileSetGet(t *testing.T) { + fc := NewFileCache() + err := fc.Set("key1", "ddd", 0) + res := "" + err = fc.Get("key1", &res) + if err != nil { + fmt.Println(err.Error()) + t.Fatalf("读取失败") + } + fmt.Println("res", res) +} +func TestFileGetJson(t *testing.T) { + fc := NewFileCache() + old := &r{ + A: "a", B: "b", + } + fc.Set("123", old, 0) + res := &r{} + err2 := fc.Get("123", res) + fmt.Println("res", res) + if err2 != nil { + t.Fatalf("读取失败" + err2.Error()) + } +} +func TestFileSetGetJson(t *testing.T) { + fc := NewFileCache() + + old_rr := &rr{AA: "aa", BB: "bb"} + old := &r{ + A: "a", B: "b", + R: old_rr, + } + err := fc.Set("123", old, 300) + if err != nil { + t.Fatalf("写入失败") + } + //old_rr.AA = "aaa" + fmt.Println("old_rr", old) + + res := &r{} + err2 := fc.Get("123", res) + fmt.Println("res", res) + if err2 != nil { + t.Fatalf("读取失败" + err2.Error()) + } + if !reflect.DeepEqual(res, old) { + t.Fatalf("读取错误") + } + +} + +func BenchmarkSet(b *testing.B) { + fc := NewFileCache() + b.ResetTimer() + for i := 0; i < b.N; i++ { + fc.Set("123", "{dsv}", 1000) + } +} + +func BenchmarkGet(b *testing.B) { + fc := NewFileCache() + b.ResetTimer() + v := "" + for i := 0; i < b.N; i++ { + fc.Get("123", &v) + } +} diff --git a/lib/cache/memory.go b/lib/cache/memory.go new file mode 100644 index 0000000..49d8f9d --- /dev/null +++ b/lib/cache/memory.go @@ -0,0 +1,215 @@ +package cache + +import ( + "container/heap" + "container/list" + "errors" + "reflect" + "sync" + "time" +) + +type MemoryCache struct { + data map[string]*CacheItem + ll *list.List // 用于实现LRU + pq PriorityQueue // 用于实现TTL + quit chan struct{} + mu sync.Mutex + maxBytes int64 + usedBytes int64 +} + +type CacheItem struct { + Key string + Value string + Expiration int64 + Index int + ListEle *list.Element +} + +type PriorityQueue []*CacheItem + +func (pq PriorityQueue) Len() int { return len(pq) } + +func (pq PriorityQueue) Less(i, j int) bool { + return pq[i].Expiration < pq[j].Expiration +} + +func (pq PriorityQueue) Swap(i, j int) { + pq[i], pq[j] = pq[j], pq[i] + pq[i].Index = i + pq[j].Index = j +} + +func (pq *PriorityQueue) Push(x interface{}) { + item := x.(*CacheItem) + item.Index = len(*pq) + *pq = append(*pq, item) +} + +func (pq *PriorityQueue) Pop() interface{} { + old := *pq + n := len(old) + item := old[n-1] + old[n-1] = nil // avoid memory leak + item.Index = -1 // for safety + *pq = old[0 : n-1] + return item +} + +func (m *MemoryCache) Get(key string, value interface{}) error { + // 使用反射将存储的值设置到传入的指针变量中 + val := reflect.ValueOf(value) + if val.Kind() != reflect.Ptr { + return errors.New("value must be a pointer") + } + //设为空值 + val.Elem().Set(reflect.Zero(val.Elem().Type())) + + m.mu.Lock() + defer m.mu.Unlock() + + if m.data == nil { + return nil + } + + if item, ok := m.data[key]; ok { + if item.Expiration < time.Now().UnixNano() { + m.deleteItem(item) + return nil + } + //移动到队列尾部 + m.ll.MoveToBack(item.ListEle) + + err := DecodeValue(item.Value, value) + if err != nil { + return err + } + } + return nil +} + +func (m *MemoryCache) Set(key string, value interface{}, exp int) error { + m.mu.Lock() + defer m.mu.Unlock() + + v, err := EncodeValue(value) + if err != nil { + return err + } + //key 所占用的内存 + keyBytes := int64(len(key)) + //value所占用的内存空间大小 + valueBytes := int64(len(v)) + //判断是否超过最大内存限制 + if m.maxBytes != 0 && m.maxBytes < keyBytes+valueBytes { + return errors.New("exceed maxBytes") + } + m.usedBytes += keyBytes + valueBytes + if m.maxBytes != 0 && m.usedBytes > m.maxBytes { + m.RemoveOldest() + } + if exp <= 0 { + exp = MaxTimeOut + } + expiration := time.Now().Add(time.Duration(exp) * time.Second).UnixNano() + item, exists := m.data[key] + if exists { + item.Value = v + item.Expiration = expiration + heap.Fix(&m.pq, item.Index) + m.ll.MoveToBack(item.ListEle) + } else { + ele := m.ll.PushBack(key) + item = &CacheItem{ + Key: key, + Value: v, + Expiration: expiration, + ListEle: ele, + } + m.data[key] = item + heap.Push(&m.pq, item) + } + + return nil +} + +func (m *MemoryCache) RemoveOldest() { + for m.maxBytes != 0 && m.usedBytes > m.maxBytes { + elem := m.ll.Front() + if elem != nil { + key := elem.Value.(string) + item := m.data[key] + m.deleteItem(item) + } + } +} + +// evictExpiredItems removes all expired items from the cache. +func (m *MemoryCache) evictExpiredItems() { + m.mu.Lock() + defer m.mu.Unlock() + now := time.Now().UnixNano() + for m.pq.Len() > 0 { + item := m.pq[0] + if item.Expiration > now { + break + } + m.deleteItem(item) + } +} + +// startEviction starts a goroutine that evicts expired items from the cache. +func (m *MemoryCache) startEviction() { + ticker := time.NewTicker(1 * time.Second) + + go func() { + for { + select { + case <-ticker.C: + m.evictExpiredItems() + case <-m.quit: + ticker.Stop() + return + } + } + }() +} + +// stopEviction 停止定时清理 +func (m *MemoryCache) stopEviction() { + close(m.quit) +} + +// deleteItem removes a key from the cache. +func (m *MemoryCache) deleteItem(item *CacheItem) { + m.ll.Remove(item.ListEle) + m.usedBytes -= int64(len(item.Key)) + int64(len(item.Value)) + heap.Remove(&m.pq, item.Index) + delete(m.data, item.Key) +} + +func (m *MemoryCache) Gc() error { + m.mu.Lock() + defer m.mu.Unlock() + m.data = make(map[string]*CacheItem) + m.ll = list.New() + m.pq = make(PriorityQueue, 0) + heap.Init(&m.pq) + m.usedBytes = 0 + return nil +} + +// NewMemoryCache creates a new MemoryCache.default maxBytes is 0, means no limit. +func NewMemoryCache(maxBytes int64) *MemoryCache { + cache := &MemoryCache{ + data: make(map[string]*CacheItem), + pq: make(PriorityQueue, 0), + quit: make(chan struct{}), + ll: list.New(), + maxBytes: maxBytes, + } + heap.Init(&cache.pq) + cache.startEviction() + return cache +} diff --git a/lib/cache/memory_test.go b/lib/cache/memory_test.go new file mode 100644 index 0000000..89243e7 --- /dev/null +++ b/lib/cache/memory_test.go @@ -0,0 +1,107 @@ +package cache + +import ( + "fmt" + "testing" + "time" +) + +func TestMemorySet(t *testing.T) { + mc := NewMemoryCache(0) + err := mc.Set("123", "44567", 0) + if err != nil { + fmt.Println(err.Error()) + t.Fatalf("写入失败") + } +} + +func TestMemoryGet(t *testing.T) { + mc := NewMemoryCache(0) + mc.Set("123", "44567", 0) + res := "" + err := mc.Get("123", &res) + fmt.Println("res", res) + if err != nil { + t.Fatalf("读取失败 " + err.Error()) + } + if res != "44567" { + t.Fatalf("读取错误") + } + +} + +func TestMemorySetExpGet(t *testing.T) { + mc := NewMemoryCache(0) + //mc.stopEviction() + mc.Set("1", "10", 10) + mc.Set("2", "5", 5) + err := mc.Set("3", "3", 3) + if err != nil { + t.Fatalf("写入失败") + } + + res := "" + err = mc.Get("3", &res) + if err != nil { + t.Fatalf("读取失败" + err.Error()) + } + fmt.Println("res 3", res) + time.Sleep(4 * time.Second) + //res = "" + err = mc.Get("3", &res) + if err != nil { + t.Fatalf("读取失败" + err.Error()) + } + fmt.Println("res 3", res) + err = mc.Get("2", &res) + if err != nil { + t.Fatalf("读取失败" + err.Error()) + } + fmt.Println("res 2", res) + err = mc.Get("1", &res) + if err != nil { + t.Fatalf("读取失败" + err.Error()) + } + fmt.Println("res 1", res) + +} +func TestMemoryLru(t *testing.T) { + mc := NewMemoryCache(18) + mc.Set("1", "1111", 10) + mc.Set("2", "2222", 5) + //读取一次,2就会被放到最后 + mc.Get("1", nil) + err := mc.Set("3", "三", 3) + if err != nil { + //t.Fatalf("写入失败") + } + + res := "" + err = mc.Get("3", &res) + if err != nil { + t.Fatalf("读取失败" + err.Error()) + } + fmt.Println("res3", res) + res = "" + err = mc.Get("2", &res) + if err != nil { + t.Fatalf("读取失败" + err.Error()) + } + fmt.Println("res2", res) + res = "" + err = mc.Get("1", &res) + if err != nil { + t.Fatalf("读取失败" + err.Error()) + } + fmt.Println("res1", res) + +} +func BenchmarkMemorySet(b *testing.B) { + mc := NewMemoryCache(0) + b.ResetTimer() + for i := 0; i < b.N; i++ { + key := fmt.Sprintf("key%d", i) + value := fmt.Sprintf("value%d", i) + mc.Set(key, value, 1000) + } +} diff --git a/lib/cache/redis.go b/lib/cache/redis.go new file mode 100644 index 0000000..5ad957a --- /dev/null +++ b/lib/cache/redis.go @@ -0,0 +1,49 @@ +package cache + +import ( + "context" + "github.com/go-redis/redis/v8" + "time" +) + +var ctx = context.Background() + +type RedisCache struct { + rdb *redis.Client +} + +func RedisCacheInit(conf *redis.Options) *RedisCache { + c := &RedisCache{} + c.rdb = redis.NewClient(conf) + return c +} + +func (c *RedisCache) Get(key string, value interface{}) error { + data, err := c.rdb.Get(ctx, key).Result() + if err != nil { + return err + } + err1 := DecodeValue(data, value) + return err1 +} + +func (c *RedisCache) Set(key string, value interface{}, exp int) error { + str, err := EncodeValue(value) + if err != nil { + return err + } + if exp <= 0 { + exp = MaxTimeOut + } + _, err1 := c.rdb.Set(ctx, key, str, time.Duration(exp)*time.Second).Result() + return err1 +} + +func (c *RedisCache) Gc() error { + return nil +} + +func NewRedis(conf *redis.Options) *RedisCache { + cache := RedisCacheInit(conf) + return cache +} diff --git a/lib/cache/redis_test.go b/lib/cache/redis_test.go new file mode 100644 index 0000000..c8052cd --- /dev/null +++ b/lib/cache/redis_test.go @@ -0,0 +1,94 @@ +package cache + +import ( + "fmt" + "github.com/go-redis/redis/v8" + "reflect" + "testing" +) + +func TestRedisSet(t *testing.T) { + //rc := New("redis") + rc := RedisCacheInit(&redis.Options{ + Addr: "192.168.1.168:6379", + Password: "", // no password set + DB: 0, // use default DB + }) + err := rc.Set("123", "ddd", 0) + if err != nil { + fmt.Println(err.Error()) + t.Fatalf("写入失败") + } +} + +func TestRedisGet(t *testing.T) { + rc := RedisCacheInit(&redis.Options{ + Addr: "192.168.1.168:6379", + Password: "", // no password set + DB: 0, // use default DB + }) + err := rc.Set("123", "451156", 300) + if err != nil { + t.Fatalf("写入失败") + } + res := "" + err = rc.Get("123", &res) + if err != nil { + t.Fatalf("读取失败") + } + fmt.Println("res", res) +} + +func TestRedisGetJson(t *testing.T) { + rc := RedisCacheInit(&redis.Options{ + Addr: "192.168.1.168:6379", + Password: "", // no password set + DB: 0, // use default DB + }) + type r struct { + Aa string `json:"a"` + B string `json:"c"` + } + old := &r{ + Aa: "ab", B: "cdc", + } + err := rc.Set("1233", old, 300) + if err != nil { + t.Fatalf("写入失败") + } + + res := &r{} + err2 := rc.Get("1233", res) + if err2 != nil { + t.Fatalf("读取失败") + } + if !reflect.DeepEqual(res, old) { + t.Fatalf("读取错误") + } + fmt.Println(res, res.Aa) +} + +func BenchmarkRSet(b *testing.B) { + rc := RedisCacheInit(&redis.Options{ + Addr: "192.168.1.168:6379", + Password: "", // no password set + DB: 0, // use default DB + }) + b.ResetTimer() + for i := 0; i < b.N; i++ { + rc.Set("123", "{dsv}", 1000) + } +} + +func BenchmarkRGet(b *testing.B) { + rc := RedisCacheInit(&redis.Options{ + Addr: "192.168.1.168:6379", + Password: "", // no password set + DB: 0, // use default DB + }) + b.ResetTimer() + v := "" + for i := 0; i < b.N; i++ { + rc.Get("123", &v) + } +} diff --git a/lib/cache/simple_cache.go b/lib/cache/simple_cache.go new file mode 100644 index 0000000..f599bd7 --- /dev/null +++ b/lib/cache/simple_cache.go @@ -0,0 +1,65 @@ +package cache + +import ( + "errors" + "reflect" + "sync" +) + +// 此处实现了一个简单的缓存,用于测试 +// SimpleCache is a simple cache implementation +type SimpleCache struct { + data map[string]interface{} + mu sync.Mutex + maxBytes int64 + usedBytes int64 +} + +func (s *SimpleCache) Get(key string, value interface{}) error { + s.mu.Lock() + defer s.mu.Unlock() + + // 使用反射将存储的值设置到传入的指针变量中 + val := reflect.ValueOf(value) + if val.Kind() != reflect.Ptr { + return errors.New("value must be a pointer") + } + v, ok := s.data[key] + if !ok { + //设为空值 + val.Elem().Set(reflect.Zero(val.Elem().Type())) + return nil + } + + vval := reflect.ValueOf(v) + if val.Elem().Type() != vval.Type() { + //设为空值 + val.Elem().Set(reflect.Zero(val.Elem().Type())) + return nil + } + + val.Elem().Set(reflect.ValueOf(v)) + return nil +} + +func (s *SimpleCache) Set(key string, value interface{}, exp int) error { + s.mu.Lock() + defer s.mu.Unlock() + // 检查传入的值是否是指针,如果是则取其值 + val := reflect.ValueOf(value) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + + s.data[key] = val.Interface() + return nil +} +func (s *SimpleCache) Gc() error { + return nil +} + +func NewSimpleCache() *SimpleCache { + return &SimpleCache{ + data: make(map[string]interface{}), + } +} diff --git a/lib/cache/simple_cache_test.go b/lib/cache/simple_cache_test.go new file mode 100644 index 0000000..01c3b4d --- /dev/null +++ b/lib/cache/simple_cache_test.go @@ -0,0 +1,108 @@ +package cache + +import ( + "fmt" + "testing" +) + +func TestSimpleCache_Set(t *testing.T) { + s := NewSimpleCache() + err := s.Set("key", "value", 0) + if err != nil { + t.Fatalf("写入失败") + } + err = s.Set("key", 111, 0) + if err != nil { + t.Fatalf("写入失败") + } +} + +func TestSimpleCache_Get(t *testing.T) { + s := NewSimpleCache() + err := s.Set("key", "value", 0) + value := "" + err = s.Get("key", &value) + fmt.Println("value", value) + if err != nil { + t.Fatalf("读取失败") + } + + err = s.Set("key1", 11, 0) + value1 := 0 + err = s.Get("key1", &value1) + fmt.Println("value1", value1) + if err != nil { + t.Fatalf("读取失败") + } + + err = s.Set("key2", []byte{'a', 'b'}, 0) + value2 := []byte{} + err = s.Get("key2", &value2) + fmt.Println("value2", string(value2)) + if err != nil { + t.Fatalf("读取失败") + } + + err = s.Set("key3", 33.33, 0) + var value3 int + err = s.Get("key3", &value3) + fmt.Println("value3", value3) + if err != nil { + t.Fatalf("读取失败") + } + +} + +type r struct { + A string `json:"a"` + B string `json:"b"` + R *rr `json:"r"` +} +type r2 struct { + A string `json:"a"` + B string `json:"b"` +} +type rr struct { + AA string `json:"aa"` + BB string `json:"bb"` +} + +func TestSimpleCache_GetStruct(t *testing.T) { + s := NewSimpleCache() + + old_rr := &rr{ + AA: "aa", BB: "bb", + } + + old := &r{ + A: "ab", B: "cdc", + R: old_rr, + } + err := s.Set("key", old, 300) + if err != nil { + t.Fatalf("写入失败") + } + + res := &r{} + err2 := s.Get("key", res) + fmt.Println("res", res) + if err2 != nil { + t.Fatalf("读取失败" + err2.Error()) + + } + + //修改原始值,看后面是否会变化 + old.A = "aa" + old_rr.AA = "aaa" + fmt.Println("old", old) + res2 := &r{} + err3 := s.Get("key", res2) + fmt.Println("res2", res2, res2.R.AA, res2.R.BB) + if err3 != nil { + t.Fatalf("读取失败" + err3.Error()) + + } + //if reflect.DeepEqual(res, old) { + // t.Fatalf("读取错误") + //} +} diff --git a/lib/jwt/jwt.go b/lib/jwt/jwt.go new file mode 100644 index 0000000..0fdc587 --- /dev/null +++ b/lib/jwt/jwt.go @@ -0,0 +1,61 @@ +package jwt + +import ( + "crypto/rsa" + "github.com/golang-jwt/jwt/v5" + "os" + "time" +) + +type Jwt struct { + privateKey *rsa.PrivateKey + TokenExpireDuration time.Duration +} + +type UserClaims struct { + UserId uint `json:"user_id"` + jwt.RegisteredClaims +} + +func NewJwt(privateKeyFile string, tokenExpireDuration time.Duration) *Jwt { + privateKeyContent, err := os.ReadFile(privateKeyFile) + if err != nil { + panic(err) + } + privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyContent) + if err != nil { + panic(err) + } + return &Jwt{ + privateKey: privateKey, + TokenExpireDuration: tokenExpireDuration, + } +} + +func (s *Jwt) GenerateToken(userId uint) string { + t := jwt.NewWithClaims(jwt.SigningMethodRS256, + UserClaims{ + UserId: userId, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(s.TokenExpireDuration)), + }, + }) + token, err := t.SignedString(s.privateKey) + if err != nil { + return "" + } + return token +} + +func (s *Jwt) ParseToken(tokenString string) (uint, error) { + token, err := jwt.ParseWithClaims(tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) { + return s.privateKey.Public(), nil + }) + if err != nil { + return 0, err + } + if claims, ok := token.Claims.(*UserClaims); ok && token.Valid { + return claims.UserId, nil + } + return 0, err +} diff --git a/lib/jwt/jwt_test.go b/lib/jwt/jwt_test.go new file mode 100644 index 0000000..b40d80c --- /dev/null +++ b/lib/jwt/jwt_test.go @@ -0,0 +1,80 @@ +package jwt + +import ( + "fmt" + "testing" + "time" +) + +var pk = `-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAnJpq2Sy91iGW3+EuG4V2ke59tITpGINzht0rO8WiRwu11W4p +wakS4K4BbjvmC8YjaxXhKE5LHDw0IXvTdIDN7Fuu4qs9xWXIoK+nC3qWrVBtj/1o +RJrYme1NenTXEgPlN1FOU6/9XQGgvb+1MSNqxknYo7183mHACvsIIuSTMEFhUbUw +XYVQrCtACUILZ9wIDOEzclIY2ZPMTnL1vkvfj629KwGtAvpEyc96Y/HMSH5/VkiG +p6L+k+NSjco9HntAGYTiQkfranvdqxRDUsKS53SbV3QSz1zc0l5OEyZDuxFTL7UC +7v0G/HVqz6mLpMje756PG/WEpwa/lADc/8FJ5QIDAQABAoIBAEsqUt6qevOsa55J +lrfe92pT7kIXCUqazXiN75Jg6eLv2/b1SVWKsWTmIAmo9mHwWE+t0MRnz+VdgCgS +JwxkRnKMDwT87Eky8Xku1h7MWEYXtH7IQqOrLwuyut1r907OT9adT9sbPaDGh0CM +I4vSVA2YpELzUFvszyB2HRGiZINkHfdLsNxUKsHJOdXbv82RItwzmCYcZismnR3J +P8THn06eoBNtlqwdFziuREOzjNnj6J/3glhR5mu4c4+AJoj0hmVaBDfac3GsQsbP +x79QQPrUqH9UZ4szubYHXP0uRi/ARlHQ+GNp6foYIsevC0OtLdau0/ouFlfGkEep +3aIV5oECgYEAyyWrNhw+BhNFXsyPzEQ4/mO5ucup3cE/tAAtLiSckoXjmY8K7PQr +xfKRCkuM1qpcxtYkbTs35aOdK48gL0NVd50QzrWFrQkQkVnpnJ1lYeVgEL1DmalD +B55bwTdShcs0gEoKefZCvmotrmYdSpMGsapqqbZFrysFFzRDyDxnHfcCgYEAxVjA +/dXxCEUjYFVC3i833lI/yiycJrhjIeffc6DqpSReuTU+i8Nh3sLiytaSqPFVASDS +08K3JwVguMTzDgrYkl365lm50WxcBuNgLkSqA90vE/H6gkRZVkuzOb7T+ZdDxf0s +7RH4aqeeOSiOcZ3uC+d53UArJFidETXbgguXkAMCgYA22Ynbx05b15IwYW0mCvmU +fhqkdr/7lvT7RdztC4eW7D2itYOOrPKwtKjCrdluEHuSWDlnoMib4UxLeY6IFFcc +P7VNCqf4K21kwXEZD0pTX1pLyr5Y2+G0SeaeSbCnXVFknhksCvjEbui8oOehvgbd +q5S3E/bGsAfk1wDCLMTuywKBgACHrH0CBhOvm9i2YeeW2N+P+PviAslX1WxR4xe8 +ZuTqpBZ7Ph/B9pFSlKlWyi4J9+B45hgLfdJtAUV9welXvh0mg3X657TYRab/FVMK +fCpmfangDHwtEtBYg7K0AH27GkN92pEIa1JeAN7GbRuBARKnHHyrn3IJiuJw8pX2 +0gFhAoGBAIquI9sAB2dKEOMW+iQJkLH8Hh8/EWyslow+QJiyIsRe1l9jtkOxC5D3 +Hj4yO4j5LOWDMTgDcLsZTxbGiTzkNc/HghrNIevDAQdgjJQNl84zDjyyCA4r/MA7 +bYJTtYj8q6J0EDbRdT9b6hMclyzjNXdx2loJxR0R8WUeL1lDEPq8 +-----END RSA PRIVATE KEY-----` + +// 测试token生成 +func TestGenerateToken(t *testing.T) { + jwtService := NewJwt(pk, time.Second*1000) + token := jwtService.GenerateToken(1) + if token == "" { + t.Fatal("token生成失败") + } + fmt.Println(pk, token) +} + +// 测试token解析 +func TestParseToken(t *testing.T) { + jwtService := NewJwt(pk, time.Second*1000) + token := jwtService.GenerateToken(999) + if token == "" { + t.Fatal("token生成失败") + } + uid, err := jwtService.ParseToken(token) + if err != nil { + + t.Fatal("token解析失败", err) + } + if uid != 999 { + t.Fatal("token解析失败") + } +} + +func BenchmarkJwtService_GenerateToken(b *testing.B) { + jwtService := NewJwt(pk, time.Second*1000) + b.ResetTimer() + for i := 0; i < b.N; i++ { + jwtService.GenerateToken(999) + } +} + +func BenchmarkJwtService_ParseToken(b *testing.B) { + jwtService := NewJwt(pk, time.Second*1000) + token := jwtService.GenerateToken(999) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = jwtService.ParseToken(token) + } + +} diff --git a/lib/lock/local.go b/lib/lock/local.go new file mode 100644 index 0000000..a6c492b --- /dev/null +++ b/lib/lock/local.go @@ -0,0 +1,32 @@ +package lock + +import ( + "sync" +) + +type Local struct { + Locks *sync.Map +} + +func (l *Local) Lock(key string) { + lock := l.GetLock(key) + lock.Lock() +} + +func (l *Local) UnLock(key string) { + lock, ok := l.Locks.Load(key) + if ok { + lock.(*sync.Mutex).Unlock() + } +} + +func (l *Local) GetLock(key string) *sync.Mutex { + lock, _ := l.Locks.LoadOrStore(key, &sync.Mutex{}) + return lock.(*sync.Mutex) +} + +func NewLocal() *Local { + return &Local{ + Locks: &sync.Map{}, + } +} diff --git a/lib/lock/local_test.go b/lib/lock/local_test.go new file mode 100644 index 0000000..e2ee037 --- /dev/null +++ b/lib/lock/local_test.go @@ -0,0 +1,100 @@ +package lock + +import ( + "fmt" + "sync" + "testing" +) + +func TestLocal_GetLock(t *testing.T) { + l := NewLocal() + wg := sync.WaitGroup{} + wg.Add(3) + var l1 *sync.Mutex + var l2 *sync.Mutex + var l3 *sync.Mutex + i := 0 + go func() { + l1 = l.GetLock("key") + fmt.Println("l1", l1, i) + l1.Lock() + fmt.Println("l1", i) + i++ + l1.Unlock() + wg.Done() + }() + go func() { + l2 = l.GetLock("key") + fmt.Println("l2", l2, i) + l2.Lock() + fmt.Println("l2", i) + i++ + l2.Unlock() + wg.Done() + }() + go func() { + l3 = l.GetLock("key") + fmt.Println("l3", l3, i) + l3.Lock() + fmt.Println("l3", i) + i++ + l3.Unlock() + wg.Done() + }() + wg.Wait() + + fmt.Println(l1, l2, l3) + fmt.Println(l1 == l2, l2 == l3) + fmt.Println(&sync.Mutex{} == &sync.Mutex{}) +} + +func TestLocal_Lock(t *testing.T) { + l := NewLocal() + wg := sync.WaitGroup{} + wg.Add(3) + i := 0 + go func() { + l.Lock("key") + fmt.Println("l1", i) + i++ + l.UnLock("key") + wg.Done() + }() + go func() { + l.Lock("key") + fmt.Println("l2", i) + i++ + l.UnLock("key") + wg.Done() + }() + go func() { + l.Lock("key") + fmt.Println("l3", i) + i++ + l.UnLock("key") + wg.Done() + }() + wg.Wait() + +} +func TestSyncMap(t *testing.T) { + m := sync.Map{} + wg := sync.WaitGroup{} + wg.Add(3) + go func() { + v, ok := m.LoadOrStore("key", 1) + fmt.Println(1, v, ok) + wg.Done() + }() + go func() { + v, ok := m.LoadOrStore("key", 2) + fmt.Println(2, v, ok) + wg.Done() + }() + go func() { + v, ok := m.LoadOrStore("key", 3) + fmt.Println(3, v, ok) + wg.Done() + }() + wg.Wait() +} diff --git a/lib/lock/lock.go b/lib/lock/lock.go new file mode 100644 index 0000000..ca27461 --- /dev/null +++ b/lib/lock/lock.go @@ -0,0 +1,9 @@ +package lock + +import "sync" + +type Locker interface { + GetLock(key string) *sync.Mutex + Lock(key string) + UnLock(key string) +} diff --git a/lib/logger/logger.go b/lib/logger/logger.go new file mode 100644 index 0000000..bc6d39c --- /dev/null +++ b/lib/logger/logger.go @@ -0,0 +1,54 @@ +package logger + +import ( + nested "github.com/antonfisher/nested-logrus-formatter" + log "github.com/sirupsen/logrus" + "io" + "os" +) + +const ( + DebugMode = "debug" + ReleaseMode = "release" +) + +type Config struct { + Path string + Level string + ReportCaller bool +} + +func New(c *Config) *log.Logger { + log.SetFormatter(&nested.Formatter{ + // HideKeys: true, + TimestampFormat: "2006-01-02 15:04:05", + NoColors: true, + NoFieldsColors: true, + //FieldsOrder: []string{"name", "age"}, + }) + + // 日志文件 + f := c.Path + var write io.Writer + if f != "" { + fwriter, err := os.OpenFile(f, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) + if err != nil { + panic("open log file fail!") + } + write = io.MultiWriter(fwriter, os.Stdout) + } else { + write = os.Stdout + } + + log.SetOutput(write) + + log.SetReportCaller(c.ReportCaller) + + level, err2 := log.ParseLevel(c.Level) + if err2 != nil { + level = log.DebugLevel + } + log.SetLevel(level) + + return log.StandardLogger() +} diff --git a/lib/orm/mysql.go b/lib/orm/mysql.go new file mode 100644 index 0000000..8fc9ed8 --- /dev/null +++ b/lib/orm/mysql.go @@ -0,0 +1,40 @@ +package orm + +import ( + "fmt" + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +type MysqlConfig struct { + Dns string + MaxIdleConns int + MaxOpenConns int +} + +func NewMysql(mysqlConf *MysqlConfig) *gorm.DB { + db, err := gorm.Open(mysql.New(mysql.Config{ + DSN: mysqlConf.Dns, // DSN data source name + DefaultStringSize: 256, // string 类型字段的默认长度 + //DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持 + //DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引 + //DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列 + //SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置 + }), &gorm.Config{ + DisableForeignKeyConstraintWhenMigrating: true, + }) + if err != nil { + fmt.Println(err) + } + sqlDB, err2 := db.DB() + if err2 != nil { + fmt.Println(err2) + } + // SetMaxIdleConns 设置空闲连接池中连接的最大数量 + sqlDB.SetMaxIdleConns(mysqlConf.MaxIdleConns) + + // SetMaxOpenConns 设置打开数据库连接的最大数量。 + sqlDB.SetMaxOpenConns(mysqlConf.MaxOpenConns) + + return db +} diff --git a/lib/orm/sqlite.go b/lib/orm/sqlite.go new file mode 100644 index 0000000..1186da6 --- /dev/null +++ b/lib/orm/sqlite.go @@ -0,0 +1,30 @@ +package orm + +import ( + "fmt" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +type SqliteConfig struct { + MaxIdleConns int + MaxOpenConns int +} + +func NewSqlite(sqliteConf *SqliteConfig) *gorm.DB { + db, err := gorm.Open(sqlite.Open("./data/rustdeskapi.db"), &gorm.Config{}) + if err != nil { + fmt.Println(err) + } + sqlDB, err2 := db.DB() + if err2 != nil { + fmt.Println(err2) + } + // SetMaxIdleConns 设置空闲连接池中连接的最大数量 + sqlDB.SetMaxIdleConns(sqliteConf.MaxIdleConns) + + // SetMaxOpenConns 设置打开数据库连接的最大数量。 + sqlDB.SetMaxOpenConns(sqliteConf.MaxOpenConns) + + return db +} diff --git a/lib/upload/local.go b/lib/upload/local.go new file mode 100644 index 0000000..a4326bc --- /dev/null +++ b/lib/upload/local.go @@ -0,0 +1,4 @@ +package upload + +type Local struct { +} diff --git a/lib/upload/oss.go b/lib/upload/oss.go new file mode 100644 index 0000000..7bada1d --- /dev/null +++ b/lib/upload/oss.go @@ -0,0 +1,475 @@ +package upload + +import ( + "bytes" + "crypto" + "crypto/hmac" + "crypto/md5" + "crypto/rsa" + "crypto/sha1" + "crypto/x509" + "encoding/base64" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "hash" + "io" + "io/ioutil" + "net/http" + "strconv" + "time" +) + +type Oss struct { + AccessKeyId string + AccessKeySecret string + Host string + CallbackUrl string + ExpireTime int64 + MaxByte int64 +} + +const ( + base64Table = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_" +) + +var coder = base64.NewEncoding(base64Table) + +func base64Encode(src []byte) []byte { + return []byte(coder.EncodeToString(src)) +} + +func get_gmt_iso8601(expire_end int64) string { + var tokenExpire = time.Unix(expire_end, 0).UTC().Format("2006-01-02T15:04:05Z") + return tokenExpire +} + +type ConfigStruct struct { + Expiration string `json:"expiration"` + Conditions [][]interface{} `json:"conditions"` +} + +type PolicyToken struct { + AccessKeyId string `json:"accessid"` + Host string `json:"host"` + Expire int64 `json:"expire"` + Signature string `json:"signature"` + Policy string `json:"policy"` + Directory string `json:"dir"` + Callback string `json:"callback"` +} + +type CallbackParam struct { + CallbackUrl string `json:"callbackUrl"` + CallbackBody string `json:"callbackBody"` + CallbackBodyType string `json:"callbackBodyType"` +} + +type CallbackBaseForm struct { + Bucket string `json:"bucket" form:"bucket"` + Etag string `json:"etag" form:"etag"` + Filename string `json:"filename" form:"filename"` + Size string `json:"size" form:"size"` + MimeType string `json:"mime_type" form:"mime_type"` + Height string `json:"height" form:"height"` + Width string `json:"width" form:"width"` + Format string `json:"format" form:"format"` + OriginFilename string `json:"origin_filename" form:"origin_filename"` +} + +func (oc *Oss) GetPolicyToken(uploadDir string) string { + now := time.Now().Unix() + expire_end := now + oc.ExpireTime + var tokenExpire = get_gmt_iso8601(expire_end) + + //create post policy json + var config ConfigStruct + config.Expiration = tokenExpire + var condition = []interface{}{"starts-with", "$key", uploadDir} + var condition_limit = []interface{}{"content-length-range", 0, oc.MaxByte} + config.Conditions = append(config.Conditions, condition, condition_limit) + + //calucate signature + result, err := json.Marshal(config) + debyte := base64.StdEncoding.EncodeToString(result) + h := hmac.New(func() hash.Hash { + return sha1.New() + }, []byte(oc.AccessKeySecret)) + io.WriteString(h, debyte) + signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil)) + + var callbackParam CallbackParam + callbackParam.CallbackUrl = oc.CallbackUrl + + callbackParam.CallbackBody = + "bucket=${bucket}&" + + "etag=${etag}&" + + "filename=${object}&" + + "size=${size}&" + + "mime_type=${mimeType}&" + + "height=${imageInfo.height}&" + + "width=${imageInfo.width}&" + + "format=${imageInfo.format}&" + + "origin_filename=${x:origin_filename}" + callbackParam.CallbackBodyType = "application/x-www-form-urlencoded" + callback_str, err := json.Marshal(callbackParam) + if err != nil { + fmt.Println("callback json err:", err) + } + callbackBase64 := base64.StdEncoding.EncodeToString(callback_str) + + var policyToken PolicyToken + policyToken.AccessKeyId = oc.AccessKeyId + policyToken.Host = oc.Host + policyToken.Expire = expire_end + policyToken.Signature = string(signedStr) + policyToken.Directory = uploadDir + policyToken.Policy = string(debyte) + policyToken.Callback = string(callbackBase64) + response, err := json.Marshal(policyToken) + if err != nil { + fmt.Println("json err:", err) + } + return string(response) +} + +func (oc *Oss) Verify(r *http.Request) bool { + + // Get PublicKey bytes + bytePublicKey, err := getPublicKey(r) + if err != nil { + return false + } + + // Get Authorization bytes : decode from Base64String + byteAuthorization, err := getAuthorization(r) + if err != nil { + return false + } + + // Get MD5 bytes from Newly Constructed Authrization String. + byteMD5, err := getMD5FromNewAuthString(r) + if err != nil { + return false + } + + // verifySignature and response to client + if verifySignature(bytePublicKey, byteMD5, byteAuthorization) { + // do something you want accoding to callback_body ... + return true + } else { + return false + } +} + +// getPublicKey : Get PublicKey bytes from Request.URL +func getPublicKey(r *http.Request) ([]byte, error) { + var bytePublicKey []byte + // get PublicKey URL + publicKeyURLBase64 := r.Header.Get("x-oss-pub-key-url") + if publicKeyURLBase64 == "" { + fmt.Println("GetPublicKey from Request header failed : No x-oss-pub-key-url field. ") + return bytePublicKey, errors.New("no x-oss-pub-key-url field in Request header ") + } + publicKeyURL, _ := base64.StdEncoding.DecodeString(publicKeyURLBase64) + // fmt.Printf("publicKeyURL={%s}\n", publicKeyURL) + // get PublicKey Content from URL + responsePublicKeyURL, err := http.Get(string(publicKeyURL)) + if err != nil { + fmt.Printf("Get PublicKey Content from URL failed : %s \n", err.Error()) + return bytePublicKey, err + } + bytePublicKey, err = ioutil.ReadAll(responsePublicKeyURL.Body) + if err != nil { + fmt.Printf("Read PublicKey Content from URL failed : %s \n", err.Error()) + return bytePublicKey, err + } + defer responsePublicKeyURL.Body.Close() + // fmt.Printf("publicKey={%s}\n", bytePublicKey) + return bytePublicKey, nil +} + +// getAuthorization : decode from Base64String +func getAuthorization(r *http.Request) ([]byte, error) { + var byteAuthorization []byte + // Get Authorization bytes : decode from Base64String + strAuthorizationBase64 := r.Header.Get("authorization") + if strAuthorizationBase64 == "" { + fmt.Println("Failed to get authorization field from request header. ") + return byteAuthorization, errors.New("no authorization field in Request header") + } + byteAuthorization, _ = base64.StdEncoding.DecodeString(strAuthorizationBase64) + return byteAuthorization, nil +} + +// getMD5FromNewAuthString : Get MD5 bytes from Newly Constructed Authrization String. +func getMD5FromNewAuthString(r *http.Request) ([]byte, error) { + var byteMD5 []byte + // Construct the New Auth String from URI+Query+Body + bodyContent, err := ioutil.ReadAll(r.Body) + r.Body.Close() + r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyContent)) + if err != nil { + fmt.Printf("Read Request Body failed : %s \n", err.Error()) + return byteMD5, err + } + strCallbackBody := string(bodyContent) + // fmt.Printf("r.URL.RawPath={%s}, r.URL.Query()={%s}, strCallbackBody={%s}\n", r.URL.RawPath, r.URL.Query(), strCallbackBody) + strURLPathDecode, errUnescape := unescapePath(r.URL.Path, encodePathSegment) //url.PathUnescape(r.URL.Path) for Golang v1.8.2+ + if errUnescape != nil { + fmt.Printf("url.PathUnescape failed : URL.Path=%s, error=%s \n", r.URL.Path, err.Error()) + return byteMD5, errUnescape + } + + // Generate New Auth String prepare for MD5 + strAuth := "" + if r.URL.RawQuery == "" { + strAuth = fmt.Sprintf("%s\n%s", strURLPathDecode, strCallbackBody) + } else { + strAuth = fmt.Sprintf("%s?%s\n%s", strURLPathDecode, r.URL.RawQuery, strCallbackBody) + } + // fmt.Printf("NewlyConstructedAuthString={%s}\n", strAuth) + + // Generate MD5 from the New Auth String + md5Ctx := md5.New() + md5Ctx.Write([]byte(strAuth)) + byteMD5 = md5Ctx.Sum(nil) + + return byteMD5, nil +} + +/* VerifySignature +* VerifySignature需要三个重要的数据信息来进行签名验证: 1>获取公钥PublicKey; 2>生成新的MD5鉴权串; 3>解码Request携带的鉴权串; +* 1>获取公钥PublicKey : 从RequestHeader的"x-oss-pub-key-url"字段中获取 URL, 读取URL链接的包含的公钥内容, 进行解码解析, 将其作为rsa.VerifyPKCS1v15的入参。 +* 2>生成新的MD5鉴权串 : 把Request中的url中的path部分进行urldecode, 加上url的query部分, 再加上body, 组合之后进行MD5编码, 得到MD5鉴权字节串。 +* 3>解码Request携带的鉴权串 : 获取RequestHeader的"authorization"字段, 对其进行Base64解码,作为签名验证的鉴权对比串。 +* rsa.VerifyPKCS1v15进行签名验证,返回验证结果。 +* */ +func verifySignature(bytePublicKey []byte, byteMd5 []byte, authorization []byte) bool { + pubBlock, _ := pem.Decode(bytePublicKey) + if pubBlock == nil { + fmt.Printf("Failed to parse PEM block containing the public key") + return false + } + pubInterface, err := x509.ParsePKIXPublicKey(pubBlock.Bytes) + if (pubInterface == nil) || (err != nil) { + fmt.Printf("x509.ParsePKIXPublicKey(publicKey) failed : %s \n", err.Error()) + return false + } + pub := pubInterface.(*rsa.PublicKey) + + errorVerifyPKCS1v15 := rsa.VerifyPKCS1v15(pub, crypto.MD5, byteMd5, authorization) + if errorVerifyPKCS1v15 != nil { + fmt.Printf("\nSignature Verification is Failed : %s \n", errorVerifyPKCS1v15.Error()) + //printByteArray(byteMd5, "AuthMd5(fromNewAuthString)") + //printByteArray(bytePublicKey, "PublicKeyBase64") + //printByteArray(authorization, "AuthorizationFromRequest") + return false + } + + fmt.Printf("\nSignature Verification is Successful. \n") + return true +} + +func printByteArray(byteArrary []byte, arrName string) { + fmt.Printf("++++++++ printByteArray : ArrayName=%s, ArrayLength=%d \n", arrName, len(byteArrary)) + for i := 0; i < len(byteArrary); i++ { + fmt.Printf("%02x", byteArrary[i]) + } + fmt.Printf("\n-------- printByteArray : End . \n") +} + +type EscapeError string + +func (e EscapeError) Error() string { + return "invalid URL escape " + strconv.Quote(string(e)) +} + +type InvalidHostError string + +func (e InvalidHostError) Error() string { + return "invalid character " + strconv.Quote(string(e)) + " in host name" +} + +type encoding int + +const ( + encodePath encoding = 1 + iota + encodePathSegment + encodeHost + encodeZone + encodeUserPassword + encodeQueryComponent + encodeFragment +) + +// unescapePath : unescapes a string; the mode specifies, which section of the URL string is being unescaped. +func unescapePath(s string, mode encoding) (string, error) { + // Count %, check that they're well-formed. + mode = encodePathSegment + n := 0 + hasPlus := false + for i := 0; i < len(s); { + switch s[i] { + case '%': + n++ + if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) { + s = s[i:] + if len(s) > 3 { + s = s[:3] + } + return "", EscapeError(s) + } + // Per https://tools.ietf.org/html/rfc3986#page-21 + // in the host component %-encoding can only be used + // for non-ASCII bytes. + // But https://tools.ietf.org/html/rfc6874#section-2 + // introduces %25 being allowed to escape a percent sign + // in IPv6 scoped-address literals. Yay. + if mode == encodeHost && unhex(s[i+1]) < 8 && s[i:i+3] != "%25" { + return "", EscapeError(s[i : i+3]) + } + if mode == encodeZone { + // RFC 6874 says basically "anything goes" for zone identifiers + // and that even non-ASCII can be redundantly escaped, + // but it seems prudent to restrict %-escaped bytes here to those + // that are valid host name bytes in their unescaped form. + // That is, you can use escaping in the zone identifier but not + // to introduce bytes you couldn't just write directly. + // But Windows puts spaces here! Yay. + v := unhex(s[i+1])<<4 | unhex(s[i+2]) + if s[i:i+3] != "%25" && v != ' ' && shouldEscape(v, encodeHost) { + return "", EscapeError(s[i : i+3]) + } + } + i += 3 + case '+': + hasPlus = mode == encodeQueryComponent + i++ + default: + if (mode == encodeHost || mode == encodeZone) && s[i] < 0x80 && shouldEscape(s[i], mode) { + return "", InvalidHostError(s[i : i+1]) + } + i++ + } + } + + if n == 0 && !hasPlus { + return s, nil + } + + t := make([]byte, len(s)-2*n) + j := 0 + for i := 0; i < len(s); { + switch s[i] { + case '%': + t[j] = unhex(s[i+1])<<4 | unhex(s[i+2]) + j++ + i += 3 + case '+': + if mode == encodeQueryComponent { + t[j] = ' ' + } else { + t[j] = '+' + } + j++ + i++ + default: + t[j] = s[i] + j++ + i++ + } + } + return string(t), nil +} + +// Please be informed that for now shouldEscape does not check all +// reserved characters correctly. See golang.org/issue/5684. +func shouldEscape(c byte, mode encoding) bool { + // §2.3 Unreserved characters (alphanum) + if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' { + return false + } + + if mode == encodeHost || mode == encodeZone { + // §3.2.2 Host allows + // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" + // as part of reg-name. + // We add : because we include :port as part of host. + // We add [ ] because we include [ipv6]:port as part of host. + // We add < > because they're the only characters left that + // we could possibly allow, and Parse will reject them if we + // escape them (because hosts can't use %-encoding for + // ASCII bytes). + switch c { + case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '[', ']', '<', '>', '"': + return false + } + } + + switch c { + case '-', '_', '.', '~': // §2.3 Unreserved characters (mark) + return false + + case '$', '&', '+', ',', '/', ':', ';', '=', '?', '@': // §2.2 Reserved characters (reserved) + // Different sections of the URL allow a few of + // the reserved characters to appear unescaped. + switch mode { + case encodePath: // §3.3 + // The RFC allows : @ & = + $ but saves / ; , for assigning + // meaning to individual path segments. This package + // only manipulates the path as a whole, so we allow those + // last three as well. That leaves only ? to escape. + return c == '?' + + case encodePathSegment: // §3.3 + // The RFC allows : @ & = + $ but saves / ; , for assigning + // meaning to individual path segments. + return c == '/' || c == ';' || c == ',' || c == '?' + + case encodeUserPassword: // §3.2.1 + // The RFC allows ';', ':', '&', '=', '+', '$', and ',' in + // userinfo, so we must escape only '@', '/', and '?'. + // The parsing of userinfo treats ':' as special so we must escape + // that too. + return c == '@' || c == '/' || c == '?' || c == ':' + + case encodeQueryComponent: // §3.4 + // The RFC reserves (so we must escape) everything. + return true + + case encodeFragment: // §4.1 + // The RFC text is silent but the grammar allows + // everything, so escape nothing. + return false + } + } + + // Everything else must be escaped. + return true +} + +func ishex(c byte) bool { + switch { + case '0' <= c && c <= '9': + return true + case 'a' <= c && c <= 'f': + return true + case 'A' <= c && c <= 'F': + return true + } + return false +} + +func unhex(c byte) byte { + switch { + case '0' <= c && c <= '9': + return c - '0' + case 'a' <= c && c <= 'f': + return c - 'a' + 10 + case 'A' <= c && c <= 'F': + return c - 'A' + 10 + } + return 0 +} diff --git a/model/addressBook.go b/model/addressBook.go new file mode 100644 index 0000000..6750398 --- /dev/null +++ b/model/addressBook.go @@ -0,0 +1,44 @@ +package model + +import "Gwen/model/custom_types" + +// final String id; +// String hash; // personal ab hash password +// String password; // shared ab password +// String username; // pc username +// String hostname; +// String platform; +// String alias; +// List tags; +// bool forceAlwaysRelay = false; +// String rdpPort; +// String rdpUsername; +// bool online = false; +// String loginName; //login username +// bool? sameServer; + +// AddressBook 有些字段是Personal才会上传的 +type AddressBook struct { + RowId uint `gorm:"primaryKey" json:"row_id"` + Id string `json:"id" gorm:"default:0;not null;index"` + Username string `json:"username" gorm:"default:'';not null;"` + Password string `json:"password" gorm:"default:'';not null;"` + Hostname string `json:"hostname" gorm:"default:'';not null;"` + Alias string `json:"alias" gorm:"default:'';not null;"` + Platform string `json:"platform" gorm:"default:'';not null;"` + Tags custom_types.AutoJson `json:"tags" gorm:"not null;" swaggertype:"array,string"` + Hash string `json:"hash" gorm:"default:'';not null;"` + UserId uint `json:"user_id" gorm:"default:0;not null;index"` + ForceAlwaysRelay bool `json:"forceAlwaysRelay" gorm:"default:0;not null;"` + RdpPort string `json:"rdpPort" gorm:"default:'';not null;"` + RdpUsername string `json:"rdpUsername" gorm:"default:'';not null;"` + Online bool `json:"online" gorm:"default:0;not null;"` + LoginName string `json:"loginName" gorm:"default:'';not null;"` + SameServer bool `json:"sameServer" gorm:"default:0;not null;"` + TimeModel +} + +type AddressBookList struct { + AddressBooks []*AddressBook `json:"list"` + Pagination +} diff --git a/model/custom_types/auto_json.go b/model/custom_types/auto_json.go new file mode 100644 index 0000000..2538f06 --- /dev/null +++ b/model/custom_types/auto_json.go @@ -0,0 +1,66 @@ +package custom_types + +import ( + "database/sql/driver" + "encoding/json" + "errors" + "fmt" +) + +// AutoJson 数据类型 +type AutoJson json.RawMessage + +func (j *AutoJson) Scan(value interface{}) error { + + var strValue string + switch v := value.(type) { + case []byte: + strValue = string(v) + case string: + strValue = v + default: + return errors.New(fmt.Sprintf("Failed Scan AutoJson value: %v", value)) + } + bytes := []byte(strValue) + //bytes, ok := value.([]byte) + //if !ok { + // return errors.New(fmt.Sprint("Failed Scan AutoJson value:", value)) + //} + + if bytes == nil || len(bytes) == 0 { + *j = AutoJson(json.RawMessage{'[', ']'}) + return nil + } + result := &json.RawMessage{} + err := json.Unmarshal(bytes, result) + //解析json错误 返回空 + if err != nil { + *j = AutoJson(json.RawMessage{'[', ']'}) + return nil + } + *j = AutoJson(*result) + return err +} +func (j AutoJson) Value() (driver.Value, error) { + bytes, err := json.RawMessage(j).MarshalJSON() + return string(bytes), err +} +func (j AutoJson) MarshalJSON() ([]byte, error) { + b, err := json.RawMessage(j).MarshalJSON() + if err != nil { + return nil, err + } + return b, err +} + +func (j *AutoJson) UnmarshalJSON(b []byte) error { + result := json.RawMessage{} + err := result.UnmarshalJSON(b) + *j = AutoJson(result) + return err +} + +func (j AutoJson) String() string { + s, _ := j.MarshalJSON() + return (string)(s) +} diff --git a/model/custom_types/auto_time.go b/model/custom_types/auto_time.go new file mode 100644 index 0000000..9e9e13b --- /dev/null +++ b/model/custom_types/auto_time.go @@ -0,0 +1,24 @@ +package custom_types + +import ( + "database/sql/driver" + "time" +) + +// AutoTime 自定义时间格式 +type AutoTime time.Time + +func (mt AutoTime) Value() (driver.Value, error) { + var zeroTime time.Time + t := time.Time(mt) + if t.UnixNano() == zeroTime.UnixNano() { + return nil, nil + } + return t, nil +} + +func (mt AutoTime) MarshalJSON() ([]byte, error) { + //b := make([]byte, 0, len("2006-01-02 15:04:05")+2) + b := time.Time(mt).AppendFormat([]byte{}, "\"2006-01-02 15:04:05\"") + return b, nil +} diff --git a/model/group.go b/model/group.go new file mode 100644 index 0000000..4d09b54 --- /dev/null +++ b/model/group.go @@ -0,0 +1,18 @@ +package model + +const ( + GroupTypeDefault = 1 // 默认 + GroupTypeShare = 2 // 共享 +) + +type Group struct { + IdModel + Name string `json:"name" gorm:"default:'';not null;"` + Type int `json:"type" gorm:"default:1;not null;"` + TimeModel +} + +type GroupList struct { + Groups []*Group `json:"list"` + Pagination +} diff --git a/model/model.go b/model/model.go new file mode 100644 index 0000000..8cc5615 --- /dev/null +++ b/model/model.go @@ -0,0 +1,27 @@ +package model + +import ( + "Gwen/model/custom_types" +) + +type StatusCode int + +const ( + COMMON_STATUS_ENABLE StatusCode = 1 //通用状态 启用 + COMMON_STATUS_DISABLED StatusCode = 2 //通用状态 禁用 +) + +type IdModel struct { + Id uint `gorm:"primaryKey" json:"id"` +} +type TimeModel struct { + CreatedAt custom_types.AutoTime `json:"created_at" gorm:"type:timestamp;"` + UpdatedAt custom_types.AutoTime `json:"updated_at" gorm:"type:timestamp;"` +} + +// Pagination +type Pagination struct { + Page int64 `form:"page" json:"page"` + Total int64 `form:"total" json:"total"` + PageSize int64 `form:"page_size" json:"page_size"` +} diff --git a/model/peer.go b/model/peer.go new file mode 100644 index 0000000..ad1ed6e --- /dev/null +++ b/model/peer.go @@ -0,0 +1,21 @@ +package model + +type Peer struct { + RowId uint `json:"row_id" gorm:"primaryKey;"` + Id string `json:"id" gorm:"default:'';not null;index"` + Cpu string `json:"cpu" gorm:"default:'';not null;"` + Hostname string `json:"hostname" gorm:"default:'';not null;"` + Memory string `json:"memory" gorm:"default:'';not null;"` + Os string `json:"os" gorm:"default:'';not null;"` + Username string `json:"username" gorm:"default:'';not null;"` + Uuid string `json:"uuid" gorm:"default:'';not null;index"` + Version string `json:"version" gorm:"default:'';not null;"` + UserId uint `json:"user_id" gorm:"default:0;not null;index"` + User User `json:"user,omitempty" gorm:""` + TimeModel +} + +type PeerList struct { + Peers []*Peer `json:"list"` + Pagination +} diff --git a/model/tag.go b/model/tag.go new file mode 100644 index 0000000..ccc9bfc --- /dev/null +++ b/model/tag.go @@ -0,0 +1,14 @@ +package model + +type Tag struct { + IdModel + Name string `json:"name" gorm:"default:'';not null;"` + UserId uint `json:"user_id" gorm:"default:0;not null;index"` + Color uint `json:"color" gorm:"default:0;not null;"` //color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度,后面6位表示颜色, 可以转成rgba + TimeModel +} + +type TagList struct { + Tags []*Tag `json:"list"` + Pagination +} diff --git a/model/user.go b/model/user.go new file mode 100644 index 0000000..32af6d7 --- /dev/null +++ b/model/user.go @@ -0,0 +1,18 @@ +package model + +type User struct { + IdModel + Username string `json:"username" gorm:"default:'';not null;index,unique"` + Password string `json:"-" gorm:"default:'';not null;"` + Nickname string `json:"nickname" gorm:"default:'';not null;"` + Avatar string `json:"avatar" gorm:"default:'';not null;"` + GroupId uint `json:"group_id" gorm:"default:0;not null;index"` + IsAdmin *bool `json:"is_admin" gorm:"default:0;not null;"` + Status StatusCode `json:"status" gorm:"default:1;not null;"` + TimeModel +} + +type UserList struct { + Users []*User `json:"list,omitempty"` + Pagination +} diff --git a/model/userToken.go b/model/userToken.go new file mode 100644 index 0000000..73c3339 --- /dev/null +++ b/model/userToken.go @@ -0,0 +1,9 @@ +package model + +type UserToken struct { + IdModel + UserId uint `json:"user_id" gorm:"default:0;not null;index"` + Token string `json:"token" gorm:"default:'';not null;index"` + ExpiredAt int64 `json:"expired_at" gorm:"default:0;not null;"` + TimeModel +} diff --git a/model/version.go b/model/version.go new file mode 100644 index 0000000..5b46e1f --- /dev/null +++ b/model/version.go @@ -0,0 +1,7 @@ +package model + +type Version struct { + IdModel + Version uint `json:"version" gorm:"default:0;not null;"` + TimeModel +} diff --git a/service/addressBook.go b/service/addressBook.go new file mode 100644 index 0000000..f4fdf75 --- /dev/null +++ b/service/addressBook.go @@ -0,0 +1,109 @@ +package service + +import ( + "Gwen/global" + "Gwen/model" + "gorm.io/gorm" +) + +type AddressBookService struct { +} + +func (s *AddressBookService) Info(id uint) *model.AddressBook { + p := &model.AddressBook{} + global.DB.Where("id = ?", id).First(p) + return p +} +func (s *AddressBookService) InfoByRowId(id uint) *model.AddressBook { + p := &model.AddressBook{} + global.DB.Where("row_id = ?", id).First(p) + return p +} +func (s *AddressBookService) ListByUserId(userId, page, pageSize uint) (res *model.AddressBookList) { + res = s.List(page, pageSize, func(tx *gorm.DB) { + tx.Where("user_id = ?", userId) + }) + return +} +func (s *AddressBookService) ListByUserIds(userIds []uint, page, pageSize uint) (res *model.AddressBookList) { + res = s.List(page, pageSize, func(tx *gorm.DB) { + tx.Where("user_id in (?)", userIds) + }) + return +} + +// AddAddressBook +func (s *AddressBookService) AddAddressBook(ab *model.AddressBook) error { + return global.DB.Create(ab).Error +} + +// UpdateAddressBook +func (s *AddressBookService) UpdateAddressBook(abs []*model.AddressBook, userId uint) error { + //比较peers和数据库中的数据,如果peers中的数据在数据库中不存在,则添加,如果存在则更新,如果数据库中的数据在peers中不存在,则删除 + // 开始事务 + tx := global.DB.Begin() + //1. 获取数据库中的数据 + var dbABs []*model.AddressBook + tx.Where("user_id = ?", userId).Find(&dbABs) + //2. 比较peers和数据库中的数据 + //2.1 获取peers中的id + aBIds := make(map[string]*model.AddressBook) + for _, ab := range abs { + aBIds[ab.Id] = ab + } + //2.2 获取数据库中的id + dbABIds := make(map[string]*model.AddressBook) + for _, dbAb := range dbABs { + dbABIds[dbAb.Id] = dbAb + } + //2.3 比较peers和数据库中的数据 + for id, ab := range aBIds { + dbAB, ok := dbABIds[id] + ab.UserId = userId + if !ok { + //添加 + tx.Create(ab) + } else { + //更新 + tx.Model(&model.AddressBook{}).Where("row_id = ?", dbAB.RowId).Updates(ab) + } + } + //2.4 删除 + for id, dbAB := range dbABIds { + _, ok := aBIds[id] + if !ok { + tx.Delete(dbAB) + } + } + tx.Commit() + return nil + +} + +func (t *AddressBookService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.AddressBookList) { + res = &model.AddressBookList{} + res.Page = int64(page) + res.PageSize = int64(pageSize) + tx := global.DB.Model(&model.AddressBook{}) + if where != nil { + where(tx) + } + tx.Count(&res.Total) + tx.Scopes(Paginate(page, pageSize)) + tx.Find(&res.AddressBooks) + return +} + +// Create 创建 +func (t *AddressBookService) Create(u *model.AddressBook) error { + res := global.DB.Create(u).Error + return res +} +func (t *AddressBookService) Delete(u *model.AddressBook) error { + return global.DB.Delete(u).Error +} + +// Update 更新 +func (t *AddressBookService) Update(u *model.AddressBook) error { + return global.DB.Model(u).Updates(u).Error +} diff --git a/service/group.go b/service/group.go new file mode 100644 index 0000000..358e2cb --- /dev/null +++ b/service/group.go @@ -0,0 +1,45 @@ +package service + +import ( + "Gwen/global" + "Gwen/model" + "gorm.io/gorm" +) + +type GroupService struct { +} + +// InfoById 根据用户id取用户信息 +func (us *GroupService) InfoById(id uint) *model.Group { + u := &model.Group{} + global.DB.Where("id = ?", id).First(u) + return u +} + +func (us *GroupService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.GroupList) { + res = &model.GroupList{} + res.Page = int64(page) + res.PageSize = int64(pageSize) + tx := global.DB.Model(&model.Group{}) + if where != nil { + where(tx) + } + tx.Count(&res.Total) + tx.Scopes(Paginate(page, pageSize)) + tx.Find(&res.Groups) + return +} + +// Create 创建 +func (us *GroupService) Create(u *model.Group) error { + res := global.DB.Create(u).Error + return res +} +func (us *GroupService) Delete(u *model.Group) error { + return global.DB.Delete(u).Error +} + +// Update 更新 +func (us *GroupService) Update(u *model.Group) error { + return global.DB.Model(u).Updates(u).Error +} diff --git a/service/peer.go b/service/peer.go new file mode 100644 index 0000000..f654dc4 --- /dev/null +++ b/service/peer.go @@ -0,0 +1,63 @@ +package service + +import ( + "Gwen/global" + "Gwen/model" + "gorm.io/gorm" +) + +type PeerService struct { +} + +// FindById 根据id查找 +func (ps *PeerService) FindById(id string) *model.Peer { + p := &model.Peer{} + global.DB.Where("id = ?", id).First(p) + return p +} +func (ps *PeerService) InfoByRowId(id uint) *model.Peer { + p := &model.Peer{} + global.DB.Where("row_id = ?", id).First(p) + return p +} + +//// ListByUserIds 根据用户id取列表 +//func (ps *PeerService) ListByUserIds(userIds []uint, page, pageSize uint) (res *model.PeerList) { +// res = &model.PeerList{} +// res.Page = int64(page) +// res.PageSize = int64(pageSize) +// tx := global.DB.Model(&model.Peer{}).Preload("User") +// tx.Where("user_id in (?)", userIds) +// tx.Count(&res.Total) +// tx.Scopes(Paginate(page, pageSize)) +// tx.Find(&res.Peers) +// return +//} + +func (ps *PeerService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.PeerList) { + res = &model.PeerList{} + res.Page = int64(page) + res.PageSize = int64(pageSize) + tx := global.DB.Model(&model.Peer{}) + if where != nil { + where(tx) + } + tx.Count(&res.Total) + tx.Scopes(Paginate(page, pageSize)) + tx.Find(&res.Peers) + return +} + +// Create 创建 +func (ps *PeerService) Create(u *model.Peer) error { + res := global.DB.Create(u).Error + return res +} +func (ps *PeerService) Delete(u *model.Peer) error { + return global.DB.Delete(u).Error +} + +// Update 更新 +func (ps *PeerService) Update(u *model.Peer) error { + return global.DB.Model(u).Updates(u).Error +} diff --git a/service/service.go b/service/service.go new file mode 100644 index 0000000..a8a4f03 --- /dev/null +++ b/service/service.go @@ -0,0 +1,42 @@ +package service + +import ( + "Gwen/model" + "gorm.io/gorm" +) + +type Service struct { + //AdminService *AdminService + //AdminRoleService *AdminRoleService + *UserService + *AddressBookService + *TagService + *PeerService + *GroupService +} + +func New() *Service { + all := new(Service) + return all +} + +var AllService = New() + +func Paginate(page, pageSize uint) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + if page == 0 { + page = 1 + } + if pageSize == 0 { + pageSize = 10 + } + offset := (page - 1) * pageSize + return db.Offset(int(offset)).Limit(int(pageSize)) + } +} + +func CommonEnable() func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + return db.Where("status = ?", model.COMMON_STATUS_ENABLE) + } +} diff --git a/service/tag.go b/service/tag.go new file mode 100644 index 0000000..881776b --- /dev/null +++ b/service/tag.go @@ -0,0 +1,88 @@ +package service + +import ( + "Gwen/global" + "Gwen/model" + "gorm.io/gorm" +) + +type TagService struct { +} + +func (s *TagService) Info(id uint) *model.Tag { + p := &model.Tag{} + global.DB.Where("id = ?", id).First(p) + return p +} + +func (s *TagService) ListByUserId(userId uint) (res *model.TagList) { + res = s.List(1, 1000, func(tx *gorm.DB) { + tx.Where("user_id = ?", userId) + }) + return +} + +func (s *TagService) UpdateTags(userId uint, tags map[string]uint) { + tx := global.DB.Begin() + //先查询所有tag + var allTags []*model.Tag + tx.Where("user_id = ?", userId).Find(&allTags) + for _, t := range allTags { + if _, ok := tags[t.Name]; !ok { + //删除 + tx.Delete(t) + } else { + if tags[t.Name] != t.Color { + //更新 + t.Color = tags[t.Name] + tx.Save(t) + } + //移除 + delete(tags, t.Name) + } + } + //新增 + for tag, color := range tags { + t := &model.Tag{} + t.Name = tag + t.Color = color + t.UserId = userId + tx.Create(t) + } + tx.Commit() +} + +// InfoById 根据用户id取用户信息 +func (t *TagService) InfoById(id uint) *model.Tag { + u := &model.Tag{} + global.DB.Where("id = ?", id).First(u) + return u +} + +func (t *TagService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.TagList) { + res = &model.TagList{} + res.Page = int64(page) + res.PageSize = int64(pageSize) + tx := global.DB.Model(&model.Tag{}) + if where != nil { + where(tx) + } + tx.Count(&res.Total) + tx.Scopes(Paginate(page, pageSize)) + tx.Find(&res.Tags) + return +} + +// Create 创建 +func (t *TagService) Create(u *model.Tag) error { + res := global.DB.Create(u).Error + return res +} +func (t *TagService) Delete(u *model.Tag) error { + return global.DB.Delete(u).Error +} + +// Update 更新 +func (t *TagService) Update(u *model.Tag) error { + return global.DB.Model(u).Updates(u).Error +} diff --git a/service/user.go b/service/user.go new file mode 100644 index 0000000..9b36b40 --- /dev/null +++ b/service/user.go @@ -0,0 +1,171 @@ +package service + +import ( + "Gwen/global" + adResp "Gwen/http/response/admin" + "Gwen/model" + "Gwen/utils" + "github.com/gin-gonic/gin" + "gorm.io/gorm" + "time" +) + +type UserService struct { +} + +// InfoById 根据用户id取用户信息 +func (us *UserService) InfoById(id uint) *model.User { + u := &model.User{} + global.DB.Where("id = ?", id).First(u) + return u +} + +// InfoByOpenid 根据openid取用户信息 +func (us *UserService) InfoByOpenid(openid string) *model.User { + u := &model.User{} + global.DB.Where("openid = ?", openid).First(u) + return u +} + +// InfoByUsernamePassword 根据用户名密码取用户信息 +func (us *UserService) InfoByUsernamePassword(username, password string) *model.User { + u := &model.User{} + global.DB.Where("username = ? and password = ?", username, us.EncryptPassword(password)).First(u) + return u +} + +// InfoByAccesstoken 根据accesstoken取用户信息 +func (us *UserService) InfoByAccessToken(token string) *model.User { + u := &model.User{} + ut := &model.UserToken{} + global.DB.Where("token = ?", token).First(ut) + if ut.Id == 0 { + return u + } + if ut.ExpiredAt < time.Now().Unix() { + return u + } + global.DB.Where("id = ?", ut.UserId).First(u) + return u +} + +// GenerateToken 生成token +func (us *UserService) GenerateToken(u *model.User) string { + return utils.Md5(u.Username + u.Password + time.Now().String()) +} + +// Login 登录 +func (us *UserService) Login(u *model.User) *model.UserToken { + token := us.GenerateToken(u) + ut := &model.UserToken{ + UserId: u.Id, + Token: token, + ExpiredAt: time.Now().Add(time.Hour * 24 * 7).Unix(), + } + global.DB.Create(ut) + return ut +} + +// CurUser 获取当前用户 +func (us *UserService) CurUser(c *gin.Context) *model.User { + user, _ := c.Get("curUser") + u, ok := user.(*model.User) + if !ok { + return nil + } + return u +} + +func (us *UserService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.UserList) { + res = &model.UserList{} + res.Page = int64(page) + res.PageSize = int64(pageSize) + tx := global.DB.Model(&model.User{}) + if where != nil { + where(tx) + } + tx.Count(&res.Total) + tx.Scopes(Paginate(page, pageSize)) + tx.Find(&res.Users) + return +} + +// ListByGroupId 根据组id取用户列表 +func (us *UserService) ListByGroupId(groupId, page, pageSize uint) (res *model.UserList) { + res = us.List(page, pageSize, func(tx *gorm.DB) { + tx.Where("group_id = ?", groupId) + }) + return +} + +// ListIdsByGroupId 根据组id取用户id列表 +func (us *UserService) ListIdsByGroupId(groupId uint) (ids []uint) { + global.DB.Model(&model.User{}).Where("group_id = ?", groupId).Pluck("id", &ids) + return ids + +} + +// ListIdAndNameByGroupId 根据组id取用户id和用户名列表 +func (us *UserService) ListIdAndNameByGroupId(groupId uint) (res []*model.User) { + global.DB.Model(&model.User{}).Where("group_id = ?", groupId).Select("id, username").Find(&res) + return res +} + +// EncryptPassword 加密密码 +func (us *UserService) EncryptPassword(password string) string { + return utils.Md5(password + "rustdesk-api") +} + +// CheckUserEnable 判断用户是否禁用 +func (us *UserService) CheckUserEnable(u *model.User) bool { + return u.Status == model.COMMON_STATUS_ENABLE +} + +// Create 创建 +func (us *UserService) Create(u *model.User) error { + u.Password = us.EncryptPassword(u.Password) + res := global.DB.Create(u).Error + return res +} + +// Logout 退出登录 +func (us *UserService) Logout(u *model.User, token string) error { + return global.DB.Where("user_id = ? and token = ?", u.Id, token).Delete(&model.UserToken{}).Error +} +func (us *UserService) Delete(u *model.User) error { + return global.DB.Delete(u).Error +} + +// Update 更新 +func (us *UserService) Update(u *model.User) error { + return global.DB.Model(u).Updates(u).Error +} + +// FlushToken 清空token +func (us *UserService) FlushToken(u *model.User) error { + return global.DB.Where("user_id = ?", u.Id).Delete(&model.UserToken{}).Error +} + +// UpdatePassword 更新密码 +func (us *UserService) UpdatePassword(u *model.User, password string) error { + u.Password = us.EncryptPassword(password) + err := global.DB.Model(u).Update("password", u.Password).Error + if err != nil { + return err + } + err = us.FlushToken(u) + return err +} + +// IsAdmin 是否管理员 +func (us *UserService) IsAdmin(u *model.User) bool { + return *u.IsAdmin +} + +// RouteNames +func (us *UserService) RouteNames(u *model.User) []string { + if us.IsAdmin(u) { + return adResp.AdminRouteNames + } + return adResp.UserRouteNames +} diff --git a/utils/tools.go b/utils/tools.go new file mode 100644 index 0000000..c58e275 --- /dev/null +++ b/utils/tools.go @@ -0,0 +1,63 @@ +package utils + +import ( + "crypto/md5" + "encoding/json" + "fmt" + "reflect" + "runtime/debug" +) + +func Md5(str string) string { + t := md5.Sum(([]byte)(str)) + return fmt.Sprintf("%x", t) +} + +func CopyStructByJson(src, dst interface{}) { + str, _ := json.Marshal(src) + err := json.Unmarshal(str, dst) + if err != nil { + return + } +} + +// CopyStructToMap 结构体转map +func CopyStructToMap(src interface{}) map[string]interface{} { + var res = map[string]interface{}{} + str, _ := json.Marshal(src) + err := json.Unmarshal(str, &res) + if err != nil { + return nil + } + return res +} + +// SafeGo is a common function to recover panic for goroutines +func SafeGo(f interface{}, params ...interface{}) { + go func() { + defer func() { + if r := recover(); r != nil { + fmt.Printf("Recovered in SafeGo: %v\n", r) + debug.PrintStack() + } + }() + + // Convert f to a reflect.Value + funcValue := reflect.ValueOf(f) + + // Check if the f is a function + if funcValue.Kind() != reflect.Func { + fmt.Println("SafeGo: value is not a function") + return + } + + // Convert params to reflect.Value + paramsValue := make([]reflect.Value, len(params)) + for i, param := range params { + paramsValue[i] = reflect.ValueOf(param) + } + + // Call the function f with params + funcValue.Call(paramsValue) + }() +}