This commit is contained in:
ljw
2024-09-13 15:57:29 +08:00
commit c53df223d1
112 changed files with 14353 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
.idea
runtime
!runtime/cache/.gitignore
go.sum
resources
!resources/public/upload/.gitignore
!resources/public/web
release
data
+105
View File
@@ -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 <relay-server-ip[:port]> -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. 后台文档 <youer server>/admin/swagger/index.html
2. PC端文档 <youer server>/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`
目录下生成对应的可执行文件。
+14
View File
@@ -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
+15
View File
@@ -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/
+252
View File
@@ -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)
}
}
+42
View File
@@ -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
View File
+9
View File
@@ -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"`
}
+56
View File
@@ -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
}
+8
View File
@@ -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"`
}
+19
View File
@@ -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"`
}
+8
View File
@@ -0,0 +1,8 @@
package config
import "time"
type Jwt struct {
PrivateKey string `mapstructure:"private-key"`
ExpireDuration time.Duration `mapstructure:"expire-duration"`
}
+7
View File
@@ -0,0 +1,7 @@
package config
type Logger struct {
Path string
Level string
ReportCaller bool `mapstructure:"report-caller"`
}
+10
View File
@@ -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"`
}
+7
View File
@@ -0,0 +1,7 @@
package config
type Redis struct {
Addr string
Password string
Db int
}
+8
View File
@@ -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"`
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+830
View File
@@ -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)
}
+805
View File
@@ -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"
}
}
}
+513
View File
@@ -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"
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

+5
View File
@@ -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
+33
View File
@@ -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
)
+73
View File
@@ -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
)
+191
View File
@@ -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, "信息不存在")
}
+83
View File
@@ -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,
})
}
+160
View File
@@ -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, "信息不存在")
}
+74
View File
@@ -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)
}
+160
View File
@@ -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, "信息不存在")
}
+30
View File
@@ -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)
}
+190
View File
@@ -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, "信息不存在")
}
+261
View File
@@ -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)
}
+150
View File
@@ -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, "")
}
+115
View File
@@ -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,
})
}
+39
View File
@@ -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{})
}
+90
View File
@@ -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)
}
+48
View File
@@ -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, "")
}
+74
View File
@@ -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,
})
}
+42
View File
@@ -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,
},
)
}
+68
View File
@@ -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)
}
+30
View File
@@ -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)
}
+32
View File
@@ -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()
}
}
+22
View File
@@ -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()
}
}
+23
View File
@@ -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()
}
}
+50
View File
@@ -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()
}
}
+20
View File
@@ -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()
}
}
+44
View File
@@ -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()
}
}
+56
View File
@@ -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
}
+21
View File
@@ -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
}
+6
View File
@@ -0,0 +1,6 @@
package admin
type Login struct {
Username string `json:"username" validate:"required" label:"用户名"`
Password string `json:"password,omitempty" validate:"required" label:"密码"`
}
+30
View File
@@ -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,
}
}
+33
View File
@@ -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
}
+57
View File
@@ -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"`
}
+37
View File
@@ -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,
}
}
+41
View File
@@ -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"`
}
+13
View File
@@ -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{"*"}
+9
View File
@@ -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"`
}
+74
View File
@@ -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<String, dynamic> info = {};
int? status;
String user = '';
String user_name = '';
String note = '';
PeerPayload.fromJson(Map<String, dynamic> json)
: id = json['id'] ?? '',
info = (json['info'] is Map<String, dynamic>) ? 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
// }
//}
+55
View File
@@ -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"`
}
+55
View File
@@ -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,
}
}
+53
View File
@@ -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"`
}
+118
View File
@@ -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)
}
}*/
+73
View File
@@ -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"))
}
+15
View File
@@ -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"))
}
+12
View File
@@ -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)
}
+11
View File
@@ -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)
}
+71
View File
@@ -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
}
+92
View File
@@ -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)
}
+103
View File
@@ -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(),
}
}
+94
View File
@@ -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)
}
}
+215
View File
@@ -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
}
+107
View File
@@ -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)
}
}
+49
View File
@@ -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
}
+94
View File
@@ -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)
}
}
+65
View File
@@ -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{}),
}
}
+108
View File
@@ -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("读取错误")
//}
}
+61
View File
@@ -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
}
+80
View File
@@ -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)
}
}
+32
View File
@@ -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{},
}
}
+100
View File
@@ -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()
}
+9
View File
@@ -0,0 +1,9 @@
package lock
import "sync"
type Locker interface {
GetLock(key string) *sync.Mutex
Lock(key string)
UnLock(key string)
}
+54
View File
@@ -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()
}
+40
View File
@@ -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
}
+30
View File
@@ -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
}
+4
View File
@@ -0,0 +1,4 @@
package upload
type Local struct {
}
+475
View File
@@ -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
}
+44
View File
@@ -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<dynamic> 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
}
+66
View File
@@ -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)
}
+24
View File
@@ -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
}
+18
View File
@@ -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
}
+27
View File
@@ -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"`
}

Some files were not shown because too many files have changed in this diff Show More