ljw 1 год назад
Сommit
c53df223d1
100 измененных файлов с 13676 добавлено и 0 удалено
  1. 9 0
      .gitignore
  2. 105 0
      README.md
  3. 14 0
      build.bat
  4. 15 0
      build.sh
  5. 252 0
      cmd/apimain.go
  6. 42 0
      conf/config.yaml
  7. 0 0
      conf/jwt_pri.pem
  8. 9 0
      config/cache.go
  9. 56 0
      config/config.go
  10. 8 0
      config/gin.go
  11. 19 0
      config/gorm.go
  12. 8 0
      config/jwt.go
  13. 7 0
      config/logger.go
  14. 10 0
      config/oss.go
  15. 7 0
      config/redis.go
  16. 8 0
      config/rustdesk.go
  17. 2331 0
      docs/admin/admin_docs.go
  18. 2306 0
      docs/admin/admin_swagger.json
  19. 1411 0
      docs/admin/admin_swagger.yaml
  20. 830 0
      docs/api/api_docs.go
  21. 805 0
      docs/api/api_swagger.json
  22. 513 0
      docs/api/api_swagger.yaml
  23. BIN
      docs/api_swag.png
  24. BIN
      docs/pc_ab.png
  25. BIN
      docs/pc_gr.png
  26. BIN
      docs/web_admin.png
  27. BIN
      docs/web_admin_gr.png
  28. BIN
      docs/web_resetpwd.png
  29. BIN
      docs/web_user.png
  30. BIN
      docs/webclient_conf.png
  31. 5 0
      generate_api.go
  32. 33 0
      global/global.go
  33. 73 0
      go.mod
  34. 191 0
      http/controller/admin/addressBook.go
  35. 83 0
      http/controller/admin/file.go
  36. 160 0
      http/controller/admin/group.go
  37. 74 0
      http/controller/admin/login.go
  38. 160 0
      http/controller/admin/peer.go
  39. 30 0
      http/controller/admin/rustdesk.go
  40. 190 0
      http/controller/admin/tag.go
  41. 261 0
      http/controller/admin/user.go
  42. 150 0
      http/controller/api/ab.go
  43. 115 0
      http/controller/api/group.go
  44. 39 0
      http/controller/api/index.go
  45. 90 0
      http/controller/api/login.go
  46. 48 0
      http/controller/api/peer.go
  47. 74 0
      http/controller/api/user.go
  48. 42 0
      http/controller/api/webClient.go
  49. 68 0
      http/controller/web/index.go
  50. 30 0
      http/http.go
  51. 32 0
      http/middleware/admin.go
  52. 22 0
      http/middleware/admin_privilege.go
  53. 23 0
      http/middleware/cors.go
  54. 50 0
      http/middleware/jwt.go
  55. 20 0
      http/middleware/logger.go
  56. 44 0
      http/middleware/rustauth.go
  57. 56 0
      http/request/admin/addressBook.go
  58. 21 0
      http/request/admin/group.go
  59. 6 0
      http/request/admin/login.go
  60. 30 0
      http/request/admin/peer.go
  61. 33 0
      http/request/admin/tag.go
  62. 57 0
      http/request/admin/user.go
  63. 37 0
      http/request/api/peer.go
  64. 41 0
      http/request/api/user.go
  65. 13 0
      http/response/admin/user.go
  66. 9 0
      http/response/api/ab.go
  67. 74 0
      http/response/api/peer.go
  68. 55 0
      http/response/api/user.go
  69. 55 0
      http/response/api/webClient.go
  70. 53 0
      http/response/response.go
  71. 118 0
      http/router/admin.go
  72. 73 0
      http/router/api.go
  73. 15 0
      http/router/router.go
  74. 12 0
      http/run.go
  75. 11 0
      http/run_win.go
  76. 71 0
      lib/cache/cache.go
  77. 92 0
      lib/cache/cache_test.go
  78. 103 0
      lib/cache/file.go
  79. 94 0
      lib/cache/file_test.go
  80. 215 0
      lib/cache/memory.go
  81. 107 0
      lib/cache/memory_test.go
  82. 49 0
      lib/cache/redis.go
  83. 94 0
      lib/cache/redis_test.go
  84. 65 0
      lib/cache/simple_cache.go
  85. 108 0
      lib/cache/simple_cache_test.go
  86. 61 0
      lib/jwt/jwt.go
  87. 80 0
      lib/jwt/jwt_test.go
  88. 32 0
      lib/lock/local.go
  89. 100 0
      lib/lock/local_test.go
  90. 9 0
      lib/lock/lock.go
  91. 54 0
      lib/logger/logger.go
  92. 40 0
      lib/orm/mysql.go
  93. 30 0
      lib/orm/sqlite.go
  94. 4 0
      lib/upload/local.go
  95. 475 0
      lib/upload/oss.go
  96. 44 0
      model/addressBook.go
  97. 66 0
      model/custom_types/auto_json.go
  98. 24 0
      model/custom_types/auto_time.go
  99. 18 0
      model/group.go
  100. 0 0
      model/model.go

+ 9 - 0
.gitignore

@@ -0,0 +1,9 @@
1
+.idea
2
+runtime
3
+!runtime/cache/.gitignore
4
+go.sum
5
+resources
6
+!resources/public/upload/.gitignore
7
+!resources/public/web
8
+release
9
+data

+ 105 - 0
README.md

@@ -0,0 +1,105 @@
1
+# RustDesk API
2
+
3
+本项目使用 Go 实现了 RustDesk 的 API,并包含了 Web UI 和 Web 客户端。RustDesk 是一个远程桌面软件,提供了自托管的解决方案。
4
+
5
+## 使用前准备
6
+
7
+### Rustdesk
8
+
9
+1. PC客户端使用的是 ***1.3.0***,经测试 ***1.2.6+*** 都可以
10
+2. server端必须指定key,不能用自带的生成的key,否则可能链接不上或者超时
11
+
12
+```bash
13
+hbbs -r <relay-server-ip[:port]> -k 123456789
14
+hbbr -k 123456789
15
+```
16
+
17
+## 功能
18
+
19
+### **API 服务**: 基本实现了PC端基础的接口。
20
+
21
+![pc_ab](docs/pc_ab.png)
22
+![pc_gr](docs/pc_gr.png)
23
+
24
+### **Web UI**: 使用前后端分离,提供用户友好的管理界面,主要用来管理和展示。
25
+
26
+***初次安装管理员为用户名密码为admin admin,请即时更改密码***
27
+
28
+1. 管理员界面
29
+   ![web_admin](docs/web_admin.png)
30
+2. 普通用户界面
31
+   ![web_user](docs/web_user.png)
32
+3. 更改密码在右上角
33
+
34
+![web_resetpwd](docs/web_resetpwd.png)
35
+
36
+4. 分组可以自定义,方便管理,暂时支持两种类型: `共享组` 和 `普通组`
37
+   ![web_admin_gr](docs/web_admin_gr.png)
38
+
39
+### **Web 客户端**:
40
+
41
+1. 如果已经登录了后台,web client将自动直接登录
42
+2. 如果没登录后台,点击右上角登录即可,api server已经自动配置好了
43
+3. 登录后台后,会将地址簿自动保存到web client中,方便使用
44
+   ![webclient_conf](docs/webclient_conf.png)
45
+
46
+### **自动化文档**: 使用 Swag 生成 API 文档,方便开发者理解和使用 API。
47
+
48
+1. 后台文档 <youer server>/admin/swagger/index.html
49
+2. PC端文档 <youer server>/swagger/index.html
50
+   ![api_swag](docs/api_swag.png)
51
+
52
+## 安装与运行
53
+
54
+### 相关配置
55
+
56
+* 参考`conf/config.yaml`配置文件,修改相关配置。如果`gorm.type`是`sqlite`,则不需要配置mysql相关配置。
57
+
58
+```yaml
59
+gin:
60
+  api-addr: "0.0.0.0:21114"
61
+  mode: "release"
62
+  resources-path: 'resources'
63
+gorm:
64
+  type: "sqlite"
65
+  max-idle-conns: 10
66
+  max-open-conns: 100
67
+mysql:
68
+  username: "root"
69
+  password: "111111"
70
+  addr: "192.168.1.66:3308"
71
+  dbname: "rustdesk"
72
+rustdesk:
73
+  id-server: "192.168.1.66:21116"
74
+  relay-server: "192.168.1.66:21117"
75
+  api-server: "http://192.168.1.66:21114"
76
+  key: "123456789"
77
+```
78
+
79
+### 安装步骤
80
+
81
+#### docker运行
82
+
83
+#### 下载release直接运行
84
+
85
+#### 源码安装
86
+
87
+1. 克隆仓库
88
+   ```bash
89
+   git clone https://github.com/lejianwen/rustdesk-api.git
90
+   cd rustdesk-api
91
+   ```
92
+2. 安装依赖
93
+    ```bash
94
+    go mod tidy
95
+    ```
96
+3. 运行
97
+    ```bash
98
+    go run cmd/apimain.go
99
+    #或者直接Build
100
+    ./build.sh
101
+    #或者使用generate_api.go生成api
102
+    go generate generate_api.go
103
+    ```
104
+4. 编译,如果想自己编译,先cd到项目根目录,然后windows下直接运行`build.bat`,linux下运行`build.sh`,编译后会在`release`
105
+   目录下生成对应的可执行文件。

+ 14 - 0
build.bat

@@ -0,0 +1,14 @@
1
+@echo off
2
+go env -w GO111MODULE=on
3
+go env -w GOPROXY=https://goproxy.cn,direct
4
+go env -w CGO_ENABLED=1
5
+go env -w GOOS=windows
6
+go env -w GOARCH=amd64
7
+swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin
8
+swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
9
+go build -o release/apimain.exe cmd/apimain.go
10
+xcopy resources release\resources /E /I /Y
11
+xcopy docs release\docs /E /I /Y
12
+xcopy data release\data /E /I /Y
13
+xcopy conf release\conf /E /I /Y
14
+xcopy runtime release\runtime /E /I /Y

+ 15 - 0
build.sh

@@ -0,0 +1,15 @@
1
+#!/bin/sh
2
+
3
+go env -w GO111MODULE=on
4
+go env -w GOPROXY=https://goproxy.cn,direct
5
+go env -w CGO_ENABLED=1
6
+go env -w GOOS=linux
7
+go env -w GOARCH=amd64
8
+swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin
9
+swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
10
+go build -o release/apimain cmd/apimain.go
11
+cp -ar resources release/
12
+cp -ar docs release/
13
+cp -ar data release/
14
+cp -ar conf release/
15
+cp -ar runtime release/

+ 252 - 0
cmd/apimain.go

@@ -0,0 +1,252 @@
1
+package main
2
+
3
+import (
4
+	"Gwen/config"
5
+	"Gwen/global"
6
+	"Gwen/http"
7
+	"Gwen/lib/cache"
8
+	"Gwen/lib/lock"
9
+	"Gwen/lib/logger"
10
+	"Gwen/lib/orm"
11
+	"Gwen/lib/upload"
12
+	"Gwen/model"
13
+	"Gwen/service"
14
+	"fmt"
15
+	"github.com/go-playground/locales/en"
16
+	"github.com/go-playground/locales/zh_Hans_CN"
17
+	ut "github.com/go-playground/universal-translator"
18
+	"github.com/go-playground/validator/v10"
19
+	zh_translations "github.com/go-playground/validator/v10/translations/zh"
20
+	"github.com/go-redis/redis/v8"
21
+	"reflect"
22
+)
23
+
24
+// @title 管理系统API
25
+// @version 1.0
26
+// @description 接口
27
+// @basePath /api
28
+// @securityDefinitions.apikey token
29
+// @in header
30
+// @name api-token
31
+// @securitydefinitions.apikey BearerAuth
32
+// @in header
33
+// @name Authorization
34
+func main() {
35
+	//配置解析
36
+	global.Viper = config.Init(&global.Config, func() {
37
+		fmt.Println(global.Config)
38
+	})
39
+
40
+	//日志
41
+	global.Logger = logger.New(&logger.Config{
42
+		Path:         global.Config.Logger.Path,
43
+		Level:        global.Config.Logger.Level,
44
+		ReportCaller: global.Config.Logger.ReportCaller,
45
+	})
46
+
47
+	//redis
48
+	global.Redis = redis.NewClient(&redis.Options{
49
+		Addr:     global.Config.Redis.Addr,
50
+		Password: global.Config.Redis.Password,
51
+		DB:       global.Config.Redis.Db,
52
+	})
53
+
54
+	//cache
55
+	if global.Config.Cache.Type == cache.TypeFile {
56
+		fc := cache.NewFileCache()
57
+		fc.SetDir(global.Config.Cache.FileDir)
58
+		global.Cache = fc
59
+	} else if global.Config.Cache.Type == cache.TypeRedis {
60
+		global.Cache = cache.NewRedis(&redis.Options{
61
+			Addr:     global.Config.Cache.RedisAddr,
62
+			Password: global.Config.Cache.RedisPwd,
63
+			DB:       global.Config.Cache.RedisDb,
64
+		})
65
+	}
66
+	//gorm
67
+	if global.Config.Gorm.Type == config.TypeMysql {
68
+		dns := global.Config.Mysql.Username + ":" + global.Config.Mysql.Password + "@(" + global.Config.Mysql.Addr + ")/" + global.Config.Mysql.Dbname + "?charset=utf8mb4&parseTime=True&loc=Local"
69
+		global.DB = orm.NewMysql(&orm.MysqlConfig{
70
+			Dns:          dns,
71
+			MaxIdleConns: global.Config.Gorm.MaxIdleConns,
72
+			MaxOpenConns: global.Config.Gorm.MaxOpenConns,
73
+		})
74
+	} else {
75
+		//sqlite
76
+		global.DB = orm.NewSqlite(&orm.SqliteConfig{
77
+			MaxIdleConns: global.Config.Gorm.MaxIdleConns,
78
+			MaxOpenConns: global.Config.Gorm.MaxOpenConns,
79
+		})
80
+	}
81
+	DatabaseAutoUpdate()
82
+
83
+	//validator
84
+	ApiInitValidator()
85
+
86
+	//oss
87
+	global.Oss = &upload.Oss{
88
+		AccessKeyId:     global.Config.Oss.AccessKeyId,
89
+		AccessKeySecret: global.Config.Oss.AccessKeySecret,
90
+		Host:            global.Config.Oss.Host,
91
+		CallbackUrl:     global.Config.Oss.CallbackUrl,
92
+		ExpireTime:      global.Config.Oss.ExpireTime,
93
+		MaxByte:         global.Config.Oss.MaxByte,
94
+	}
95
+
96
+	//jwt
97
+	//fmt.Println(global.Config.Jwt.PrivateKey)
98
+	//global.Jwt = jwt.NewJwt(global.Config.Jwt.PrivateKey, global.Config.Jwt.ExpireDuration*time.Second)
99
+
100
+	//locker
101
+	global.Lock = lock.NewLocal()
102
+
103
+	//gin
104
+	http.ApiInit()
105
+
106
+}
107
+
108
+func ApiInitValidator() {
109
+	validate := validator.New()
110
+	enT := en.New()
111
+	cn := zh_Hans_CN.New()
112
+	uni := ut.New(enT, cn)
113
+	trans, _ := uni.GetTranslator("cn")
114
+	err := zh_translations.RegisterDefaultTranslations(validate, trans)
115
+	if err != nil {
116
+		//退出
117
+		panic(err)
118
+	}
119
+	validate.RegisterTagNameFunc(func(field reflect.StructField) string {
120
+		label := field.Tag.Get("label")
121
+		if label == "" {
122
+			return field.Name
123
+		}
124
+		return label
125
+	})
126
+	global.Validator.Validate = validate
127
+	global.Validator.VTrans = trans
128
+
129
+	global.Validator.ValidStruct = func(i interface{}) []string {
130
+		err := global.Validator.Validate.Struct(i)
131
+		errList := make([]string, 0, 10)
132
+		if err != nil {
133
+			if _, ok := err.(*validator.InvalidValidationError); ok {
134
+				errList = append(errList, err.Error())
135
+				return errList
136
+			}
137
+			for _, err2 := range err.(validator.ValidationErrors) {
138
+				errList = append(errList, err2.Translate(global.Validator.VTrans))
139
+			}
140
+		}
141
+		return errList
142
+	}
143
+	global.Validator.ValidVar = func(field interface{}, tag string) []string {
144
+		err := global.Validator.Validate.Var(field, tag)
145
+		fmt.Println(err)
146
+		errList := make([]string, 0, 10)
147
+		if err != nil {
148
+			if _, ok := err.(*validator.InvalidValidationError); ok {
149
+				errList = append(errList, err.Error())
150
+				return errList
151
+			}
152
+			for _, err2 := range err.(validator.ValidationErrors) {
153
+				errList = append(errList, err2.Translate(global.Validator.VTrans))
154
+			}
155
+		}
156
+		return errList
157
+	}
158
+
159
+}
160
+
161
+func DatabaseAutoUpdate() {
162
+	version := 100
163
+
164
+	db := global.DB
165
+
166
+	if global.Config.Gorm.Type == config.TypeMysql {
167
+		//检查存不存在数据库,不存在则创建
168
+		dbName := db.Migrator().CurrentDatabase()
169
+		fmt.Println("dbName", dbName)
170
+		if dbName == "" {
171
+			dbName = global.Config.Mysql.Dbname
172
+			// 移除 DSN 中的数据库名称,以便初始连接时不指定数据库
173
+			dsnWithoutDB := global.Config.Mysql.Username + ":" + global.Config.Mysql.Password + "@(" + global.Config.Mysql.Addr + ")/?charset=utf8mb4&parseTime=True&loc=Local"
174
+			//新链接
175
+			dbWithoutDB := orm.NewMysql(&orm.MysqlConfig{
176
+				Dns: dsnWithoutDB,
177
+			})
178
+			// 获取底层的 *sql.DB 对象,并确保在程序退出时关闭连接
179
+			sqlDBWithoutDB, err := dbWithoutDB.DB()
180
+			if err != nil {
181
+				fmt.Printf("获取底层 *sql.DB 对象失败: %v\n", err)
182
+				return
183
+			}
184
+			defer func() {
185
+				if err := sqlDBWithoutDB.Close(); err != nil {
186
+					fmt.Printf("关闭连接失败: %v\n", err)
187
+				}
188
+			}()
189
+
190
+			err = dbWithoutDB.Exec("CREATE DATABASE IF NOT EXISTS " + dbName + " DEFAULT CHARSET utf8mb4").Error
191
+			if err != nil {
192
+				fmt.Println(err)
193
+				return
194
+			}
195
+		}
196
+	}
197
+
198
+	if !db.Migrator().HasTable(&model.Version{}) {
199
+		Migrate(uint(version))
200
+	} else {
201
+		//查找最后一个version
202
+		var v model.Version
203
+		db.Last(&v)
204
+		if v.Version < uint(version) {
205
+			Migrate(uint(version))
206
+		}
207
+	}
208
+
209
+}
210
+func Migrate(version uint) {
211
+	fmt.Println("migrating....", version)
212
+	err := global.DB.AutoMigrate(
213
+		&model.Version{},
214
+		&model.User{},
215
+		&model.UserToken{},
216
+		&model.Tag{},
217
+		&model.AddressBook{},
218
+		&model.Peer{},
219
+		&model.Group{},
220
+	)
221
+	if err != nil {
222
+		fmt.Println("migrate err :=>", err)
223
+	}
224
+	global.DB.Create(&model.Version{Version: version})
225
+	//如果是初次则创建一个默认用户
226
+	var vc int64
227
+	global.DB.Model(&model.Version{}).Count(&vc)
228
+	if vc == 1 {
229
+		group := &model.Group{
230
+			Name: "默认组",
231
+			Type: model.GroupTypeDefault,
232
+		}
233
+		service.AllService.GroupService.Create(group)
234
+		groupShare := &model.Group{
235
+			Name: "共享组",
236
+			Type: model.GroupTypeShare,
237
+		}
238
+		service.AllService.GroupService.Create(groupShare)
239
+		//是true
240
+		is_admin := true
241
+		admin := &model.User{
242
+			Username: "admin",
243
+			Nickname: "管理员",
244
+			Status:   model.COMMON_STATUS_ENABLE,
245
+			IsAdmin:  &is_admin,
246
+			GroupId:  1,
247
+		}
248
+		admin.Password = service.AllService.UserService.EncryptPassword("admin")
249
+		global.DB.Create(admin)
250
+	}
251
+
252
+}

+ 42 - 0
conf/config.yaml

@@ -0,0 +1,42 @@
1
+gin:
2
+  api-addr: "0.0.0.0:21114"
3
+  mode: "release" #release,debug,test
4
+  resources-path: 'resources'  #对外静态文件目录
5
+gorm:
6
+  type: "sqlite"
7
+  max-idle-conns: 10
8
+  max-open-conns: 100
9
+mysql:
10
+  username: "root"
11
+  password: "111111"
12
+  addr: "192.168.1.66:3308"
13
+  dbname: "rustdesk2"
14
+rustdesk:
15
+  id-server: "124.220.134.240:21116"
16
+  relay-server: "124.220.134.240:21117"
17
+  api-server: "http://127.0.0.1:21114"
18
+  key: "ljw19891989"
19
+redis:
20
+  addr: "127.0.0.1:6379"
21
+  password: ""
22
+  db: 0
23
+logger:
24
+  path: "./runtime/log.txt"
25
+  level: "error" #trace,debug,info,warn,error,fatal
26
+  report-caller: true
27
+cache:
28
+  type: "file"
29
+  file-dir: "./runtime/cache"
30
+  redis-addr: "127.0.0.1:6379"
31
+  redis-pwd: "ljw19891989"
32
+  redis-db: 0
33
+oss:
34
+  access-key-id: ""
35
+  access-key-secret: ""
36
+  host: ""
37
+  callback-url: ""
38
+  expire-time: 30
39
+  max-byte: 10240
40
+jwt:
41
+  private-key: "./conf/jwt_pri.pem"
42
+  expire-duration: 360000

+ 0 - 0
conf/jwt_pri.pem


+ 9 - 0
config/cache.go

@@ -0,0 +1,9 @@
1
+package config
2
+
3
+type Cache struct {
4
+	Type      string
5
+	RedisAddr string `mapstructure:"redis-addr"`
6
+	RedisPwd  string `mapstructure:"redis-pwd"`
7
+	RedisDb   int    `mapstructure:"redis-db"`
8
+	FileDir   string `mapstructure:"file-dir"`
9
+}

+ 56 - 0
config/config.go

@@ -0,0 +1,56 @@
1
+package config
2
+
3
+import (
4
+	"flag"
5
+	"fmt"
6
+	"github.com/fsnotify/fsnotify"
7
+	"github.com/spf13/viper"
8
+)
9
+
10
+const (
11
+	DebugMode     = "debug"
12
+	ReleaseMode   = "release"
13
+	DefaultConfig = "conf/config.yaml"
14
+)
15
+
16
+type Config struct {
17
+	Gorm     Gorm
18
+	Mysql    Mysql
19
+	Gin      Gin
20
+	Logger   Logger
21
+	Redis    Redis
22
+	Cache    Cache
23
+	Oss      Oss
24
+	Jwt      Jwt
25
+	Rustdesk Rustdesk
26
+}
27
+
28
+// Init 初始化配置
29
+func Init(rowVal interface{}, cb func()) *viper.Viper {
30
+	var config string
31
+	flag.StringVar(&config, "c", "", "choose config file.")
32
+	flag.Parse()
33
+	if config == "" { // 优先级: 命令行 > 默认值
34
+		config = DefaultConfig
35
+	}
36
+	v := viper.New()
37
+	v.SetConfigFile(config)
38
+	v.SetConfigType("yaml")
39
+	err := v.ReadInConfig()
40
+	if err != nil {
41
+		panic(fmt.Errorf("Fatal error config file: %s \n", err))
42
+	}
43
+	v.WatchConfig()
44
+	v.OnConfigChange(func(e fsnotify.Event) {
45
+		//配置文件修改监听
46
+		fmt.Println("config file changed:", e.Name)
47
+		if err2 := v.Unmarshal(rowVal); err2 != nil {
48
+			fmt.Println(err2)
49
+		}
50
+		cb()
51
+	})
52
+	if err := v.Unmarshal(rowVal); err != nil {
53
+		fmt.Println(err)
54
+	}
55
+	return v
56
+}

+ 8 - 0
config/gin.go

@@ -0,0 +1,8 @@
1
+package config
2
+
3
+type Gin struct {
4
+	ApiAddr       string `mapstructure:"api-addr"`
5
+	AdminAddr     string `mapstructure:"admin-addr"`
6
+	Mode          string
7
+	ResourcesPath string `mapstructure:"resources-path"`
8
+}

+ 19 - 0
config/gorm.go

@@ -0,0 +1,19 @@
1
+package config
2
+
3
+const (
4
+	TypeSqlite = "sqlite"
5
+	TypeMysql  = "mysql"
6
+)
7
+
8
+type Gorm struct {
9
+	Type         string `mapstructure:"type"`
10
+	MaxIdleConns int    `mapstructure:"max-idle-conns"`
11
+	MaxOpenConns int    `mapstructure:"max-open-conns"`
12
+}
13
+
14
+type Mysql struct {
15
+	Addr     string `mapstructure:"addr"`
16
+	Username string `mapstructure:"username"`
17
+	Password string `mapstructure:"password"`
18
+	Dbname   string `mapstructure:"dbname"`
19
+}

+ 8 - 0
config/jwt.go

@@ -0,0 +1,8 @@
1
+package config
2
+
3
+import "time"
4
+
5
+type Jwt struct {
6
+	PrivateKey     string        `mapstructure:"private-key"`
7
+	ExpireDuration time.Duration `mapstructure:"expire-duration"`
8
+}

+ 7 - 0
config/logger.go

@@ -0,0 +1,7 @@
1
+package config
2
+
3
+type Logger struct {
4
+	Path         string
5
+	Level        string
6
+	ReportCaller bool `mapstructure:"report-caller"`
7
+}

+ 10 - 0
config/oss.go

@@ -0,0 +1,10 @@
1
+package config
2
+
3
+type Oss struct {
4
+	AccessKeyId     string `mapstructure:"access-key-id"`
5
+	AccessKeySecret string `mapstructure:"access-key-secret"`
6
+	Host            string `mapstructure:"host"`
7
+	CallbackUrl     string `mapstructure:"callback-url"`
8
+	ExpireTime      int64  `mapstructure:"expire-time"`
9
+	MaxByte         int64  `mapstructure:"max-byte"`
10
+}

+ 7 - 0
config/redis.go

@@ -0,0 +1,7 @@
1
+package config
2
+
3
+type Redis struct {
4
+	Addr     string
5
+	Password string
6
+	Db       int
7
+}

+ 8 - 0
config/rustdesk.go

@@ -0,0 +1,8 @@
1
+package config
2
+
3
+type Rustdesk struct {
4
+	IdServer    string `mapstructure:"id-server"`
5
+	RelayServer string `mapstructure:"relay-server"`
6
+	ApiServer   string `mapstructure:"api-server"`
7
+	Key         string `mapstructure:"key"`
8
+}

Разница между файлами не показана из-за своего большого размера
+ 2331 - 0
docs/admin/admin_docs.go


Разница между файлами не показана из-за своего большого размера
+ 2306 - 0
docs/admin/admin_swagger.json


Разница между файлами не показана из-за своего большого размера
+ 1411 - 0
docs/admin/admin_swagger.yaml


+ 830 - 0
docs/api/api_docs.go

@@ -0,0 +1,830 @@
1
+// Package api Code generated by swaggo/swag. DO NOT EDIT
2
+package api
3
+
4
+import "github.com/swaggo/swag"
5
+
6
+const docTemplateapi = `{
7
+    "schemes": {{ marshal .Schemes }},
8
+    "swagger": "2.0",
9
+    "info": {
10
+        "description": "{{escape .Description}}",
11
+        "title": "{{.Title}}",
12
+        "contact": {},
13
+        "version": "{{.Version}}"
14
+    },
15
+    "host": "{{.Host}}",
16
+    "basePath": "{{.BasePath}}",
17
+    "paths": {
18
+        "/": {
19
+            "get": {
20
+                "description": "首页",
21
+                "consumes": [
22
+                    "application/json"
23
+                ],
24
+                "produces": [
25
+                    "application/json"
26
+                ],
27
+                "tags": [
28
+                    "首页"
29
+                ],
30
+                "summary": "首页",
31
+                "responses": {
32
+                    "200": {
33
+                        "description": "OK",
34
+                        "schema": {
35
+                            "$ref": "#/definitions/response.Response"
36
+                        }
37
+                    },
38
+                    "500": {
39
+                        "description": "Internal Server Error",
40
+                        "schema": {
41
+                            "$ref": "#/definitions/response.Response"
42
+                        }
43
+                    }
44
+                }
45
+            }
46
+        },
47
+        "/ab": {
48
+            "get": {
49
+                "security": [
50
+                    {
51
+                        "BearerAuth": []
52
+                    }
53
+                ],
54
+                "description": "地址列表",
55
+                "consumes": [
56
+                    "application/json"
57
+                ],
58
+                "produces": [
59
+                    "application/json"
60
+                ],
61
+                "tags": [
62
+                    "地址"
63
+                ],
64
+                "summary": "地址列表",
65
+                "responses": {
66
+                    "200": {
67
+                        "description": "OK",
68
+                        "schema": {
69
+                            "$ref": "#/definitions/response.Response"
70
+                        }
71
+                    },
72
+                    "500": {
73
+                        "description": "Internal Server Error",
74
+                        "schema": {
75
+                            "$ref": "#/definitions/response.ErrorResponse"
76
+                        }
77
+                    }
78
+                }
79
+            },
80
+            "post": {
81
+                "security": [
82
+                    {
83
+                        "BearerAuth": []
84
+                    }
85
+                ],
86
+                "description": "地址更新",
87
+                "consumes": [
88
+                    "application/json"
89
+                ],
90
+                "produces": [
91
+                    "application/json"
92
+                ],
93
+                "tags": [
94
+                    "地址"
95
+                ],
96
+                "summary": "地址更新",
97
+                "parameters": [
98
+                    {
99
+                        "description": "地址表单",
100
+                        "name": "body",
101
+                        "in": "body",
102
+                        "required": true,
103
+                        "schema": {
104
+                            "$ref": "#/definitions/api.AddressBookForm"
105
+                        }
106
+                    }
107
+                ],
108
+                "responses": {
109
+                    "200": {
110
+                        "description": "null",
111
+                        "schema": {
112
+                            "type": "string"
113
+                        }
114
+                    },
115
+                    "500": {
116
+                        "description": "Internal Server Error",
117
+                        "schema": {
118
+                            "$ref": "#/definitions/response.ErrorResponse"
119
+                        }
120
+                    }
121
+                }
122
+            }
123
+        },
124
+        "/ab/add": {
125
+            "post": {
126
+                "security": [
127
+                    {
128
+                        "BearerAuth": []
129
+                    }
130
+                ],
131
+                "description": "标签",
132
+                "consumes": [
133
+                    "application/json"
134
+                ],
135
+                "produces": [
136
+                    "application/json"
137
+                ],
138
+                "tags": [
139
+                    "地址"
140
+                ],
141
+                "summary": "标签添加",
142
+                "responses": {
143
+                    "200": {
144
+                        "description": "OK",
145
+                        "schema": {
146
+                            "type": "string"
147
+                        }
148
+                    },
149
+                    "500": {
150
+                        "description": "Internal Server Error",
151
+                        "schema": {
152
+                            "$ref": "#/definitions/response.ErrorResponse"
153
+                        }
154
+                    }
155
+                }
156
+            }
157
+        },
158
+        "/ab/personal": {
159
+            "post": {
160
+                "security": [
161
+                    {
162
+                        "BearerAuth": []
163
+                    }
164
+                ],
165
+                "description": "个人信息",
166
+                "consumes": [
167
+                    "application/json"
168
+                ],
169
+                "produces": [
170
+                    "application/json"
171
+                ],
172
+                "tags": [
173
+                    "用户"
174
+                ],
175
+                "summary": "个人信息",
176
+                "parameters": [
177
+                    {
178
+                        "description": "string valid",
179
+                        "name": "string",
180
+                        "in": "body",
181
+                        "schema": {
182
+                            "type": "string"
183
+                        }
184
+                    }
185
+                ],
186
+                "responses": {
187
+                    "200": {
188
+                        "description": "OK",
189
+                        "schema": {
190
+                            "$ref": "#/definitions/response.Response"
191
+                        }
192
+                    },
193
+                    "500": {
194
+                        "description": "Internal Server Error",
195
+                        "schema": {
196
+                            "$ref": "#/definitions/response.Response"
197
+                        }
198
+                    }
199
+                }
200
+            }
201
+        },
202
+        "/api": {
203
+            "get": {
204
+                "security": [
205
+                    {
206
+                        "token": []
207
+                    }
208
+                ],
209
+                "description": "用户信息",
210
+                "consumes": [
211
+                    "application/json"
212
+                ],
213
+                "produces": [
214
+                    "application/json"
215
+                ],
216
+                "tags": [
217
+                    "用户"
218
+                ],
219
+                "summary": "用户信息",
220
+                "responses": {
221
+                    "200": {
222
+                        "description": "OK",
223
+                        "schema": {
224
+                            "$ref": "#/definitions/api.UserPayload"
225
+                        }
226
+                    },
227
+                    "500": {
228
+                        "description": "Internal Server Error",
229
+                        "schema": {
230
+                            "$ref": "#/definitions/response.Response"
231
+                        }
232
+                    }
233
+                }
234
+            }
235
+        },
236
+        "/currentUser": {
237
+            "get": {
238
+                "security": [
239
+                    {
240
+                        "token": []
241
+                    }
242
+                ],
243
+                "description": "用户信息",
244
+                "consumes": [
245
+                    "application/json"
246
+                ],
247
+                "produces": [
248
+                    "application/json"
249
+                ],
250
+                "tags": [
251
+                    "用户"
252
+                ],
253
+                "summary": "用户信息",
254
+                "responses": {
255
+                    "200": {
256
+                        "description": "OK",
257
+                        "schema": {
258
+                            "$ref": "#/definitions/api.UserPayload"
259
+                        }
260
+                    },
261
+                    "500": {
262
+                        "description": "Internal Server Error",
263
+                        "schema": {
264
+                            "$ref": "#/definitions/response.Response"
265
+                        }
266
+                    }
267
+                }
268
+            }
269
+        },
270
+        "/heartbeat": {
271
+            "post": {
272
+                "description": "心跳",
273
+                "consumes": [
274
+                    "application/json"
275
+                ],
276
+                "produces": [
277
+                    "application/json"
278
+                ],
279
+                "tags": [
280
+                    "首页"
281
+                ],
282
+                "summary": "心跳",
283
+                "responses": {
284
+                    "200": {
285
+                        "description": "OK"
286
+                    },
287
+                    "500": {
288
+                        "description": "Internal Server Error",
289
+                        "schema": {
290
+                            "$ref": "#/definitions/response.Response"
291
+                        }
292
+                    }
293
+                }
294
+            }
295
+        },
296
+        "/login": {
297
+            "post": {
298
+                "description": "登录",
299
+                "consumes": [
300
+                    "application/json"
301
+                ],
302
+                "produces": [
303
+                    "application/json"
304
+                ],
305
+                "tags": [
306
+                    "登录"
307
+                ],
308
+                "summary": "登录",
309
+                "parameters": [
310
+                    {
311
+                        "description": "登录表单",
312
+                        "name": "body",
313
+                        "in": "body",
314
+                        "required": true,
315
+                        "schema": {
316
+                            "$ref": "#/definitions/api.LoginForm"
317
+                        }
318
+                    }
319
+                ],
320
+                "responses": {
321
+                    "200": {
322
+                        "description": "OK",
323
+                        "schema": {
324
+                            "$ref": "#/definitions/api.LoginRes"
325
+                        }
326
+                    },
327
+                    "500": {
328
+                        "description": "Internal Server Error",
329
+                        "schema": {
330
+                            "$ref": "#/definitions/response.ErrorResponse"
331
+                        }
332
+                    }
333
+                }
334
+            }
335
+        },
336
+        "/login-options": {
337
+            "post": {
338
+                "description": "登录选项",
339
+                "consumes": [
340
+                    "application/json"
341
+                ],
342
+                "produces": [
343
+                    "application/json"
344
+                ],
345
+                "tags": [
346
+                    "登录"
347
+                ],
348
+                "summary": "登录选项",
349
+                "responses": {
350
+                    "200": {
351
+                        "description": "OK",
352
+                        "schema": {
353
+                            "type": "array",
354
+                            "items": {
355
+                                "type": "string"
356
+                            }
357
+                        }
358
+                    },
359
+                    "500": {
360
+                        "description": "Internal Server Error",
361
+                        "schema": {
362
+                            "$ref": "#/definitions/response.ErrorResponse"
363
+                        }
364
+                    }
365
+                }
366
+            }
367
+        },
368
+        "/logout": {
369
+            "post": {
370
+                "description": "登出",
371
+                "consumes": [
372
+                    "application/json"
373
+                ],
374
+                "produces": [
375
+                    "application/json"
376
+                ],
377
+                "tags": [
378
+                    "登录"
379
+                ],
380
+                "summary": "登出",
381
+                "responses": {
382
+                    "200": {
383
+                        "description": "OK",
384
+                        "schema": {
385
+                            "type": "string"
386
+                        }
387
+                    },
388
+                    "500": {
389
+                        "description": "Internal Server Error",
390
+                        "schema": {
391
+                            "$ref": "#/definitions/response.ErrorResponse"
392
+                        }
393
+                    }
394
+                }
395
+            }
396
+        },
397
+        "/peers": {
398
+            "get": {
399
+                "security": [
400
+                    {
401
+                        "BearerAuth": []
402
+                    }
403
+                ],
404
+                "description": "机器",
405
+                "consumes": [
406
+                    "application/json"
407
+                ],
408
+                "produces": [
409
+                    "application/json"
410
+                ],
411
+                "tags": [
412
+                    "群组"
413
+                ],
414
+                "summary": "机器",
415
+                "parameters": [
416
+                    {
417
+                        "type": "integer",
418
+                        "description": "页码",
419
+                        "name": "page",
420
+                        "in": "query"
421
+                    },
422
+                    {
423
+                        "type": "integer",
424
+                        "description": "每页数量",
425
+                        "name": "pageSize",
426
+                        "in": "query"
427
+                    },
428
+                    {
429
+                        "type": "integer",
430
+                        "description": "状态",
431
+                        "name": "status",
432
+                        "in": "query"
433
+                    },
434
+                    {
435
+                        "type": "string",
436
+                        "description": "accessible",
437
+                        "name": "accessible",
438
+                        "in": "query"
439
+                    }
440
+                ],
441
+                "responses": {
442
+                    "200": {
443
+                        "description": "OK",
444
+                        "schema": {
445
+                            "$ref": "#/definitions/response.DataResponse"
446
+                        }
447
+                    },
448
+                    "500": {
449
+                        "description": "Internal Server Error",
450
+                        "schema": {
451
+                            "$ref": "#/definitions/response.Response"
452
+                        }
453
+                    }
454
+                }
455
+            }
456
+        },
457
+        "/server-config": {
458
+            "get": {
459
+                "security": [
460
+                    {
461
+                        "token": []
462
+                    }
463
+                ],
464
+                "description": "服务配置,给webclient提供api-server",
465
+                "consumes": [
466
+                    "application/json"
467
+                ],
468
+                "produces": [
469
+                    "application/json"
470
+                ],
471
+                "tags": [
472
+                    "WEBCLIENT"
473
+                ],
474
+                "summary": "服务配置",
475
+                "responses": {
476
+                    "200": {
477
+                        "description": "OK",
478
+                        "schema": {
479
+                            "$ref": "#/definitions/response.Response"
480
+                        }
481
+                    },
482
+                    "500": {
483
+                        "description": "Internal Server Error",
484
+                        "schema": {
485
+                            "$ref": "#/definitions/response.Response"
486
+                        }
487
+                    }
488
+                }
489
+            }
490
+        },
491
+        "/sysinfo": {
492
+            "post": {
493
+                "security": [
494
+                    {
495
+                        "BearerAuth": []
496
+                    }
497
+                ],
498
+                "description": "提交系统信息",
499
+                "consumes": [
500
+                    "application/json"
501
+                ],
502
+                "produces": [
503
+                    "application/json"
504
+                ],
505
+                "tags": [
506
+                    "地址"
507
+                ],
508
+                "summary": "提交系统信息",
509
+                "parameters": [
510
+                    {
511
+                        "description": "系统信息表单",
512
+                        "name": "body",
513
+                        "in": "body",
514
+                        "required": true,
515
+                        "schema": {
516
+                            "$ref": "#/definitions/api.PeerForm"
517
+                        }
518
+                    }
519
+                ],
520
+                "responses": {
521
+                    "200": {
522
+                        "description": "SYSINFO_UPDATED,ID_NOT_FOUND",
523
+                        "schema": {
524
+                            "type": "string"
525
+                        }
526
+                    },
527
+                    "500": {
528
+                        "description": "Internal Server Error",
529
+                        "schema": {
530
+                            "$ref": "#/definitions/response.ErrorResponse"
531
+                        }
532
+                    }
533
+                }
534
+            }
535
+        },
536
+        "/tags": {
537
+            "post": {
538
+                "security": [
539
+                    {
540
+                        "BearerAuth": []
541
+                    }
542
+                ],
543
+                "description": "标签",
544
+                "consumes": [
545
+                    "application/json"
546
+                ],
547
+                "produces": [
548
+                    "application/json"
549
+                ],
550
+                "tags": [
551
+                    "地址"
552
+                ],
553
+                "summary": "标签",
554
+                "responses": {
555
+                    "200": {
556
+                        "description": "OK",
557
+                        "schema": {
558
+                            "type": "array",
559
+                            "items": {
560
+                                "$ref": "#/definitions/model.Tag"
561
+                            }
562
+                        }
563
+                    },
564
+                    "500": {
565
+                        "description": "Internal Server Error",
566
+                        "schema": {
567
+                            "$ref": "#/definitions/response.ErrorResponse"
568
+                        }
569
+                    }
570
+                }
571
+            }
572
+        },
573
+        "/users": {
574
+            "get": {
575
+                "security": [
576
+                    {
577
+                        "BearerAuth": []
578
+                    }
579
+                ],
580
+                "description": "用户列表",
581
+                "consumes": [
582
+                    "application/json"
583
+                ],
584
+                "produces": [
585
+                    "application/json"
586
+                ],
587
+                "tags": [
588
+                    "群组"
589
+                ],
590
+                "summary": "用户列表",
591
+                "parameters": [
592
+                    {
593
+                        "type": "integer",
594
+                        "description": "页码",
595
+                        "name": "page",
596
+                        "in": "query"
597
+                    },
598
+                    {
599
+                        "type": "integer",
600
+                        "description": "每页数量",
601
+                        "name": "pageSize",
602
+                        "in": "query"
603
+                    },
604
+                    {
605
+                        "type": "integer",
606
+                        "description": "状态",
607
+                        "name": "status",
608
+                        "in": "query"
609
+                    },
610
+                    {
611
+                        "type": "string",
612
+                        "description": "accessible",
613
+                        "name": "accessible",
614
+                        "in": "query"
615
+                    }
616
+                ],
617
+                "responses": {
618
+                    "200": {
619
+                        "description": "OK",
620
+                        "schema": {
621
+                            "allOf": [
622
+                                {
623
+                                    "$ref": "#/definitions/response.DataResponse"
624
+                                },
625
+                                {
626
+                                    "type": "object",
627
+                                    "properties": {
628
+                                        "data": {
629
+                                            "type": "array",
630
+                                            "items": {
631
+                                                "$ref": "#/definitions/api.UserPayload"
632
+                                            }
633
+                                        }
634
+                                    }
635
+                                }
636
+                            ]
637
+                        }
638
+                    },
639
+                    "500": {
640
+                        "description": "Internal Server Error",
641
+                        "schema": {
642
+                            "$ref": "#/definitions/response.ErrorResponse"
643
+                        }
644
+                    }
645
+                }
646
+            }
647
+        }
648
+    },
649
+    "definitions": {
650
+        "api.AddressBookForm": {
651
+            "type": "object",
652
+            "properties": {
653
+                "data": {
654
+                    "type": "string",
655
+                    "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}\"}"
656
+                }
657
+            }
658
+        },
659
+        "api.LoginForm": {
660
+            "type": "object",
661
+            "required": [
662
+                "username"
663
+            ],
664
+            "properties": {
665
+                "password": {
666
+                    "type": "string",
667
+                    "maxLength": 20,
668
+                    "minLength": 4
669
+                },
670
+                "username": {
671
+                    "type": "string",
672
+                    "maxLength": 10,
673
+                    "minLength": 4
674
+                }
675
+            }
676
+        },
677
+        "api.LoginRes": {
678
+            "type": "object",
679
+            "properties": {
680
+                "access_token": {
681
+                    "type": "string"
682
+                },
683
+                "secret": {
684
+                    "type": "string"
685
+                },
686
+                "tfa_type": {
687
+                    "type": "string"
688
+                },
689
+                "type": {
690
+                    "type": "string"
691
+                },
692
+                "user": {
693
+                    "$ref": "#/definitions/api.UserPayload"
694
+                }
695
+            }
696
+        },
697
+        "api.PeerForm": {
698
+            "type": "object",
699
+            "properties": {
700
+                "cpu": {
701
+                    "type": "string"
702
+                },
703
+                "hostname": {
704
+                    "type": "string"
705
+                },
706
+                "id": {
707
+                    "type": "string"
708
+                },
709
+                "memory": {
710
+                    "type": "string"
711
+                },
712
+                "os": {
713
+                    "type": "string"
714
+                },
715
+                "username": {
716
+                    "type": "string"
717
+                },
718
+                "uuid": {
719
+                    "type": "string"
720
+                },
721
+                "version": {
722
+                    "type": "string"
723
+                }
724
+            }
725
+        },
726
+        "api.UserPayload": {
727
+            "type": "object",
728
+            "properties": {
729
+                "email": {
730
+                    "type": "string"
731
+                },
732
+                "is_admin": {
733
+                    "type": "boolean"
734
+                },
735
+                "name": {
736
+                    "type": "string"
737
+                },
738
+                "note": {
739
+                    "type": "string"
740
+                },
741
+                "status": {
742
+                    "type": "integer"
743
+                }
744
+            }
745
+        },
746
+        "model.Tag": {
747
+            "type": "object",
748
+            "properties": {
749
+                "color": {
750
+                    "description": "color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度,后面6位表示颜色, 可以转成rgba",
751
+                    "type": "integer"
752
+                },
753
+                "created_at": {
754
+                    "type": "string"
755
+                },
756
+                "id": {
757
+                    "type": "integer"
758
+                },
759
+                "name": {
760
+                    "type": "string"
761
+                },
762
+                "updated_at": {
763
+                    "type": "string"
764
+                },
765
+                "user_id": {
766
+                    "type": "integer"
767
+                }
768
+            }
769
+        },
770
+        "response.DataResponse": {
771
+            "type": "object",
772
+            "properties": {
773
+                "data": {},
774
+                "total": {
775
+                    "type": "integer"
776
+                }
777
+            }
778
+        },
779
+        "response.ErrorResponse": {
780
+            "type": "object",
781
+            "properties": {
782
+                "error": {
783
+                    "type": "string"
784
+                }
785
+            }
786
+        },
787
+        "response.Response": {
788
+            "type": "object",
789
+            "properties": {
790
+                "code": {
791
+                    "type": "integer"
792
+                },
793
+                "data": {},
794
+                "message": {
795
+                    "type": "string"
796
+                }
797
+            }
798
+        }
799
+    },
800
+    "securityDefinitions": {
801
+        "BearerAuth": {
802
+            "type": "apiKey",
803
+            "name": "Authorization",
804
+            "in": "header"
805
+        },
806
+        "token": {
807
+            "type": "apiKey",
808
+            "name": "api-token",
809
+            "in": "header"
810
+        }
811
+    }
812
+}`
813
+
814
+// SwaggerInfoapi holds exported Swagger Info so clients can modify it
815
+var SwaggerInfoapi = &swag.Spec{
816
+	Version:          "1.0",
817
+	Host:             "",
818
+	BasePath:         "/api",
819
+	Schemes:          []string{},
820
+	Title:            "管理系统API",
821
+	Description:      "接口",
822
+	InfoInstanceName: "api",
823
+	SwaggerTemplate:  docTemplateapi,
824
+	LeftDelim:        "{{",
825
+	RightDelim:       "}}",
826
+}
827
+
828
+func init() {
829
+	swag.Register(SwaggerInfoapi.InstanceName(), SwaggerInfoapi)
830
+}

+ 805 - 0
docs/api/api_swagger.json

@@ -0,0 +1,805 @@
1
+{
2
+    "swagger": "2.0",
3
+    "info": {
4
+        "description": "接口",
5
+        "title": "管理系统API",
6
+        "contact": {},
7
+        "version": "1.0"
8
+    },
9
+    "basePath": "/api",
10
+    "paths": {
11
+        "/": {
12
+            "get": {
13
+                "description": "首页",
14
+                "consumes": [
15
+                    "application/json"
16
+                ],
17
+                "produces": [
18
+                    "application/json"
19
+                ],
20
+                "tags": [
21
+                    "首页"
22
+                ],
23
+                "summary": "首页",
24
+                "responses": {
25
+                    "200": {
26
+                        "description": "OK",
27
+                        "schema": {
28
+                            "$ref": "#/definitions/response.Response"
29
+                        }
30
+                    },
31
+                    "500": {
32
+                        "description": "Internal Server Error",
33
+                        "schema": {
34
+                            "$ref": "#/definitions/response.Response"
35
+                        }
36
+                    }
37
+                }
38
+            }
39
+        },
40
+        "/ab": {
41
+            "get": {
42
+                "security": [
43
+                    {
44
+                        "BearerAuth": []
45
+                    }
46
+                ],
47
+                "description": "地址列表",
48
+                "consumes": [
49
+                    "application/json"
50
+                ],
51
+                "produces": [
52
+                    "application/json"
53
+                ],
54
+                "tags": [
55
+                    "地址"
56
+                ],
57
+                "summary": "地址列表",
58
+                "responses": {
59
+                    "200": {
60
+                        "description": "OK",
61
+                        "schema": {
62
+                            "$ref": "#/definitions/response.Response"
63
+                        }
64
+                    },
65
+                    "500": {
66
+                        "description": "Internal Server Error",
67
+                        "schema": {
68
+                            "$ref": "#/definitions/response.ErrorResponse"
69
+                        }
70
+                    }
71
+                }
72
+            },
73
+            "post": {
74
+                "security": [
75
+                    {
76
+                        "BearerAuth": []
77
+                    }
78
+                ],
79
+                "description": "地址更新",
80
+                "consumes": [
81
+                    "application/json"
82
+                ],
83
+                "produces": [
84
+                    "application/json"
85
+                ],
86
+                "tags": [
87
+                    "地址"
88
+                ],
89
+                "summary": "地址更新",
90
+                "parameters": [
91
+                    {
92
+                        "description": "地址表单",
93
+                        "name": "body",
94
+                        "in": "body",
95
+                        "required": true,
96
+                        "schema": {
97
+                            "$ref": "#/definitions/api.AddressBookForm"
98
+                        }
99
+                    }
100
+                ],
101
+                "responses": {
102
+                    "200": {
103
+                        "description": "null",
104
+                        "schema": {
105
+                            "type": "string"
106
+                        }
107
+                    },
108
+                    "500": {
109
+                        "description": "Internal Server Error",
110
+                        "schema": {
111
+                            "$ref": "#/definitions/response.ErrorResponse"
112
+                        }
113
+                    }
114
+                }
115
+            }
116
+        },
117
+        "/ab/add": {
118
+            "post": {
119
+                "security": [
120
+                    {
121
+                        "BearerAuth": []
122
+                    }
123
+                ],
124
+                "description": "标签",
125
+                "consumes": [
126
+                    "application/json"
127
+                ],
128
+                "produces": [
129
+                    "application/json"
130
+                ],
131
+                "tags": [
132
+                    "地址"
133
+                ],
134
+                "summary": "标签添加",
135
+                "responses": {
136
+                    "200": {
137
+                        "description": "OK",
138
+                        "schema": {
139
+                            "type": "string"
140
+                        }
141
+                    },
142
+                    "500": {
143
+                        "description": "Internal Server Error",
144
+                        "schema": {
145
+                            "$ref": "#/definitions/response.ErrorResponse"
146
+                        }
147
+                    }
148
+                }
149
+            }
150
+        },
151
+        "/ab/personal": {
152
+            "post": {
153
+                "security": [
154
+                    {
155
+                        "BearerAuth": []
156
+                    }
157
+                ],
158
+                "description": "个人信息",
159
+                "consumes": [
160
+                    "application/json"
161
+                ],
162
+                "produces": [
163
+                    "application/json"
164
+                ],
165
+                "tags": [
166
+                    "用户"
167
+                ],
168
+                "summary": "个人信息",
169
+                "parameters": [
170
+                    {
171
+                        "description": "string valid",
172
+                        "name": "string",
173
+                        "in": "body",
174
+                        "schema": {
175
+                            "type": "string"
176
+                        }
177
+                    }
178
+                ],
179
+                "responses": {
180
+                    "200": {
181
+                        "description": "OK",
182
+                        "schema": {
183
+                            "$ref": "#/definitions/response.Response"
184
+                        }
185
+                    },
186
+                    "500": {
187
+                        "description": "Internal Server Error",
188
+                        "schema": {
189
+                            "$ref": "#/definitions/response.Response"
190
+                        }
191
+                    }
192
+                }
193
+            }
194
+        },
195
+        "/api": {
196
+            "get": {
197
+                "security": [
198
+                    {
199
+                        "token": []
200
+                    }
201
+                ],
202
+                "description": "用户信息",
203
+                "consumes": [
204
+                    "application/json"
205
+                ],
206
+                "produces": [
207
+                    "application/json"
208
+                ],
209
+                "tags": [
210
+                    "用户"
211
+                ],
212
+                "summary": "用户信息",
213
+                "responses": {
214
+                    "200": {
215
+                        "description": "OK",
216
+                        "schema": {
217
+                            "$ref": "#/definitions/api.UserPayload"
218
+                        }
219
+                    },
220
+                    "500": {
221
+                        "description": "Internal Server Error",
222
+                        "schema": {
223
+                            "$ref": "#/definitions/response.Response"
224
+                        }
225
+                    }
226
+                }
227
+            }
228
+        },
229
+        "/currentUser": {
230
+            "get": {
231
+                "security": [
232
+                    {
233
+                        "token": []
234
+                    }
235
+                ],
236
+                "description": "用户信息",
237
+                "consumes": [
238
+                    "application/json"
239
+                ],
240
+                "produces": [
241
+                    "application/json"
242
+                ],
243
+                "tags": [
244
+                    "用户"
245
+                ],
246
+                "summary": "用户信息",
247
+                "responses": {
248
+                    "200": {
249
+                        "description": "OK",
250
+                        "schema": {
251
+                            "$ref": "#/definitions/api.UserPayload"
252
+                        }
253
+                    },
254
+                    "500": {
255
+                        "description": "Internal Server Error",
256
+                        "schema": {
257
+                            "$ref": "#/definitions/response.Response"
258
+                        }
259
+                    }
260
+                }
261
+            }
262
+        },
263
+        "/heartbeat": {
264
+            "post": {
265
+                "description": "心跳",
266
+                "consumes": [
267
+                    "application/json"
268
+                ],
269
+                "produces": [
270
+                    "application/json"
271
+                ],
272
+                "tags": [
273
+                    "首页"
274
+                ],
275
+                "summary": "心跳",
276
+                "responses": {
277
+                    "200": {
278
+                        "description": "OK"
279
+                    },
280
+                    "500": {
281
+                        "description": "Internal Server Error",
282
+                        "schema": {
283
+                            "$ref": "#/definitions/response.Response"
284
+                        }
285
+                    }
286
+                }
287
+            }
288
+        },
289
+        "/login": {
290
+            "post": {
291
+                "description": "登录",
292
+                "consumes": [
293
+                    "application/json"
294
+                ],
295
+                "produces": [
296
+                    "application/json"
297
+                ],
298
+                "tags": [
299
+                    "登录"
300
+                ],
301
+                "summary": "登录",
302
+                "parameters": [
303
+                    {
304
+                        "description": "登录表单",
305
+                        "name": "body",
306
+                        "in": "body",
307
+                        "required": true,
308
+                        "schema": {
309
+                            "$ref": "#/definitions/api.LoginForm"
310
+                        }
311
+                    }
312
+                ],
313
+                "responses": {
314
+                    "200": {
315
+                        "description": "OK",
316
+                        "schema": {
317
+                            "$ref": "#/definitions/api.LoginRes"
318
+                        }
319
+                    },
320
+                    "500": {
321
+                        "description": "Internal Server Error",
322
+                        "schema": {
323
+                            "$ref": "#/definitions/response.ErrorResponse"
324
+                        }
325
+                    }
326
+                }
327
+            }
328
+        },
329
+        "/login-options": {
330
+            "post": {
331
+                "description": "登录选项",
332
+                "consumes": [
333
+                    "application/json"
334
+                ],
335
+                "produces": [
336
+                    "application/json"
337
+                ],
338
+                "tags": [
339
+                    "登录"
340
+                ],
341
+                "summary": "登录选项",
342
+                "responses": {
343
+                    "200": {
344
+                        "description": "OK",
345
+                        "schema": {
346
+                            "type": "array",
347
+                            "items": {
348
+                                "type": "string"
349
+                            }
350
+                        }
351
+                    },
352
+                    "500": {
353
+                        "description": "Internal Server Error",
354
+                        "schema": {
355
+                            "$ref": "#/definitions/response.ErrorResponse"
356
+                        }
357
+                    }
358
+                }
359
+            }
360
+        },
361
+        "/logout": {
362
+            "post": {
363
+                "description": "登出",
364
+                "consumes": [
365
+                    "application/json"
366
+                ],
367
+                "produces": [
368
+                    "application/json"
369
+                ],
370
+                "tags": [
371
+                    "登录"
372
+                ],
373
+                "summary": "登出",
374
+                "responses": {
375
+                    "200": {
376
+                        "description": "OK",
377
+                        "schema": {
378
+                            "type": "string"
379
+                        }
380
+                    },
381
+                    "500": {
382
+                        "description": "Internal Server Error",
383
+                        "schema": {
384
+                            "$ref": "#/definitions/response.ErrorResponse"
385
+                        }
386
+                    }
387
+                }
388
+            }
389
+        },
390
+        "/peers": {
391
+            "get": {
392
+                "security": [
393
+                    {
394
+                        "BearerAuth": []
395
+                    }
396
+                ],
397
+                "description": "机器",
398
+                "consumes": [
399
+                    "application/json"
400
+                ],
401
+                "produces": [
402
+                    "application/json"
403
+                ],
404
+                "tags": [
405
+                    "群组"
406
+                ],
407
+                "summary": "机器",
408
+                "parameters": [
409
+                    {
410
+                        "type": "integer",
411
+                        "description": "页码",
412
+                        "name": "page",
413
+                        "in": "query"
414
+                    },
415
+                    {
416
+                        "type": "integer",
417
+                        "description": "每页数量",
418
+                        "name": "pageSize",
419
+                        "in": "query"
420
+                    },
421
+                    {
422
+                        "type": "integer",
423
+                        "description": "状态",
424
+                        "name": "status",
425
+                        "in": "query"
426
+                    },
427
+                    {
428
+                        "type": "string",
429
+                        "description": "accessible",
430
+                        "name": "accessible",
431
+                        "in": "query"
432
+                    }
433
+                ],
434
+                "responses": {
435
+                    "200": {
436
+                        "description": "OK",
437
+                        "schema": {
438
+                            "$ref": "#/definitions/response.DataResponse"
439
+                        }
440
+                    },
441
+                    "500": {
442
+                        "description": "Internal Server Error",
443
+                        "schema": {
444
+                            "$ref": "#/definitions/response.Response"
445
+                        }
446
+                    }
447
+                }
448
+            }
449
+        },
450
+        "/server-config": {
451
+            "get": {
452
+                "security": [
453
+                    {
454
+                        "token": []
455
+                    }
456
+                ],
457
+                "description": "服务配置,给webclient提供api-server",
458
+                "consumes": [
459
+                    "application/json"
460
+                ],
461
+                "produces": [
462
+                    "application/json"
463
+                ],
464
+                "tags": [
465
+                    "WEBCLIENT"
466
+                ],
467
+                "summary": "服务配置",
468
+                "responses": {
469
+                    "200": {
470
+                        "description": "OK",
471
+                        "schema": {
472
+                            "$ref": "#/definitions/response.Response"
473
+                        }
474
+                    },
475
+                    "500": {
476
+                        "description": "Internal Server Error",
477
+                        "schema": {
478
+                            "$ref": "#/definitions/response.Response"
479
+                        }
480
+                    }
481
+                }
482
+            }
483
+        },
484
+        "/sysinfo": {
485
+            "post": {
486
+                "security": [
487
+                    {
488
+                        "BearerAuth": []
489
+                    }
490
+                ],
491
+                "description": "提交系统信息",
492
+                "consumes": [
493
+                    "application/json"
494
+                ],
495
+                "produces": [
496
+                    "application/json"
497
+                ],
498
+                "tags": [
499
+                    "地址"
500
+                ],
501
+                "summary": "提交系统信息",
502
+                "parameters": [
503
+                    {
504
+                        "description": "系统信息表单",
505
+                        "name": "body",
506
+                        "in": "body",
507
+                        "required": true,
508
+                        "schema": {
509
+                            "$ref": "#/definitions/api.PeerForm"
510
+                        }
511
+                    }
512
+                ],
513
+                "responses": {
514
+                    "200": {
515
+                        "description": "SYSINFO_UPDATED,ID_NOT_FOUND",
516
+                        "schema": {
517
+                            "type": "string"
518
+                        }
519
+                    },
520
+                    "500": {
521
+                        "description": "Internal Server Error",
522
+                        "schema": {
523
+                            "$ref": "#/definitions/response.ErrorResponse"
524
+                        }
525
+                    }
526
+                }
527
+            }
528
+        },
529
+        "/tags": {
530
+            "post": {
531
+                "security": [
532
+                    {
533
+                        "BearerAuth": []
534
+                    }
535
+                ],
536
+                "description": "标签",
537
+                "consumes": [
538
+                    "application/json"
539
+                ],
540
+                "produces": [
541
+                    "application/json"
542
+                ],
543
+                "tags": [
544
+                    "地址"
545
+                ],
546
+                "summary": "标签",
547
+                "responses": {
548
+                    "200": {
549
+                        "description": "OK",
550
+                        "schema": {
551
+                            "type": "array",
552
+                            "items": {
553
+                                "$ref": "#/definitions/model.Tag"
554
+                            }
555
+                        }
556
+                    },
557
+                    "500": {
558
+                        "description": "Internal Server Error",
559
+                        "schema": {
560
+                            "$ref": "#/definitions/response.ErrorResponse"
561
+                        }
562
+                    }
563
+                }
564
+            }
565
+        },
566
+        "/users": {
567
+            "get": {
568
+                "security": [
569
+                    {
570
+                        "BearerAuth": []
571
+                    }
572
+                ],
573
+                "description": "用户列表",
574
+                "consumes": [
575
+                    "application/json"
576
+                ],
577
+                "produces": [
578
+                    "application/json"
579
+                ],
580
+                "tags": [
581
+                    "群组"
582
+                ],
583
+                "summary": "用户列表",
584
+                "parameters": [
585
+                    {
586
+                        "type": "integer",
587
+                        "description": "页码",
588
+                        "name": "page",
589
+                        "in": "query"
590
+                    },
591
+                    {
592
+                        "type": "integer",
593
+                        "description": "每页数量",
594
+                        "name": "pageSize",
595
+                        "in": "query"
596
+                    },
597
+                    {
598
+                        "type": "integer",
599
+                        "description": "状态",
600
+                        "name": "status",
601
+                        "in": "query"
602
+                    },
603
+                    {
604
+                        "type": "string",
605
+                        "description": "accessible",
606
+                        "name": "accessible",
607
+                        "in": "query"
608
+                    }
609
+                ],
610
+                "responses": {
611
+                    "200": {
612
+                        "description": "OK",
613
+                        "schema": {
614
+                            "allOf": [
615
+                                {
616
+                                    "$ref": "#/definitions/response.DataResponse"
617
+                                },
618
+                                {
619
+                                    "type": "object",
620
+                                    "properties": {
621
+                                        "data": {
622
+                                            "type": "array",
623
+                                            "items": {
624
+                                                "$ref": "#/definitions/api.UserPayload"
625
+                                            }
626
+                                        }
627
+                                    }
628
+                                }
629
+                            ]
630
+                        }
631
+                    },
632
+                    "500": {
633
+                        "description": "Internal Server Error",
634
+                        "schema": {
635
+                            "$ref": "#/definitions/response.ErrorResponse"
636
+                        }
637
+                    }
638
+                }
639
+            }
640
+        }
641
+    },
642
+    "definitions": {
643
+        "api.AddressBookForm": {
644
+            "type": "object",
645
+            "properties": {
646
+                "data": {
647
+                    "type": "string",
648
+                    "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}\"}"
649
+                }
650
+            }
651
+        },
652
+        "api.LoginForm": {
653
+            "type": "object",
654
+            "required": [
655
+                "username"
656
+            ],
657
+            "properties": {
658
+                "password": {
659
+                    "type": "string",
660
+                    "maxLength": 20,
661
+                    "minLength": 4
662
+                },
663
+                "username": {
664
+                    "type": "string",
665
+                    "maxLength": 10,
666
+                    "minLength": 4
667
+                }
668
+            }
669
+        },
670
+        "api.LoginRes": {
671
+            "type": "object",
672
+            "properties": {
673
+                "access_token": {
674
+                    "type": "string"
675
+                },
676
+                "secret": {
677
+                    "type": "string"
678
+                },
679
+                "tfa_type": {
680
+                    "type": "string"
681
+                },
682
+                "type": {
683
+                    "type": "string"
684
+                },
685
+                "user": {
686
+                    "$ref": "#/definitions/api.UserPayload"
687
+                }
688
+            }
689
+        },
690
+        "api.PeerForm": {
691
+            "type": "object",
692
+            "properties": {
693
+                "cpu": {
694
+                    "type": "string"
695
+                },
696
+                "hostname": {
697
+                    "type": "string"
698
+                },
699
+                "id": {
700
+                    "type": "string"
701
+                },
702
+                "memory": {
703
+                    "type": "string"
704
+                },
705
+                "os": {
706
+                    "type": "string"
707
+                },
708
+                "username": {
709
+                    "type": "string"
710
+                },
711
+                "uuid": {
712
+                    "type": "string"
713
+                },
714
+                "version": {
715
+                    "type": "string"
716
+                }
717
+            }
718
+        },
719
+        "api.UserPayload": {
720
+            "type": "object",
721
+            "properties": {
722
+                "email": {
723
+                    "type": "string"
724
+                },
725
+                "is_admin": {
726
+                    "type": "boolean"
727
+                },
728
+                "name": {
729
+                    "type": "string"
730
+                },
731
+                "note": {
732
+                    "type": "string"
733
+                },
734
+                "status": {
735
+                    "type": "integer"
736
+                }
737
+            }
738
+        },
739
+        "model.Tag": {
740
+            "type": "object",
741
+            "properties": {
742
+                "color": {
743
+                    "description": "color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度,后面6位表示颜色, 可以转成rgba",
744
+                    "type": "integer"
745
+                },
746
+                "created_at": {
747
+                    "type": "string"
748
+                },
749
+                "id": {
750
+                    "type": "integer"
751
+                },
752
+                "name": {
753
+                    "type": "string"
754
+                },
755
+                "updated_at": {
756
+                    "type": "string"
757
+                },
758
+                "user_id": {
759
+                    "type": "integer"
760
+                }
761
+            }
762
+        },
763
+        "response.DataResponse": {
764
+            "type": "object",
765
+            "properties": {
766
+                "data": {},
767
+                "total": {
768
+                    "type": "integer"
769
+                }
770
+            }
771
+        },
772
+        "response.ErrorResponse": {
773
+            "type": "object",
774
+            "properties": {
775
+                "error": {
776
+                    "type": "string"
777
+                }
778
+            }
779
+        },
780
+        "response.Response": {
781
+            "type": "object",
782
+            "properties": {
783
+                "code": {
784
+                    "type": "integer"
785
+                },
786
+                "data": {},
787
+                "message": {
788
+                    "type": "string"
789
+                }
790
+            }
791
+        }
792
+    },
793
+    "securityDefinitions": {
794
+        "BearerAuth": {
795
+            "type": "apiKey",
796
+            "name": "Authorization",
797
+            "in": "header"
798
+        },
799
+        "token": {
800
+            "type": "apiKey",
801
+            "name": "api-token",
802
+            "in": "header"
803
+        }
804
+    }
805
+}

+ 513 - 0
docs/api/api_swagger.yaml

@@ -0,0 +1,513 @@
1
+basePath: /api
2
+definitions:
3
+  api.AddressBookForm:
4
+    properties:
5
+      data:
6
+        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}"}'
7
+        type: string
8
+    type: object
9
+  api.LoginForm:
10
+    properties:
11
+      password:
12
+        maxLength: 20
13
+        minLength: 4
14
+        type: string
15
+      username:
16
+        maxLength: 10
17
+        minLength: 4
18
+        type: string
19
+    required:
20
+    - username
21
+    type: object
22
+  api.LoginRes:
23
+    properties:
24
+      access_token:
25
+        type: string
26
+      secret:
27
+        type: string
28
+      tfa_type:
29
+        type: string
30
+      type:
31
+        type: string
32
+      user:
33
+        $ref: '#/definitions/api.UserPayload'
34
+    type: object
35
+  api.PeerForm:
36
+    properties:
37
+      cpu:
38
+        type: string
39
+      hostname:
40
+        type: string
41
+      id:
42
+        type: string
43
+      memory:
44
+        type: string
45
+      os:
46
+        type: string
47
+      username:
48
+        type: string
49
+      uuid:
50
+        type: string
51
+      version:
52
+        type: string
53
+    type: object
54
+  api.UserPayload:
55
+    properties:
56
+      email:
57
+        type: string
58
+      is_admin:
59
+        type: boolean
60
+      name:
61
+        type: string
62
+      note:
63
+        type: string
64
+      status:
65
+        type: integer
66
+    type: object
67
+  model.Tag:
68
+    properties:
69
+      color:
70
+        description: color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度,后面6位表示颜色,
71
+          可以转成rgba
72
+        type: integer
73
+      created_at:
74
+        type: string
75
+      id:
76
+        type: integer
77
+      name:
78
+        type: string
79
+      updated_at:
80
+        type: string
81
+      user_id:
82
+        type: integer
83
+    type: object
84
+  response.DataResponse:
85
+    properties:
86
+      data: {}
87
+      total:
88
+        type: integer
89
+    type: object
90
+  response.ErrorResponse:
91
+    properties:
92
+      error:
93
+        type: string
94
+    type: object
95
+  response.Response:
96
+    properties:
97
+      code:
98
+        type: integer
99
+      data: {}
100
+      message:
101
+        type: string
102
+    type: object
103
+info:
104
+  contact: {}
105
+  description: 接口
106
+  title: 管理系统API
107
+  version: "1.0"
108
+paths:
109
+  /:
110
+    get:
111
+      consumes:
112
+      - application/json
113
+      description: 首页
114
+      produces:
115
+      - application/json
116
+      responses:
117
+        "200":
118
+          description: OK
119
+          schema:
120
+            $ref: '#/definitions/response.Response'
121
+        "500":
122
+          description: Internal Server Error
123
+          schema:
124
+            $ref: '#/definitions/response.Response'
125
+      summary: 首页
126
+      tags:
127
+      - 首页
128
+  /ab:
129
+    get:
130
+      consumes:
131
+      - application/json
132
+      description: 地址列表
133
+      produces:
134
+      - application/json
135
+      responses:
136
+        "200":
137
+          description: OK
138
+          schema:
139
+            $ref: '#/definitions/response.Response'
140
+        "500":
141
+          description: Internal Server Error
142
+          schema:
143
+            $ref: '#/definitions/response.ErrorResponse'
144
+      security:
145
+      - BearerAuth: []
146
+      summary: 地址列表
147
+      tags:
148
+      - 地址
149
+    post:
150
+      consumes:
151
+      - application/json
152
+      description: 地址更新
153
+      parameters:
154
+      - description: 地址表单
155
+        in: body
156
+        name: body
157
+        required: true
158
+        schema:
159
+          $ref: '#/definitions/api.AddressBookForm'
160
+      produces:
161
+      - application/json
162
+      responses:
163
+        "200":
164
+          description: "null"
165
+          schema:
166
+            type: string
167
+        "500":
168
+          description: Internal Server Error
169
+          schema:
170
+            $ref: '#/definitions/response.ErrorResponse'
171
+      security:
172
+      - BearerAuth: []
173
+      summary: 地址更新
174
+      tags:
175
+      - 地址
176
+  /ab/add:
177
+    post:
178
+      consumes:
179
+      - application/json
180
+      description: 标签
181
+      produces:
182
+      - application/json
183
+      responses:
184
+        "200":
185
+          description: OK
186
+          schema:
187
+            type: string
188
+        "500":
189
+          description: Internal Server Error
190
+          schema:
191
+            $ref: '#/definitions/response.ErrorResponse'
192
+      security:
193
+      - BearerAuth: []
194
+      summary: 标签添加
195
+      tags:
196
+      - 地址
197
+  /ab/personal:
198
+    post:
199
+      consumes:
200
+      - application/json
201
+      description: 个人信息
202
+      parameters:
203
+      - description: string valid
204
+        in: body
205
+        name: string
206
+        schema:
207
+          type: string
208
+      produces:
209
+      - application/json
210
+      responses:
211
+        "200":
212
+          description: OK
213
+          schema:
214
+            $ref: '#/definitions/response.Response'
215
+        "500":
216
+          description: Internal Server Error
217
+          schema:
218
+            $ref: '#/definitions/response.Response'
219
+      security:
220
+      - BearerAuth: []
221
+      summary: 个人信息
222
+      tags:
223
+      - 用户
224
+  /api:
225
+    get:
226
+      consumes:
227
+      - application/json
228
+      description: 用户信息
229
+      produces:
230
+      - application/json
231
+      responses:
232
+        "200":
233
+          description: OK
234
+          schema:
235
+            $ref: '#/definitions/api.UserPayload'
236
+        "500":
237
+          description: Internal Server Error
238
+          schema:
239
+            $ref: '#/definitions/response.Response'
240
+      security:
241
+      - token: []
242
+      summary: 用户信息
243
+      tags:
244
+      - 用户
245
+  /currentUser:
246
+    get:
247
+      consumes:
248
+      - application/json
249
+      description: 用户信息
250
+      produces:
251
+      - application/json
252
+      responses:
253
+        "200":
254
+          description: OK
255
+          schema:
256
+            $ref: '#/definitions/api.UserPayload'
257
+        "500":
258
+          description: Internal Server Error
259
+          schema:
260
+            $ref: '#/definitions/response.Response'
261
+      security:
262
+      - token: []
263
+      summary: 用户信息
264
+      tags:
265
+      - 用户
266
+  /heartbeat:
267
+    post:
268
+      consumes:
269
+      - application/json
270
+      description: 心跳
271
+      produces:
272
+      - application/json
273
+      responses:
274
+        "200":
275
+          description: OK
276
+        "500":
277
+          description: Internal Server Error
278
+          schema:
279
+            $ref: '#/definitions/response.Response'
280
+      summary: 心跳
281
+      tags:
282
+      - 首页
283
+  /login:
284
+    post:
285
+      consumes:
286
+      - application/json
287
+      description: 登录
288
+      parameters:
289
+      - description: 登录表单
290
+        in: body
291
+        name: body
292
+        required: true
293
+        schema:
294
+          $ref: '#/definitions/api.LoginForm'
295
+      produces:
296
+      - application/json
297
+      responses:
298
+        "200":
299
+          description: OK
300
+          schema:
301
+            $ref: '#/definitions/api.LoginRes'
302
+        "500":
303
+          description: Internal Server Error
304
+          schema:
305
+            $ref: '#/definitions/response.ErrorResponse'
306
+      summary: 登录
307
+      tags:
308
+      - 登录
309
+  /login-options:
310
+    post:
311
+      consumes:
312
+      - application/json
313
+      description: 登录选项
314
+      produces:
315
+      - application/json
316
+      responses:
317
+        "200":
318
+          description: OK
319
+          schema:
320
+            items:
321
+              type: string
322
+            type: array
323
+        "500":
324
+          description: Internal Server Error
325
+          schema:
326
+            $ref: '#/definitions/response.ErrorResponse'
327
+      summary: 登录选项
328
+      tags:
329
+      - 登录
330
+  /logout:
331
+    post:
332
+      consumes:
333
+      - application/json
334
+      description: 登出
335
+      produces:
336
+      - application/json
337
+      responses:
338
+        "200":
339
+          description: OK
340
+          schema:
341
+            type: string
342
+        "500":
343
+          description: Internal Server Error
344
+          schema:
345
+            $ref: '#/definitions/response.ErrorResponse'
346
+      summary: 登出
347
+      tags:
348
+      - 登录
349
+  /peers:
350
+    get:
351
+      consumes:
352
+      - application/json
353
+      description: 机器
354
+      parameters:
355
+      - description: 页码
356
+        in: query
357
+        name: page
358
+        type: integer
359
+      - description: 每页数量
360
+        in: query
361
+        name: pageSize
362
+        type: integer
363
+      - description: 状态
364
+        in: query
365
+        name: status
366
+        type: integer
367
+      - description: accessible
368
+        in: query
369
+        name: accessible
370
+        type: string
371
+      produces:
372
+      - application/json
373
+      responses:
374
+        "200":
375
+          description: OK
376
+          schema:
377
+            $ref: '#/definitions/response.DataResponse'
378
+        "500":
379
+          description: Internal Server Error
380
+          schema:
381
+            $ref: '#/definitions/response.Response'
382
+      security:
383
+      - BearerAuth: []
384
+      summary: 机器
385
+      tags:
386
+      - 群组
387
+  /server-config:
388
+    get:
389
+      consumes:
390
+      - application/json
391
+      description: 服务配置,给webclient提供api-server
392
+      produces:
393
+      - application/json
394
+      responses:
395
+        "200":
396
+          description: OK
397
+          schema:
398
+            $ref: '#/definitions/response.Response'
399
+        "500":
400
+          description: Internal Server Error
401
+          schema:
402
+            $ref: '#/definitions/response.Response'
403
+      security:
404
+      - token: []
405
+      summary: 服务配置
406
+      tags:
407
+      - WEBCLIENT
408
+  /sysinfo:
409
+    post:
410
+      consumes:
411
+      - application/json
412
+      description: 提交系统信息
413
+      parameters:
414
+      - description: 系统信息表单
415
+        in: body
416
+        name: body
417
+        required: true
418
+        schema:
419
+          $ref: '#/definitions/api.PeerForm'
420
+      produces:
421
+      - application/json
422
+      responses:
423
+        "200":
424
+          description: SYSINFO_UPDATED,ID_NOT_FOUND
425
+          schema:
426
+            type: string
427
+        "500":
428
+          description: Internal Server Error
429
+          schema:
430
+            $ref: '#/definitions/response.ErrorResponse'
431
+      security:
432
+      - BearerAuth: []
433
+      summary: 提交系统信息
434
+      tags:
435
+      - 地址
436
+  /tags:
437
+    post:
438
+      consumes:
439
+      - application/json
440
+      description: 标签
441
+      produces:
442
+      - application/json
443
+      responses:
444
+        "200":
445
+          description: OK
446
+          schema:
447
+            items:
448
+              $ref: '#/definitions/model.Tag'
449
+            type: array
450
+        "500":
451
+          description: Internal Server Error
452
+          schema:
453
+            $ref: '#/definitions/response.ErrorResponse'
454
+      security:
455
+      - BearerAuth: []
456
+      summary: 标签
457
+      tags:
458
+      - 地址
459
+  /users:
460
+    get:
461
+      consumes:
462
+      - application/json
463
+      description: 用户列表
464
+      parameters:
465
+      - description: 页码
466
+        in: query
467
+        name: page
468
+        type: integer
469
+      - description: 每页数量
470
+        in: query
471
+        name: pageSize
472
+        type: integer
473
+      - description: 状态
474
+        in: query
475
+        name: status
476
+        type: integer
477
+      - description: accessible
478
+        in: query
479
+        name: accessible
480
+        type: string
481
+      produces:
482
+      - application/json
483
+      responses:
484
+        "200":
485
+          description: OK
486
+          schema:
487
+            allOf:
488
+            - $ref: '#/definitions/response.DataResponse'
489
+            - properties:
490
+                data:
491
+                  items:
492
+                    $ref: '#/definitions/api.UserPayload'
493
+                  type: array
494
+              type: object
495
+        "500":
496
+          description: Internal Server Error
497
+          schema:
498
+            $ref: '#/definitions/response.ErrorResponse'
499
+      security:
500
+      - BearerAuth: []
501
+      summary: 用户列表
502
+      tags:
503
+      - 群组
504
+securityDefinitions:
505
+  BearerAuth:
506
+    in: header
507
+    name: Authorization
508
+    type: apiKey
509
+  token:
510
+    in: header
511
+    name: api-token
512
+    type: apiKey
513
+swagger: "2.0"

BIN
docs/api_swag.png


BIN
docs/pc_ab.png


BIN
docs/pc_gr.png


BIN
docs/web_admin.png


BIN
docs/web_admin_gr.png


BIN
docs/web_resetpwd.png


BIN
docs/web_user.png


BIN
docs/webclient_conf.png


+ 5 - 0
generate_api.go

@@ -0,0 +1,5 @@
1
+package Gwen
2
+
3
+//go:generate swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin
4
+//go:generate swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
5
+//go:generate go run cmd/apimain.go

+ 33 - 0
global/global.go

@@ -0,0 +1,33 @@
1
+package global
2
+
3
+import (
4
+	"Gwen/config"
5
+	"Gwen/lib/cache"
6
+	"Gwen/lib/jwt"
7
+	"Gwen/lib/lock"
8
+	"Gwen/lib/upload"
9
+	ut "github.com/go-playground/universal-translator"
10
+	"github.com/go-playground/validator/v10"
11
+	"github.com/go-redis/redis/v8"
12
+	"github.com/sirupsen/logrus"
13
+	"github.com/spf13/viper"
14
+	"gorm.io/gorm"
15
+)
16
+
17
+var (
18
+	DB        *gorm.DB
19
+	Logger    *logrus.Logger
20
+	Config    config.Config
21
+	Viper     *viper.Viper
22
+	Redis     *redis.Client
23
+	Cache     cache.Handler
24
+	Validator struct {
25
+		Validate    *validator.Validate
26
+		VTrans      ut.Translator
27
+		ValidStruct func(interface{}) []string
28
+		ValidVar    func(field interface{}, tag string) []string
29
+	}
30
+	Oss  *upload.Oss
31
+	Jwt  *jwt.Jwt
32
+	Lock lock.Locker
33
+)

+ 73 - 0
go.mod

@@ -0,0 +1,73 @@
1
+module Gwen
2
+
3
+go 1.22
4
+
5
+require (
6
+	github.com/antonfisher/nested-logrus-formatter v1.3.1
7
+	github.com/fsnotify/fsnotify v1.5.1
8
+	github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6
9
+	github.com/gin-gonic/gin v1.9.0
10
+	github.com/go-playground/locales v0.14.1
11
+	github.com/go-playground/universal-translator v0.18.1
12
+	github.com/go-playground/validator/v10 v10.11.2
13
+	github.com/go-redis/redis/v8 v8.11.4
14
+	github.com/golang-jwt/jwt/v5 v5.2.1
15
+	github.com/sirupsen/logrus v1.8.1
16
+	github.com/spf13/viper v1.9.0
17
+	github.com/swaggo/files v1.0.1
18
+	github.com/swaggo/gin-swagger v1.6.0
19
+	github.com/swaggo/swag v1.16.3
20
+	gorm.io/driver/mysql v1.5.7
21
+	gorm.io/driver/sqlite v1.5.6
22
+	gorm.io/gorm v1.25.7
23
+)
24
+
25
+require (
26
+	github.com/KyleBanks/depth v1.2.1 // indirect
27
+	github.com/PuerkitoBio/purell v1.1.1 // indirect
28
+	github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
29
+	github.com/bytedance/sonic v1.8.0 // indirect
30
+	github.com/cespare/xxhash/v2 v2.1.2 // indirect
31
+	github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
32
+	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
33
+	github.com/gin-contrib/sse v0.1.0 // indirect
34
+	github.com/go-openapi/jsonpointer v0.19.5 // indirect
35
+	github.com/go-openapi/jsonreference v0.19.6 // indirect
36
+	github.com/go-openapi/spec v0.20.4 // indirect
37
+	github.com/go-openapi/swag v0.19.15 // indirect
38
+	github.com/go-sql-driver/mysql v1.7.0 // indirect
39
+	github.com/goccy/go-json v0.10.0 // indirect
40
+	github.com/hashicorp/hcl v1.0.0 // indirect
41
+	github.com/jinzhu/inflection v1.0.0 // indirect
42
+	github.com/jinzhu/now v1.1.5 // indirect
43
+	github.com/josharian/intern v1.0.0 // indirect
44
+	github.com/json-iterator/go v1.1.12 // indirect
45
+	github.com/klauspost/cpuid/v2 v2.0.9 // indirect
46
+	github.com/leodido/go-urn v1.2.1 // indirect
47
+	github.com/magiconair/properties v1.8.5 // indirect
48
+	github.com/mailru/easyjson v0.7.7 // indirect
49
+	github.com/mattn/go-isatty v0.0.17 // indirect
50
+	github.com/mattn/go-sqlite3 v1.14.23 // indirect
51
+	github.com/mitchellh/mapstructure v1.4.2 // indirect
52
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
53
+	github.com/modern-go/reflect2 v1.0.2 // indirect
54
+	github.com/pelletier/go-toml v1.9.4 // indirect
55
+	github.com/pelletier/go-toml/v2 v2.0.6 // indirect
56
+	github.com/spf13/afero v1.6.0 // indirect
57
+	github.com/spf13/cast v1.4.1 // indirect
58
+	github.com/spf13/jwalterweatherman v1.1.0 // indirect
59
+	github.com/spf13/pflag v1.0.5 // indirect
60
+	github.com/subosito/gotenv v1.2.0 // indirect
61
+	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
62
+	github.com/ugorji/go/codec v1.2.9 // indirect
63
+	golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
64
+	golang.org/x/crypto v0.14.0 // indirect
65
+	golang.org/x/net v0.17.0 // indirect
66
+	golang.org/x/sys v0.13.0 // indirect
67
+	golang.org/x/text v0.13.0 // indirect
68
+	golang.org/x/tools v0.7.0 // indirect
69
+	google.golang.org/protobuf v1.28.1 // indirect
70
+	gopkg.in/ini.v1 v1.63.2 // indirect
71
+	gopkg.in/yaml.v2 v2.4.0 // indirect
72
+	gopkg.in/yaml.v3 v3.0.1 // indirect
73
+)

+ 191 - 0
http/controller/admin/addressBook.go

@@ -0,0 +1,191 @@
1
+package admin
2
+
3
+import (
4
+	"Gwen/global"
5
+	"Gwen/http/request/admin"
6
+	"Gwen/http/response"
7
+	"Gwen/service"
8
+	_ "encoding/json"
9
+	"github.com/gin-gonic/gin"
10
+	"gorm.io/gorm"
11
+	"strconv"
12
+)
13
+
14
+type AddressBook struct {
15
+}
16
+
17
+// Detail 地址簿
18
+// @Tags 地址簿
19
+// @Summary 地址簿详情
20
+// @Description 地址簿详情
21
+// @Accept  json
22
+// @Produce  json
23
+// @Param id path int true "ID"
24
+// @Success 200 {object} response.Response{data=model.AddressBook}
25
+// @Failure 500 {object} response.Response
26
+// @Router /admin/address_book/detail/{id} [get]
27
+// @Security token
28
+func (ct *AddressBook) Detail(c *gin.Context) {
29
+	id := c.Param("id")
30
+	iid, _ := strconv.Atoi(id)
31
+	t := service.AllService.AddressBookService.InfoByRowId(uint(iid))
32
+	u := service.AllService.UserService.CurUser(c)
33
+	if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
34
+		response.Fail(c, 101, "无权限")
35
+		return
36
+	}
37
+	if t.RowId > 0 {
38
+		response.Success(c, t)
39
+		return
40
+	}
41
+	response.Fail(c, 101, "信息不存在")
42
+	return
43
+}
44
+
45
+// Create 创建地址簿
46
+// @Tags 地址簿
47
+// @Summary 创建地址簿
48
+// @Description 创建地址簿
49
+// @Accept  json
50
+// @Produce  json
51
+// @Param body body admin.AddressBookForm true "地址簿信息"
52
+// @Success 200 {object} response.Response{data=model.AddressBook}
53
+// @Failure 500 {object} response.Response
54
+// @Router /admin/address_book/create [post]
55
+// @Security token
56
+func (ct *AddressBook) Create(c *gin.Context) {
57
+	f := &admin.AddressBookForm{}
58
+	if err := c.ShouldBindJSON(f); err != nil {
59
+		response.Fail(c, 101, "参数错误")
60
+		return
61
+	}
62
+	errList := global.Validator.ValidStruct(f)
63
+	if len(errList) > 0 {
64
+		response.Fail(c, 101, errList[0])
65
+		return
66
+	}
67
+	t := f.ToAddressBook()
68
+	u := service.AllService.UserService.CurUser(c)
69
+	if !service.AllService.UserService.IsAdmin(u) || t.UserId == 0 {
70
+		t.UserId = u.Id
71
+	}
72
+	err := service.AllService.AddressBookService.Create(t)
73
+	if err != nil {
74
+		response.Fail(c, 101, "创建失败")
75
+		return
76
+	}
77
+	response.Success(c, u)
78
+}
79
+
80
+// List 列表
81
+// @Tags 地址簿
82
+// @Summary 地址簿列表
83
+// @Description 地址簿列表
84
+// @Accept  json
85
+// @Produce  json
86
+// @Param page query int false "页码"
87
+// @Param page_size query int false "页大小"
88
+// @Param user_id query int false "用户id"
89
+// @Param is_my query int false "是否是我的"
90
+// @Success 200 {object} response.Response{data=model.AddressBookList}
91
+// @Failure 500 {object} response.Response
92
+// @Router /admin/address_book/list [get]
93
+// @Security token
94
+func (ct *AddressBook) List(c *gin.Context) {
95
+	query := &admin.AddressBookQuery{}
96
+	if err := c.ShouldBindQuery(query); err != nil {
97
+		response.Fail(c, 101, "参数错误")
98
+		return
99
+	}
100
+	u := service.AllService.UserService.CurUser(c)
101
+	if !service.AllService.UserService.IsAdmin(u) || query.IsMy == 1 {
102
+		query.UserId = int(u.Id)
103
+	}
104
+	res := service.AllService.AddressBookService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
105
+		if query.UserId > 0 {
106
+			tx.Where("user_id = ?", query.UserId)
107
+		}
108
+	})
109
+	response.Success(c, res)
110
+}
111
+
112
+// Update 编辑
113
+// @Tags 地址簿
114
+// @Summary 地址簿编辑
115
+// @Description 地址簿编辑
116
+// @Accept  json
117
+// @Produce  json
118
+// @Param body body admin.AddressBookForm true "地址簿信息"
119
+// @Success 200 {object} response.Response{data=model.AddressBook}
120
+// @Failure 500 {object} response.Response
121
+// @Router /admin/address_book/update [post]
122
+// @Security token
123
+func (ct *AddressBook) Update(c *gin.Context) {
124
+	f := &admin.AddressBookForm{}
125
+	if err := c.ShouldBindJSON(f); err != nil {
126
+		response.Fail(c, 101, "参数错误")
127
+		return
128
+	}
129
+	errList := global.Validator.ValidStruct(f)
130
+	if len(errList) > 0 {
131
+		response.Fail(c, 101, errList[0])
132
+		return
133
+	}
134
+	if f.RowId == 0 {
135
+		response.Fail(c, 101, "参数错误")
136
+		return
137
+	}
138
+	t := f.ToAddressBook()
139
+	u := service.AllService.UserService.CurUser(c)
140
+	if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
141
+		response.Fail(c, 101, "无权限")
142
+		return
143
+	}
144
+	err := service.AllService.AddressBookService.Update(t)
145
+	if err != nil {
146
+		response.Fail(c, 101, "更新失败")
147
+		return
148
+	}
149
+	response.Success(c, nil)
150
+}
151
+
152
+// Delete 删除
153
+// @Tags 地址簿
154
+// @Summary 地址簿删除
155
+// @Description 地址簿删除
156
+// @Accept  json
157
+// @Produce  json
158
+// @Param body body admin.AddressBookForm true "地址簿信息"
159
+// @Success 200 {object} response.Response
160
+// @Failure 500 {object} response.Response
161
+// @Router /admin/address_book/delete [post]
162
+// @Security token
163
+func (ct *AddressBook) Delete(c *gin.Context) {
164
+	f := &admin.AddressBookForm{}
165
+	if err := c.ShouldBindJSON(f); err != nil {
166
+		response.Fail(c, 101, "系统错误")
167
+		return
168
+	}
169
+	id := f.RowId
170
+	errList := global.Validator.ValidVar(id, "required,gt=0")
171
+	if len(errList) > 0 {
172
+		response.Fail(c, 101, errList[0])
173
+		return
174
+	}
175
+	t := service.AllService.AddressBookService.InfoByRowId(f.RowId)
176
+	u := service.AllService.UserService.CurUser(c)
177
+	if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
178
+		response.Fail(c, 101, "无权限")
179
+		return
180
+	}
181
+	if u.Id > 0 {
182
+		err := service.AllService.AddressBookService.Delete(t)
183
+		if err == nil {
184
+			response.Success(c, nil)
185
+			return
186
+		}
187
+		response.Fail(c, 101, err.Error())
188
+		return
189
+	}
190
+	response.Fail(c, 101, "信息不存在")
191
+}

+ 83 - 0
http/controller/admin/file.go

@@ -0,0 +1,83 @@
1
+package admin
2
+
3
+import (
4
+	"Gwen/global"
5
+	"Gwen/http/response"
6
+	"Gwen/lib/upload"
7
+	"fmt"
8
+	"github.com/gin-gonic/gin"
9
+	"os"
10
+	"time"
11
+)
12
+
13
+type File struct {
14
+}
15
+
16
+// OssToken 文件
17
+// @Tags 文件
18
+// @Summary 获取ossToken
19
+// @Description 获取ossToken
20
+// @Accept  json
21
+// @Produce  json
22
+// @Success 200 {object} response.Response
23
+// @Failure 500 {object} response.Response
24
+// @Router /admin/file/oss_token [get]
25
+// @Security token
26
+func (f *File) OssToken(c *gin.Context) {
27
+	token := global.Oss.GetPolicyToken("")
28
+	response.Success(c, token)
29
+}
30
+
31
+type FileBack struct {
32
+	upload.CallbackBaseForm
33
+	Url string `json:"url"`
34
+}
35
+
36
+// Notify 上传成功后回调
37
+func (f *File) Notify(c *gin.Context) {
38
+
39
+	res := global.Oss.Verify(c.Request)
40
+	if !res {
41
+		response.Fail(c, 101, "权限错误")
42
+		return
43
+	}
44
+	fm := &FileBack{}
45
+	if err := c.ShouldBind(fm); err != nil {
46
+		fmt.Println(err)
47
+	}
48
+	fm.Url = global.Config.Oss.Host + "/" + fm.Filename
49
+	response.Success(c, fm)
50
+
51
+}
52
+
53
+// Upload 上传文件到本地
54
+// @Tags 文件
55
+// @Summary 上传文件到本地
56
+// @Description 上传文件到本地
57
+// @Accept  multipart/form-data
58
+// @Produce  json
59
+// @Param file formData file true "上传文件示例"
60
+// @Success 200 {object} response.Response
61
+// @Failure 500 {object} response.Response
62
+// @Router /admin/file/upload [post]
63
+// @Security token
64
+func (f *File) Upload(c *gin.Context) {
65
+	file, _ := c.FormFile("file")
66
+	timePath := time.Now().Format("20060102") + "/"
67
+	webPath := "/upload/" + timePath
68
+	path := global.Config.Gin.ResourcesPath + webPath
69
+	dst := path + file.Filename
70
+	err := os.MkdirAll(path, os.ModePerm)
71
+	if err != nil {
72
+		return
73
+	}
74
+	// 上传文件至指定目录
75
+	err = c.SaveUploadedFile(file, dst)
76
+	if err != nil {
77
+		return
78
+	}
79
+	// 返回文件web地址
80
+	response.Success(c, gin.H{
81
+		"url": webPath + file.Filename,
82
+	})
83
+}

+ 160 - 0
http/controller/admin/group.go

@@ -0,0 +1,160 @@
1
+package admin
2
+
3
+import (
4
+	"Gwen/global"
5
+	"Gwen/http/request/admin"
6
+	"Gwen/http/response"
7
+	"Gwen/service"
8
+	"github.com/gin-gonic/gin"
9
+	"strconv"
10
+)
11
+
12
+type Group struct {
13
+}
14
+
15
+// Detail 群组
16
+// @Tags 群组
17
+// @Summary 群组详情
18
+// @Description 群组详情
19
+// @Accept  json
20
+// @Produce  json
21
+// @Param id path int true "ID"
22
+// @Success 200 {object} response.Response{data=model.Group}
23
+// @Failure 500 {object} response.Response
24
+// @Router /admin/group/detail/{id} [get]
25
+// @Security token
26
+func (ct *Group) Detail(c *gin.Context) {
27
+	id := c.Param("id")
28
+	iid, _ := strconv.Atoi(id)
29
+	u := service.AllService.GroupService.InfoById(uint(iid))
30
+	if u.Id > 0 {
31
+		response.Success(c, u)
32
+		return
33
+	}
34
+	response.Fail(c, 101, "信息不存在")
35
+	return
36
+}
37
+
38
+// Create 创建群组
39
+// @Tags 群组
40
+// @Summary 创建群组
41
+// @Description 创建群组
42
+// @Accept  json
43
+// @Produce  json
44
+// @Param body body admin.GroupForm true "群组信息"
45
+// @Success 200 {object} response.Response{data=model.Group}
46
+// @Failure 500 {object} response.Response
47
+// @Router /admin/group/create [post]
48
+// @Security token
49
+func (ct *Group) Create(c *gin.Context) {
50
+	f := &admin.GroupForm{}
51
+	if err := c.ShouldBindJSON(f); err != nil {
52
+		response.Fail(c, 101, "参数错误")
53
+		return
54
+	}
55
+	errList := global.Validator.ValidStruct(f)
56
+	if len(errList) > 0 {
57
+		response.Fail(c, 101, errList[0])
58
+		return
59
+	}
60
+	u := f.ToGroup()
61
+	err := service.AllService.GroupService.Create(u)
62
+	if err != nil {
63
+		response.Fail(c, 101, "创建失败")
64
+		return
65
+	}
66
+	response.Success(c, u)
67
+}
68
+
69
+// List 列表
70
+// @Tags 群组
71
+// @Summary 群组列表
72
+// @Description 群组列表
73
+// @Accept  json
74
+// @Produce  json
75
+// @Param page query int false "页码"
76
+// @Param page_size query int false "页大小"
77
+// @Success 200 {object} response.Response{data=model.GroupList}
78
+// @Failure 500 {object} response.Response
79
+// @Router /admin/group/list [get]
80
+// @Security token
81
+func (ct *Group) List(c *gin.Context) {
82
+	query := &admin.PageQuery{}
83
+	if err := c.ShouldBindQuery(query); err != nil {
84
+		response.Fail(c, 101, "参数错误")
85
+		return
86
+	}
87
+	res := service.AllService.GroupService.List(query.Page, query.PageSize, nil)
88
+	response.Success(c, res)
89
+}
90
+
91
+// Update 编辑
92
+// @Tags 群组
93
+// @Summary 群组编辑
94
+// @Description 群组编辑
95
+// @Accept  json
96
+// @Produce  json
97
+// @Param body body admin.GroupForm true "群组信息"
98
+// @Success 200 {object} response.Response{data=model.Group}
99
+// @Failure 500 {object} response.Response
100
+// @Router /admin/group/update [post]
101
+// @Security token
102
+func (ct *Group) Update(c *gin.Context) {
103
+	f := &admin.GroupForm{}
104
+	if err := c.ShouldBindJSON(f); err != nil {
105
+		response.Fail(c, 101, "参数错误")
106
+		return
107
+	}
108
+	if f.Id == 0 {
109
+		response.Fail(c, 101, "参数错误")
110
+		return
111
+	}
112
+	errList := global.Validator.ValidStruct(f)
113
+	if len(errList) > 0 {
114
+		response.Fail(c, 101, errList[0])
115
+		return
116
+	}
117
+	u := f.ToGroup()
118
+	err := service.AllService.GroupService.Update(u)
119
+	if err != nil {
120
+		response.Fail(c, 101, "更新失败")
121
+		return
122
+	}
123
+	response.Success(c, nil)
124
+}
125
+
126
+// Delete 删除
127
+// @Tags 群组
128
+// @Summary 群组删除
129
+// @Description 群组删除
130
+// @Accept  json
131
+// @Produce  json
132
+// @Param body body admin.GroupForm true "群组信息"
133
+// @Success 200 {object} response.Response
134
+// @Failure 500 {object} response.Response
135
+// @Router /admin/group/delete [post]
136
+// @Security token
137
+func (ct *Group) Delete(c *gin.Context) {
138
+	f := &admin.GroupForm{}
139
+	if err := c.ShouldBindJSON(f); err != nil {
140
+		response.Fail(c, 101, "系统错误")
141
+		return
142
+	}
143
+	id := f.Id
144
+	errList := global.Validator.ValidVar(id, "required,gt=0")
145
+	if len(errList) > 0 {
146
+		response.Fail(c, 101, errList[0])
147
+		return
148
+	}
149
+	u := service.AllService.GroupService.InfoById(f.Id)
150
+	if u.Id > 0 {
151
+		err := service.AllService.GroupService.Delete(u)
152
+		if err == nil {
153
+			response.Success(c, nil)
154
+			return
155
+		}
156
+		response.Fail(c, 101, err.Error())
157
+		return
158
+	}
159
+	response.Fail(c, 101, "信息不存在")
160
+}

+ 74 - 0
http/controller/admin/login.go

@@ -0,0 +1,74 @@
1
+package admin
2
+
3
+import (
4
+	"Gwen/global"
5
+	"Gwen/http/request/admin"
6
+	"Gwen/http/response"
7
+	adResp "Gwen/http/response/admin"
8
+	"Gwen/service"
9
+	"fmt"
10
+	"github.com/gin-gonic/gin"
11
+)
12
+
13
+type Login struct {
14
+}
15
+
16
+// Login 登录
17
+// @Tags 登录
18
+// @Summary 登录
19
+// @Description 登录
20
+// @Accept  json
21
+// @Produce  json
22
+// @Param body body admin.Login true "登录信息"
23
+// @Success 200 {object} response.Response{data=adResp.LoginPayload}
24
+// @Failure 500 {object} response.Response
25
+// @Router /admin/login [post]
26
+// @Security token
27
+func (ct *Login) Login(c *gin.Context) {
28
+	fmt.Println("login")
29
+	f := &admin.Login{}
30
+	err := c.ShouldBindJSON(f)
31
+	if err != nil {
32
+		response.Fail(c, 101, "参数错误")
33
+		return
34
+	}
35
+
36
+	errList := global.Validator.ValidStruct(f)
37
+	if len(errList) > 0 {
38
+		response.Fail(c, 101, errList[0])
39
+		return
40
+	}
41
+	u := service.AllService.UserService.InfoByUsernamePassword(f.Username, f.Password)
42
+
43
+	if u.Id == 0 {
44
+		response.Fail(c, 101, "用户名或密码错误")
45
+		return
46
+	}
47
+
48
+	ut := service.AllService.UserService.Login(u)
49
+
50
+	response.Success(c, &adResp.LoginPayload{
51
+		Token:      ut.Token,
52
+		Username:   u.Username,
53
+		RouteNames: service.AllService.UserService.RouteNames(u),
54
+		Nickname:   u.Nickname,
55
+	})
56
+}
57
+
58
+// Logout 登出
59
+// @Tags 登录
60
+// @Summary 登出
61
+// @Description 登出
62
+// @Accept  json
63
+// @Produce  json
64
+// @Success 200 {object} response.Response
65
+// @Failure 500 {object} response.Response
66
+// @Router /admin/logout [post]
67
+func (ct *Login) Logout(c *gin.Context) {
68
+	u := service.AllService.UserService.CurUser(c)
69
+	token, ok := c.Get("token")
70
+	if ok {
71
+		service.AllService.UserService.Logout(u, token.(string))
72
+	}
73
+	response.Success(c, nil)
74
+}

+ 160 - 0
http/controller/admin/peer.go

@@ -0,0 +1,160 @@
1
+package admin
2
+
3
+import (
4
+	"Gwen/global"
5
+	"Gwen/http/request/admin"
6
+	"Gwen/http/response"
7
+	"Gwen/service"
8
+	"github.com/gin-gonic/gin"
9
+	"strconv"
10
+)
11
+
12
+type Peer struct {
13
+}
14
+
15
+// Detail 机器
16
+// @Tags 机器
17
+// @Summary 机器详情
18
+// @Description 机器详情
19
+// @Accept  json
20
+// @Produce  json
21
+// @Param id path int true "ID"
22
+// @Success 200 {object} response.Response{data=model.Peer}
23
+// @Failure 500 {object} response.Response
24
+// @Router /admin/peer/detail/{id} [get]
25
+// @Security token
26
+func (ct *Peer) Detail(c *gin.Context) {
27
+	id := c.Param("id")
28
+	iid, _ := strconv.Atoi(id)
29
+	u := service.AllService.PeerService.InfoByRowId(uint(iid))
30
+	if u.RowId > 0 {
31
+		response.Success(c, u)
32
+		return
33
+	}
34
+	response.Fail(c, 101, "信息不存在")
35
+	return
36
+}
37
+
38
+// Create 创建机器
39
+// @Tags 机器
40
+// @Summary 创建机器
41
+// @Description 创建机器
42
+// @Accept  json
43
+// @Produce  json
44
+// @Param body body admin.PeerForm true "机器信息"
45
+// @Success 200 {object} response.Response{data=model.Peer}
46
+// @Failure 500 {object} response.Response
47
+// @Router /admin/peer/create [post]
48
+// @Security token
49
+func (ct *Peer) Create(c *gin.Context) {
50
+	f := &admin.PeerForm{}
51
+	if err := c.ShouldBindJSON(f); err != nil {
52
+		response.Fail(c, 101, "参数错误")
53
+		return
54
+	}
55
+	errList := global.Validator.ValidStruct(f)
56
+	if len(errList) > 0 {
57
+		response.Fail(c, 101, errList[0])
58
+		return
59
+	}
60
+	u := f.ToPeer()
61
+	err := service.AllService.PeerService.Create(u)
62
+	if err != nil {
63
+		response.Fail(c, 101, "创建失败")
64
+		return
65
+	}
66
+	response.Success(c, u)
67
+}
68
+
69
+// List 列表
70
+// @Tags 机器
71
+// @Summary 机器列表
72
+// @Description 机器列表
73
+// @Accept  json
74
+// @Produce  json
75
+// @Param page query int false "页码"
76
+// @Param page_size query int false "页大小"
77
+// @Success 200 {object} response.Response{data=model.PeerList}
78
+// @Failure 500 {object} response.Response
79
+// @Router /admin/peer/list [get]
80
+// @Security token
81
+func (ct *Peer) List(c *gin.Context) {
82
+	query := &admin.PageQuery{}
83
+	if err := c.ShouldBindQuery(query); err != nil {
84
+		response.Fail(c, 101, "参数错误")
85
+		return
86
+	}
87
+	res := service.AllService.PeerService.List(query.Page, query.PageSize, nil)
88
+	response.Success(c, res)
89
+}
90
+
91
+// Update 编辑
92
+// @Tags 机器
93
+// @Summary 机器编辑
94
+// @Description 机器编辑
95
+// @Accept  json
96
+// @Produce  json
97
+// @Param body body admin.PeerForm true "机器信息"
98
+// @Success 200 {object} response.Response{data=model.Peer}
99
+// @Failure 500 {object} response.Response
100
+// @Router /admin/peer/update [post]
101
+// @Security token
102
+func (ct *Peer) Update(c *gin.Context) {
103
+	f := &admin.PeerForm{}
104
+	if err := c.ShouldBindJSON(f); err != nil {
105
+		response.Fail(c, 101, "参数错误")
106
+		return
107
+	}
108
+	if f.RowId == 0 {
109
+		response.Fail(c, 101, "参数错误")
110
+		return
111
+	}
112
+	errList := global.Validator.ValidStruct(f)
113
+	if len(errList) > 0 {
114
+		response.Fail(c, 101, errList[0])
115
+		return
116
+	}
117
+	u := f.ToPeer()
118
+	err := service.AllService.PeerService.Update(u)
119
+	if err != nil {
120
+		response.Fail(c, 101, "更新失败")
121
+		return
122
+	}
123
+	response.Success(c, nil)
124
+}
125
+
126
+// Delete 删除
127
+// @Tags 机器
128
+// @Summary 机器删除
129
+// @Description 机器删除
130
+// @Accept  json
131
+// @Produce  json
132
+// @Param body body admin.PeerForm true "机器信息"
133
+// @Success 200 {object} response.Response
134
+// @Failure 500 {object} response.Response
135
+// @Router /admin/peer/delete [post]
136
+// @Security token
137
+func (ct *Peer) Delete(c *gin.Context) {
138
+	f := &admin.PeerForm{}
139
+	if err := c.ShouldBindJSON(f); err != nil {
140
+		response.Fail(c, 101, "系统错误")
141
+		return
142
+	}
143
+	id := f.RowId
144
+	errList := global.Validator.ValidVar(id, "required,gt=0")
145
+	if len(errList) > 0 {
146
+		response.Fail(c, 101, errList[0])
147
+		return
148
+	}
149
+	u := service.AllService.PeerService.InfoByRowId(f.RowId)
150
+	if u.RowId > 0 {
151
+		err := service.AllService.PeerService.Delete(u)
152
+		if err == nil {
153
+			response.Success(c, nil)
154
+			return
155
+		}
156
+		response.Fail(c, 101, err.Error())
157
+		return
158
+	}
159
+	response.Fail(c, 101, "信息不存在")
160
+}

+ 30 - 0
http/controller/admin/rustdesk.go

@@ -0,0 +1,30 @@
1
+package admin
2
+
3
+import (
4
+	"Gwen/global"
5
+	"Gwen/http/response"
6
+	"github.com/gin-gonic/gin"
7
+)
8
+
9
+type Rustdesk struct {
10
+}
11
+
12
+// ServerConfig 服务配置
13
+// @Tags ADMIN
14
+// @Summary 服务配置
15
+// @Description 服务配置,给webclient提供api-server
16
+// @Accept  json
17
+// @Produce  json
18
+// @Success 200 {object} response.Response
19
+// @Failure 500 {object} response.Response
20
+// @Router /admin/server-config [get]
21
+// @Security token
22
+func (r *Rustdesk) ServerConfig(c *gin.Context) {
23
+	cf := &response.ServerConfigResponse{
24
+		IdServer:    global.Config.Rustdesk.IdServer,
25
+		Key:         global.Config.Rustdesk.Key,
26
+		RelayServer: global.Config.Rustdesk.RelayServer,
27
+		ApiServer:   global.Config.Rustdesk.ApiServer,
28
+	}
29
+	response.Success(c, cf)
30
+}

+ 190 - 0
http/controller/admin/tag.go

@@ -0,0 +1,190 @@
1
+package admin
2
+
3
+import (
4
+	"Gwen/global"
5
+	"Gwen/http/request/admin"
6
+	"Gwen/http/response"
7
+	"Gwen/service"
8
+	"github.com/gin-gonic/gin"
9
+	"gorm.io/gorm"
10
+	"strconv"
11
+)
12
+
13
+type Tag struct {
14
+}
15
+
16
+// Detail 标签
17
+// @Tags 标签
18
+// @Summary 标签详情
19
+// @Description 标签详情
20
+// @Accept  json
21
+// @Produce  json
22
+// @Param id path int true "ID"
23
+// @Success 200 {object} response.Response{data=model.Tag}
24
+// @Failure 500 {object} response.Response
25
+// @Router /admin/tag/detail/{id} [get]
26
+// @Security token
27
+func (ct *Tag) Detail(c *gin.Context) {
28
+	id := c.Param("id")
29
+	iid, _ := strconv.Atoi(id)
30
+	t := service.AllService.TagService.InfoById(uint(iid))
31
+	u := service.AllService.UserService.CurUser(c)
32
+	if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
33
+		response.Fail(c, 101, "无权限")
34
+		return
35
+	}
36
+	if t.Id > 0 {
37
+		response.Success(c, t)
38
+		return
39
+	}
40
+	response.Fail(c, 101, "信息不存在")
41
+	return
42
+}
43
+
44
+// Create 创建标签
45
+// @Tags 标签
46
+// @Summary 创建标签
47
+// @Description 创建标签
48
+// @Accept  json
49
+// @Produce  json
50
+// @Param body body admin.TagForm true "标签信息"
51
+// @Success 200 {object} response.Response{data=model.Tag}
52
+// @Failure 500 {object} response.Response
53
+// @Router /admin/tag/create [post]
54
+// @Security token
55
+func (ct *Tag) Create(c *gin.Context) {
56
+	f := &admin.TagForm{}
57
+	if err := c.ShouldBindJSON(f); err != nil {
58
+		response.Fail(c, 101, "参数错误")
59
+		return
60
+	}
61
+	errList := global.Validator.ValidStruct(f)
62
+	if len(errList) > 0 {
63
+		response.Fail(c, 101, errList[0])
64
+		return
65
+	}
66
+	t := f.ToTag()
67
+	u := service.AllService.UserService.CurUser(c)
68
+	if !service.AllService.UserService.IsAdmin(u) {
69
+		t.UserId = u.Id
70
+	}
71
+	err := service.AllService.TagService.Create(t)
72
+	if err != nil {
73
+		response.Fail(c, 101, "创建失败")
74
+		return
75
+	}
76
+	response.Success(c, u)
77
+}
78
+
79
+// List 列表
80
+// @Tags 标签
81
+// @Summary 标签列表
82
+// @Description 标签列表
83
+// @Accept  json
84
+// @Produce  json
85
+// @Param page query int false "页码"
86
+// @Param page_size query int false "页大小"
87
+// @Param is_my query int false "是否是我的"
88
+// @Param user_id query int false "用户id"
89
+// @Success 200 {object} response.Response{data=model.TagList}
90
+// @Failure 500 {object} response.Response
91
+// @Router /admin/tag/list [get]
92
+// @Security token
93
+func (ct *Tag) List(c *gin.Context) {
94
+	query := &admin.TagQuery{}
95
+	if err := c.ShouldBindQuery(query); err != nil {
96
+		response.Fail(c, 101, "参数错误")
97
+		return
98
+	}
99
+	u := service.AllService.UserService.CurUser(c)
100
+	if !service.AllService.UserService.IsAdmin(u) || query.IsMy == 1 {
101
+		query.UserId = int(u.Id)
102
+	}
103
+	res := service.AllService.TagService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
104
+		if query.UserId > 0 {
105
+			tx.Where("user_id = ?", query.UserId)
106
+		}
107
+	})
108
+	response.Success(c, res)
109
+}
110
+
111
+// Update 编辑
112
+// @Tags 标签
113
+// @Summary 标签编辑
114
+// @Description 标签编辑
115
+// @Accept  json
116
+// @Produce  json
117
+// @Param body body admin.TagForm true "标签信息"
118
+// @Success 200 {object} response.Response{data=model.Tag}
119
+// @Failure 500 {object} response.Response
120
+// @Router /admin/tag/update [post]
121
+// @Security token
122
+func (ct *Tag) Update(c *gin.Context) {
123
+	f := &admin.TagForm{}
124
+	if err := c.ShouldBindJSON(f); err != nil {
125
+		response.Fail(c, 101, "参数错误")
126
+		return
127
+	}
128
+	errList := global.Validator.ValidStruct(f)
129
+	if len(errList) > 0 {
130
+		response.Fail(c, 101, errList[0])
131
+		return
132
+	}
133
+	if f.Id == 0 {
134
+		response.Fail(c, 101, "参数错误")
135
+		return
136
+	}
137
+	t := f.ToTag()
138
+	u := service.AllService.UserService.CurUser(c)
139
+	if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
140
+		response.Fail(c, 101, "无权限")
141
+		return
142
+	}
143
+	err := service.AllService.TagService.Update(t)
144
+	if err != nil {
145
+		response.Fail(c, 101, "更新失败")
146
+		return
147
+	}
148
+	response.Success(c, nil)
149
+}
150
+
151
+// Delete 删除
152
+// @Tags 标签
153
+// @Summary 标签删除
154
+// @Description 标签删除
155
+// @Accept  json
156
+// @Produce  json
157
+// @Param body body admin.TagForm true "标签信息"
158
+// @Success 200 {object} response.Response
159
+// @Failure 500 {object} response.Response
160
+// @Router /admin/tag/delete [post]
161
+// @Security token
162
+func (ct *Tag) Delete(c *gin.Context) {
163
+	f := &admin.TagForm{}
164
+	if err := c.ShouldBindJSON(f); err != nil {
165
+		response.Fail(c, 101, "系统错误")
166
+		return
167
+	}
168
+	id := f.Id
169
+	errList := global.Validator.ValidVar(id, "required,gt=0")
170
+	if len(errList) > 0 {
171
+		response.Fail(c, 101, errList[0])
172
+		return
173
+	}
174
+	t := service.AllService.TagService.InfoById(f.Id)
175
+	u := service.AllService.UserService.CurUser(c)
176
+	if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
177
+		response.Fail(c, 101, "无权限")
178
+		return
179
+	}
180
+	if u.Id > 0 {
181
+		err := service.AllService.TagService.Delete(t)
182
+		if err == nil {
183
+			response.Success(c, nil)
184
+			return
185
+		}
186
+		response.Fail(c, 101, err.Error())
187
+		return
188
+	}
189
+	response.Fail(c, 101, "信息不存在")
190
+}

+ 261 - 0
http/controller/admin/user.go

@@ -0,0 +1,261 @@
1
+package admin
2
+
3
+import (
4
+	"Gwen/global"
5
+	"Gwen/http/request/admin"
6
+	"Gwen/http/response"
7
+	adResp "Gwen/http/response/admin"
8
+	"Gwen/service"
9
+	"github.com/gin-gonic/gin"
10
+	"gorm.io/gorm"
11
+	"strconv"
12
+)
13
+
14
+type User struct {
15
+}
16
+
17
+// Detail 管理员
18
+// @Tags 用户
19
+// @Summary 管理员详情
20
+// @Description 管理员详情
21
+// @Accept  json
22
+// @Produce  json
23
+// @Param id path int true "ID"
24
+// @Success 200 {object} response.Response{data=model.User}
25
+// @Failure 500 {object} response.Response
26
+// @Router /admin/user/detail/{id} [get]
27
+// @Security token
28
+func (ct *User) Detail(c *gin.Context) {
29
+	id := c.Param("id")
30
+	iid, _ := strconv.Atoi(id)
31
+	u := service.AllService.UserService.InfoById(uint(iid))
32
+	if u.Id > 0 {
33
+		response.Success(c, u)
34
+		return
35
+	}
36
+	response.Fail(c, 101, "信息不存在")
37
+	return
38
+}
39
+
40
+// Create 管理员
41
+// @Tags 用户
42
+// @Summary 创建管理员
43
+// @Description 创建管理员
44
+// @Accept  json
45
+// @Produce  json
46
+// @Param body body admin.UserForm true "管理员信息"
47
+// @Success 200 {object} response.Response{data=model.User}
48
+// @Failure 500 {object} response.Response
49
+// @Router /admin/user/create [post]
50
+// @Security token
51
+func (ct *User) Create(c *gin.Context) {
52
+	f := &admin.UserForm{}
53
+	if err := c.ShouldBindJSON(f); err != nil {
54
+		response.Fail(c, 101, "参数错误")
55
+		return
56
+	}
57
+	errList := global.Validator.ValidStruct(f)
58
+	if len(errList) > 0 {
59
+		response.Fail(c, 101, errList[0])
60
+		return
61
+	}
62
+	u := f.ToUser()
63
+	err := service.AllService.UserService.Create(u)
64
+	if err != nil {
65
+		response.Fail(c, 101, "创建失败")
66
+		return
67
+	}
68
+	response.Success(c, u)
69
+}
70
+
71
+// List 列表
72
+// @Tags 用户
73
+// @Summary 管理员列表
74
+// @Description 管理员列表
75
+// @Accept  json
76
+// @Produce  json
77
+// @Param page query int false "页码"
78
+// @Param page_size query int false "页大小"
79
+// @Param username query int false "账户"
80
+// @Success 200 {object} response.Response{data=model.UserList}
81
+// @Failure 500 {object} response.Response
82
+// @Router /admin/user/list [get]
83
+// @Security token
84
+func (ct *User) List(c *gin.Context) {
85
+	query := &admin.UserQuery{}
86
+	if err := c.ShouldBindQuery(query); err != nil {
87
+		response.Fail(c, 101, "参数错误")
88
+		return
89
+	}
90
+	res := service.AllService.UserService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
91
+		if query.Username != "" {
92
+			tx.Where("username like ?", "%"+query.Username+"%")
93
+		}
94
+	})
95
+	response.Success(c, res)
96
+}
97
+
98
+// Update 编辑
99
+// @Tags 用户
100
+// @Summary 管理员编辑
101
+// @Description 管理员编辑
102
+// @Accept  json
103
+// @Produce  json
104
+// @Param body body admin.UserForm true "用户信息"
105
+// @Success 200 {object} response.Response{data=model.User}
106
+// @Failure 500 {object} response.Response
107
+// @Router /admin/user/update [post]
108
+// @Security token
109
+func (ct *User) Update(c *gin.Context) {
110
+	f := &admin.UserForm{}
111
+	if err := c.ShouldBindJSON(f); err != nil {
112
+		response.Fail(c, 101, "参数错误:"+err.Error())
113
+		return
114
+	}
115
+	if f.Id == 0 {
116
+		response.Fail(c, 101, "参数错误")
117
+		return
118
+	}
119
+	errList := global.Validator.ValidStruct(f)
120
+	if len(errList) > 0 {
121
+		response.Fail(c, 101, errList[0])
122
+		return
123
+	}
124
+	u := f.ToUser()
125
+	err := service.AllService.UserService.Update(u)
126
+	if err != nil {
127
+		response.Fail(c, 101, "更新失败")
128
+		return
129
+	}
130
+	response.Success(c, nil)
131
+}
132
+
133
+// Delete 删除
134
+// @Tags 用户
135
+// @Summary 管理员删除
136
+// @Description 管理员编删除
137
+// @Accept  json
138
+// @Produce  json
139
+// @Param body body admin.UserForm true "用户信息"
140
+// @Success 200 {object} response.Response
141
+// @Failure 500 {object} response.Response
142
+// @Router /admin/user/delete [post]
143
+// @Security token
144
+func (ct *User) Delete(c *gin.Context) {
145
+	f := &admin.UserForm{}
146
+	if err := c.ShouldBindJSON(f); err != nil {
147
+		response.Fail(c, 101, "系统错误")
148
+		return
149
+	}
150
+	id := f.Id
151
+	errList := global.Validator.ValidVar(id, "required,gt=0")
152
+	if len(errList) > 0 {
153
+		response.Fail(c, 101, errList[0])
154
+		return
155
+	}
156
+	u := service.AllService.UserService.InfoById(f.Id)
157
+	if u.Id > 0 {
158
+		err := service.AllService.UserService.Delete(u)
159
+		if err == nil {
160
+			response.Success(c, nil)
161
+			return
162
+		}
163
+		response.Fail(c, 101, err.Error())
164
+		return
165
+	}
166
+	response.Fail(c, 101, "信息不存在")
167
+}
168
+
169
+// UpdatePassword 修改密码
170
+// @Tags 用户
171
+// @Summary 修改密码
172
+// @Description 修改密码
173
+// @Accept  json
174
+// @Produce  json
175
+// @Param body body admin.UserPasswordForm true "用户信息"
176
+// @Success 200 {object} response.Response
177
+// @Failure 500 {object} response.Response
178
+// @Router /admin/user/updatePassword [post]
179
+// @Security token
180
+func (ct *User) UpdatePassword(c *gin.Context) {
181
+	f := &admin.UserPasswordForm{}
182
+	if err := c.ShouldBindJSON(f); err != nil {
183
+		response.Fail(c, 101, "参数错误")
184
+		return
185
+	}
186
+	errList := global.Validator.ValidStruct(f)
187
+	if len(errList) > 0 {
188
+		response.Fail(c, 101, errList[0])
189
+		return
190
+	}
191
+	u := service.AllService.UserService.InfoById(f.Id)
192
+	if u.Id == 0 {
193
+		response.Fail(c, 101, "信息不存在")
194
+		return
195
+	}
196
+	err := service.AllService.UserService.UpdatePassword(u, f.Password)
197
+	if err != nil {
198
+		response.Fail(c, 101, "更新失败")
199
+		return
200
+	}
201
+	response.Success(c, nil)
202
+}
203
+
204
+// Current 当前用户
205
+// @Tags 用户
206
+// @Summary 当前用户
207
+// @Description 当前用户
208
+// @Accept  json
209
+// @Produce  json
210
+// @Success 200 {object} response.Response{data=adResp.LoginPayload}
211
+// @Failure 500 {object} response.Response
212
+// @Router /admin/user/current [get]
213
+// @Security token
214
+func (ct *User) Current(c *gin.Context) {
215
+	u := service.AllService.UserService.CurUser(c)
216
+	token, _ := c.Get("token")
217
+	t := token.(string)
218
+	response.Success(c, &adResp.LoginPayload{
219
+		Token:      t,
220
+		Username:   u.Username,
221
+		RouteNames: service.AllService.UserService.RouteNames(u),
222
+		Nickname:   u.Nickname,
223
+	})
224
+}
225
+
226
+// ChangeCurPwd 修改当前用户密码
227
+// @Tags 用户
228
+// @Summary 修改当前用户密码
229
+// @Description 修改当前用户密码
230
+// @Accept  json
231
+// @Produce  json
232
+// @Param body body admin.ChangeCurPasswordForm true "用户信息"
233
+// @Success 200 {object} response.Response
234
+// @Failure 500 {object} response.Response
235
+// @Router /admin/user/changeCurPwd [post]
236
+// @Security token
237
+func (ct *User) ChangeCurPwd(c *gin.Context) {
238
+	f := &admin.ChangeCurPasswordForm{}
239
+	if err := c.ShouldBindJSON(f); err != nil {
240
+		response.Fail(c, 101, "参数错误")
241
+		return
242
+	}
243
+
244
+	errList := global.Validator.ValidStruct(f)
245
+	if len(errList) > 0 {
246
+		response.Fail(c, 101, errList[0])
247
+		return
248
+	}
249
+	u := service.AllService.UserService.CurUser(c)
250
+	oldPwd := service.AllService.UserService.EncryptPassword(f.OldPassword)
251
+	if u.Password != oldPwd {
252
+		response.Fail(c, 101, "旧密码错误")
253
+		return
254
+	}
255
+	err := service.AllService.UserService.UpdatePassword(u, f.NewPassword)
256
+	if err != nil {
257
+		response.Fail(c, 101, "更新失败")
258
+		return
259
+	}
260
+	response.Success(c, nil)
261
+}

+ 150 - 0
http/controller/api/ab.go

@@ -0,0 +1,150 @@
1
+package api
2
+
3
+import (
4
+	requstform "Gwen/http/request/api"
5
+	"Gwen/http/response"
6
+	"Gwen/http/response/api"
7
+	"Gwen/model"
8
+	"Gwen/service"
9
+	"encoding/json"
10
+	"fmt"
11
+	"github.com/gin-gonic/gin"
12
+	"net/http"
13
+)
14
+
15
+type Ab struct {
16
+}
17
+
18
+// Ab
19
+// @Tags 地址
20
+// @Summary 地址列表
21
+// @Description 地址列表
22
+// @Accept  json
23
+// @Produce  json
24
+// @Success 200 {object} response.Response
25
+// @Failure 500 {object} response.ErrorResponse
26
+// @Router /ab [get]
27
+// @Security BearerAuth
28
+func (a *Ab) Ab(c *gin.Context) {
29
+	user := service.AllService.UserService.CurUser(c)
30
+
31
+	al := service.AllService.AddressBookService.ListByUserId(user.Id, 1, 1000)
32
+	tags := service.AllService.TagService.ListByUserId(user.Id)
33
+
34
+	tagColors := map[string]uint{}
35
+	//将tags中的name转成一个以逗号分割的字符串
36
+	var tagNames []string
37
+	for _, tag := range tags.Tags {
38
+		tagNames = append(tagNames, tag.Name)
39
+		tagColors[tag.Name] = tag.Color
40
+	}
41
+	tgc, _ := json.Marshal(tagColors)
42
+	res := &api.AbList{
43
+		Peers:     al.AddressBooks,
44
+		Tags:      tagNames,
45
+		TagColors: string(tgc),
46
+	}
47
+	data, _ := json.Marshal(res)
48
+	c.JSON(http.StatusOK, gin.H{
49
+		"data": string(data),
50
+		//"licensed_devices": 999,
51
+	})
52
+}
53
+
54
+// UpAb
55
+// @Tags 地址
56
+// @Summary 地址更新
57
+// @Description 地址更新
58
+// @Accept  json
59
+// @Produce  json
60
+// @Param body body requstform.AddressBookForm true "地址表单"
61
+// @Success 200 {string} string "null"
62
+// @Failure 500 {object} response.ErrorResponse
63
+// @Router /ab [post]
64
+// @Security BearerAuth
65
+func (a *Ab) UpAb(c *gin.Context) {
66
+	abf := &requstform.AddressBookForm{}
67
+	err := c.ShouldBindJSON(&abf)
68
+	if err != nil {
69
+		fmt.Println(err)
70
+		response.Error(c, "参数错误")
71
+		return
72
+	}
73
+	abd := &requstform.AddressBookFormData{}
74
+	err = json.Unmarshal([]byte(abf.Data), abd)
75
+	if err != nil {
76
+		response.Error(c, "系统错误")
77
+		return
78
+	}
79
+
80
+	//fmt.Println(abd)
81
+	//for _, peer := range abd.Peers {
82
+	//	fmt.Println(peer)
83
+	//}
84
+
85
+	user := service.AllService.UserService.CurUser(c)
86
+
87
+	err = service.AllService.AddressBookService.UpdateAddressBook(abd.Peers, user.Id)
88
+	if err != nil {
89
+		c.Abort()
90
+		return
91
+	}
92
+
93
+	tc := map[string]uint{}
94
+	err = json.Unmarshal([]byte(abd.TagColors), &tc)
95
+	if err != nil {
96
+		fmt.Println(err)
97
+		response.Error(c, "系统错误")
98
+		return
99
+	} else {
100
+		service.AllService.TagService.UpdateTags(user.Id, tc)
101
+	}
102
+
103
+	c.JSON(http.StatusOK, nil)
104
+}
105
+
106
+// Tags
107
+// @Tags 地址
108
+// @Summary 标签
109
+// @Description 标签
110
+// @Accept  json
111
+// @Produce  json
112
+// @Success 200 {object} []model.Tag
113
+// @Failure 500 {object} response.ErrorResponse
114
+// @Router /tags [post]
115
+// @Security BearerAuth
116
+func (a *Ab) Tags(c *gin.Context) {
117
+	user := service.AllService.UserService.CurUser(c)
118
+
119
+	tags := service.AllService.TagService.ListByUserId(user.Id)
120
+	c.JSON(http.StatusOK, tags.Tags)
121
+}
122
+
123
+// TagAdd
124
+// @Tags 地址
125
+// @Summary 标签添加
126
+// @Description 标签
127
+// @Accept  json
128
+// @Produce  json
129
+// @Success 200 {string} string
130
+// @Failure 500 {object} response.ErrorResponse
131
+// @Router /ab/add [post]
132
+// @Security BearerAuth
133
+func (a *Ab) TagAdd(c *gin.Context) {
134
+	t := &model.Tag{}
135
+	err := c.ShouldBindJSON(t)
136
+	if err != nil {
137
+		fmt.Println(err)
138
+		response.Error(c, "参数错误")
139
+		return
140
+
141
+	}
142
+	//u := service.AllService.UserService.CurUser(c)
143
+
144
+	//err = service.AllService.TagService.UpdateTags(t.Name, t.Color, user.Id)
145
+	//if err != nil {
146
+	//	response.Error(c, "操作失败")
147
+	//	return
148
+	//}
149
+	c.JSON(http.StatusOK, "")
150
+}

+ 115 - 0
http/controller/api/group.go

@@ -0,0 +1,115 @@
1
+package api
2
+
3
+import (
4
+	apiReq "Gwen/http/request/api"
5
+	"Gwen/http/response"
6
+	apiResp "Gwen/http/response/api"
7
+	"Gwen/model"
8
+	"Gwen/service"
9
+	"github.com/gin-gonic/gin"
10
+	"net/http"
11
+)
12
+
13
+type Group struct {
14
+}
15
+
16
+// Users 用户列表
17
+// @Tags 群组
18
+// @Summary 用户列表
19
+// @Description 用户列表
20
+// @Accept  json
21
+// @Produce  json
22
+// @Param page query int false "页码"
23
+// @Param pageSize query int false "每页数量"
24
+// @Param status query int false "状态"
25
+// @Param accessible query string false "accessible"
26
+// @Success 200 {object} response.DataResponse{data=[]apiResp.UserPayload}
27
+// @Failure 500 {object} response.ErrorResponse
28
+// @Router /users [get]
29
+// @Security BearerAuth
30
+func (g *Group) Users(c *gin.Context) {
31
+	u := service.AllService.UserService.CurUser(c)
32
+
33
+	if !*u.IsAdmin {
34
+		gr := service.AllService.GroupService.InfoById(u.GroupId)
35
+		if gr.Type != model.GroupTypeShare {
36
+			response.Error(c, "不是管理员也不在分享组")
37
+			return
38
+		}
39
+	}
40
+
41
+	q := &apiReq.UserListQuery{}
42
+	err := c.ShouldBindQuery(&q)
43
+	if err != nil {
44
+		response.Error(c, err.Error())
45
+		return
46
+	}
47
+	userList := service.AllService.UserService.ListByGroupId(u.GroupId, q.Page, q.PageSize)
48
+	var data []*apiResp.UserPayload
49
+	for _, user := range userList.Users {
50
+		up := &apiResp.UserPayload{}
51
+		up.FromUser(user)
52
+		data = append(data, up)
53
+	}
54
+	c.JSON(http.StatusOK, response.DataResponse{
55
+		Total: uint(userList.Total),
56
+		Data:  data,
57
+	})
58
+}
59
+
60
+// Peers
61
+// @Tags 群组
62
+// @Summary 机器
63
+// @Description 机器
64
+// @Accept  json
65
+// @Produce  json
66
+// @Param page query int false "页码"
67
+// @Param pageSize query int false "每页数量"
68
+// @Param status query int false "状态"
69
+// @Param accessible query string false "accessible"
70
+// @Success 200 {object} response.DataResponse
71
+// @Failure 500 {object} response.Response
72
+// @Router /peers [get]
73
+// @Security BearerAuth
74
+func (g *Group) Peers(c *gin.Context) {
75
+	u := service.AllService.UserService.CurUser(c)
76
+
77
+	if !*u.IsAdmin {
78
+		gr := service.AllService.GroupService.InfoById(u.GroupId)
79
+		if gr.Type != model.GroupTypeShare {
80
+			response.Error(c, "不是管理员也不在分享组")
81
+			return
82
+		}
83
+	}
84
+
85
+	q := &apiReq.PeerListQuery{}
86
+	err := c.ShouldBindQuery(&q)
87
+	if err != nil {
88
+		response.Error(c, err.Error())
89
+		return
90
+	}
91
+
92
+	users := service.AllService.UserService.ListIdAndNameByGroupId(u.GroupId)
93
+	namesById := make(map[uint]string)
94
+	userIds := make([]uint, 0)
95
+	for _, user := range users {
96
+		namesById[user.Id] = user.Username
97
+		userIds = append(userIds, user.Id)
98
+	}
99
+	peerList := service.AllService.AddressBookService.ListByUserIds(userIds, q.Page, q.PageSize)
100
+	var data []*apiResp.GroupPeerPayload
101
+	for _, ab := range peerList.AddressBooks {
102
+		uname, ok := namesById[ab.UserId]
103
+		if !ok {
104
+			uname = ""
105
+		}
106
+		pp := &apiResp.GroupPeerPayload{}
107
+		pp.FromAddressBook(ab, uname)
108
+		data = append(data, pp)
109
+
110
+	}
111
+	c.JSON(http.StatusOK, response.DataResponse{
112
+		Total: uint(peerList.Total),
113
+		Data:  data,
114
+	})
115
+}

+ 39 - 0
http/controller/api/index.go

@@ -0,0 +1,39 @@
1
+package api
2
+
3
+import (
4
+	"Gwen/http/response"
5
+	"github.com/gin-gonic/gin"
6
+	"net/http"
7
+)
8
+
9
+type Index struct {
10
+}
11
+
12
+// Index 首页
13
+// @Tags 首页
14
+// @Summary 首页
15
+// @Description 首页
16
+// @Accept  json
17
+// @Produce  json
18
+// @Success 200 {object} response.Response
19
+// @Failure 500 {object} response.Response
20
+// @Router / [get]
21
+func (i *Index) Index(c *gin.Context) {
22
+	response.Success(
23
+		c,
24
+		"Hello Gwen",
25
+	)
26
+}
27
+
28
+// Heartbeat 心跳
29
+// @Tags 首页
30
+// @Summary 心跳
31
+// @Description 心跳
32
+// @Accept  json
33
+// @Produce  json
34
+// @Success 200 {object} nil
35
+// @Failure 500 {object} response.Response
36
+// @Router /heartbeat [post]
37
+func (i *Index) Heartbeat(c *gin.Context) {
38
+	c.JSON(http.StatusOK, gin.H{})
39
+}

+ 90 - 0
http/controller/api/login.go

@@ -0,0 +1,90 @@
1
+package api
2
+
3
+import (
4
+	"Gwen/global"
5
+	"Gwen/http/request/api"
6
+	"Gwen/http/response"
7
+	apiResp "Gwen/http/response/api"
8
+	"Gwen/service"
9
+	"github.com/gin-gonic/gin"
10
+	"net/http"
11
+)
12
+
13
+type Login struct {
14
+}
15
+
16
+// Login 登录
17
+// @Tags 登录
18
+// @Summary 登录
19
+// @Description 登录
20
+// @Accept  json
21
+// @Produce  json
22
+// @Param body body api.LoginForm true "登录表单"
23
+// @Success 200 {object} apiResp.LoginRes
24
+// @Failure 500 {object} response.ErrorResponse
25
+// @Router /login [post]
26
+func (l *Login) Login(c *gin.Context) {
27
+	f := &api.LoginForm{}
28
+	err := c.ShouldBindJSON(f)
29
+	if err != nil {
30
+		response.Error(c, "系统错误")
31
+		return
32
+	}
33
+
34
+	errList := global.Validator.ValidStruct(f)
35
+	if len(errList) > 0 {
36
+		response.Error(c, errList[0])
37
+		return
38
+	}
39
+
40
+	u := service.AllService.UserService.InfoByUsernamePassword(f.Username, f.Password)
41
+
42
+	if u.Id == 0 {
43
+		response.Error(c, "用户名或密码错误")
44
+		return
45
+	}
46
+
47
+	ut := service.AllService.UserService.Login(u)
48
+
49
+	c.JSON(http.StatusOK, apiResp.LoginRes{
50
+		AccessToken: ut.Token,
51
+		Type:        "access_token",
52
+		User:        *(&apiResp.UserPayload{}).FromUser(u),
53
+	})
54
+}
55
+
56
+// LoginOptions
57
+// @Tags 登录
58
+// @Summary 登录选项
59
+// @Description 登录选项
60
+// @Accept  json
61
+// @Produce  json
62
+// @Success 200 {object} []string
63
+// @Failure 500 {object} response.ErrorResponse
64
+// @Router /login-options [post]
65
+func (l *Login) LoginOptions(c *gin.Context) {
66
+	test := []string{
67
+		//"common-oidc/[{\"name\":\"google\"},{\"name\":\"github\"},{\"name\":\"facebook\"},{\"name\":\"网页授权登录\",\"icon\":\"\"}]",
68
+		//"oidc/myapp",
69
+	}
70
+	c.JSON(http.StatusOK, test)
71
+}
72
+
73
+// Logout
74
+// @Tags 登录
75
+// @Summary 登出
76
+// @Description 登出
77
+// @Accept  json
78
+// @Produce  json
79
+// @Success 200 {string} string
80
+// @Failure 500 {object} response.ErrorResponse
81
+// @Router /logout [post]
82
+func (l *Login) Logout(c *gin.Context) {
83
+	u := service.AllService.UserService.CurUser(c)
84
+	token, ok := c.Get("token")
85
+	if ok {
86
+		service.AllService.UserService.Logout(u, token.(string))
87
+	}
88
+	c.JSON(http.StatusOK, nil)
89
+
90
+}

+ 48 - 0
http/controller/api/peer.go

@@ -0,0 +1,48 @@
1
+package api
2
+
3
+import (
4
+	requstform "Gwen/http/request/api"
5
+	"Gwen/http/response"
6
+	"Gwen/service"
7
+	"github.com/gin-gonic/gin"
8
+	"github.com/gin-gonic/gin/binding"
9
+	"net/http"
10
+)
11
+
12
+type Peer struct {
13
+}
14
+
15
+// SysInfo
16
+// @Tags 地址
17
+// @Summary 提交系统信息
18
+// @Description 提交系统信息
19
+// @Accept  json
20
+// @Produce  json
21
+// @Param body body requstform.PeerForm true "系统信息表单"
22
+// @Success 200 {string} string "SYSINFO_UPDATED,ID_NOT_FOUND"
23
+// @Failure 500 {object} response.ErrorResponse
24
+// @Router /sysinfo [post]
25
+// @Security BearerAuth
26
+func (p *Peer) SysInfo(c *gin.Context) {
27
+	f := &requstform.PeerForm{}
28
+	err := c.ShouldBindBodyWith(f, binding.JSON)
29
+	if err != nil {
30
+		response.Error(c, err.Error())
31
+		return
32
+	}
33
+
34
+	pe := service.AllService.PeerService.FindById(f.Id)
35
+	if pe == nil || pe.RowId == 0 {
36
+		pe = f.ToPeer()
37
+		err = service.AllService.PeerService.Create(pe)
38
+		if err != nil {
39
+			response.Error(c, err.Error())
40
+			return
41
+		}
42
+	}
43
+
44
+	//SYSINFO_UPDATED 上传成功
45
+	//ID_NOT_FOUND 下次心跳会上传
46
+	//直接响应文本
47
+	c.String(http.StatusOK, "")
48
+}

+ 74 - 0
http/controller/api/user.go

@@ -0,0 +1,74 @@
1
+package api
2
+
3
+import (
4
+	apiResp "Gwen/http/response/api"
5
+	"Gwen/service"
6
+	"fmt"
7
+	"github.com/gin-gonic/gin"
8
+	"net/http"
9
+)
10
+
11
+type User struct {
12
+}
13
+
14
+// currentUser 当前用户
15
+// @Tags 用户
16
+// @Summary 用户信息
17
+// @Description 用户信息
18
+// @Accept  json
19
+// @Produce  json
20
+// @Success 200 {object} apiResp.UserPayload
21
+// @Failure 500 {object} response.Response
22
+// @Router /currentUser [get]
23
+// @Security token
24
+func (u *User) currentUser(c *gin.Context) {
25
+	user := service.AllService.UserService.CurUser(c)
26
+	up := (&apiResp.UserPayload{}).FromUser(user)
27
+	c.JSON(http.StatusOK, up)
28
+}
29
+
30
+// Info 用户信息
31
+// @Tags 用户
32
+// @Summary 用户信息
33
+// @Description 用户信息
34
+// @Accept  json
35
+// @Produce  json
36
+// @Success 200 {object} apiResp.UserPayload
37
+// @Failure 500 {object} response.Response
38
+// @Router /api [get]
39
+// @Security token
40
+func (u *User) Info(c *gin.Context) {
41
+	user := service.AllService.UserService.CurUser(c)
42
+	up := (&apiResp.UserPayload{}).FromUser(user)
43
+	c.JSON(http.StatusOK, up)
44
+}
45
+
46
+// Personal
47
+// @Tags 用户
48
+// @Summary 个人信息
49
+// @Description 个人信息
50
+// @Accept  json
51
+// @Produce  json
52
+// @Param string body string false  "string valid"
53
+// @Success 200 {object} response.Response
54
+// @Failure 500 {object} response.Response
55
+// @Router /ab/personal [post]
56
+// @Security BearerAuth
57
+func (u *User) Personal(c *gin.Context) {
58
+	//打印全部body
59
+	fmt.Println(c.Request.Body)
60
+
61
+	/**
62
+	guid = json['guid'] ?? '',
63
+	       name = json['name'] ?? '',
64
+	       owner = json['owner'] ?? '',
65
+	       note = json['note'] ?? '',
66
+	       rule = json['rule'] ?? 0;
67
+	*/
68
+	//如果返回了guid,后面的请求会有变化
69
+	c.JSON(http.StatusOK, gin.H{
70
+		//"guid": "123456",
71
+		//"name": "admindddd",
72
+		//"rule": 1,
73
+	})
74
+}

+ 42 - 0
http/controller/api/webClient.go

@@ -0,0 +1,42 @@
1
+package api
2
+
3
+import (
4
+	"Gwen/global"
5
+	"Gwen/http/response"
6
+	"Gwen/http/response/api"
7
+	"Gwen/service"
8
+	"github.com/gin-gonic/gin"
9
+)
10
+
11
+type WebClient struct {
12
+}
13
+
14
+// ServerConfig 服务配置
15
+// @Tags WEBCLIENT
16
+// @Summary 服务配置
17
+// @Description 服务配置,给webclient提供api-server
18
+// @Accept  json
19
+// @Produce  json
20
+// @Success 200 {object} response.Response
21
+// @Failure 500 {object} response.Response
22
+// @Router /server-config [get]
23
+// @Security token
24
+func (i *WebClient) ServerConfig(c *gin.Context) {
25
+	u := service.AllService.UserService.CurUser(c)
26
+
27
+	peers := map[string]*api.WebClientPeerPayload{}
28
+	abs := service.AllService.AddressBookService.ListByUserId(u.Id, 1, 100)
29
+	for _, ab := range abs.AddressBooks {
30
+		pp := &api.WebClientPeerPayload{}
31
+		pp.FromAddressBook(ab)
32
+		peers[ab.Id] = pp
33
+	}
34
+	response.Success(
35
+		c,
36
+		gin.H{
37
+			"id_server": global.Config.Rustdesk.IdServer,
38
+			"key":       global.Config.Rustdesk.Key,
39
+			//"peers":     peers,
40
+		},
41
+	)
42
+}

+ 68 - 0
http/controller/web/index.go

@@ -0,0 +1,68 @@
1
+package web
2
+
3
+import (
4
+	"Gwen/global"
5
+	"github.com/gin-gonic/gin"
6
+)
7
+
8
+type Index struct {
9
+}
10
+
11
+func (i *Index) ConfigJs(c *gin.Context) {
12
+	apiServer := global.Config.Rustdesk.ApiServer
13
+
14
+	tmp := `
15
+ 	  window._gwen = {}
16
+      window._gwen.kv = {}
17
+      function getQueryVariable() {
18
+          const query = window.location.hash.substring(3);
19
+          const vars = query.split("&");
20
+          for (var i = 0; i < vars.length; i++) {
21
+              var pair = vars[i].split("=");
22
+              window._gwen.kv[pair[0]] = pair[1]
23
+          }
24
+      }
25
+      getQueryVariable()
26
+      const id = window._gwen.kv.id || ''
27
+      if (id) {
28
+        localStorage.setItem('remote-id', id)
29
+      }
30
+      window._gwen.hosts = [
31
+        "rs-sg.rustdesk.com",
32
+        "rs-cn.rustdesk.com",
33
+        "rs-us.rustdesk.com",
34
+      ]
35
+localStorage.setItem('api-server', "` + apiServer + `")
36
+const autoWriteServer = () => {
37
+          return setTimeout(() => {
38
+              const token = localStorage.getItem('access_token')
39
+              const apiserver = localStorage.getItem('api-server')
40
+              if (token && apiserver) {
41
+                  fetch(apiserver + "/api/server-config", {
42
+                          method: 'POST',
43
+                          headers: {
44
+                              'Content-Type': 'application/json',
45
+                              'Authorization': 'Bearer ' + token
46
+                          }
47
+                      }
48
+                  ).then(res => res.json()).then(res => {
49
+                      if (res.code === 0) {
50
+						  if(!localStorage.getItem('custom-rendezvous-server')  || !localStorage.getItem('key') ) {
51
+	 						localStorage.setItem('custom-rendezvous-server', res.data.id_server)
52
+							localStorage.setItem('key', res.data.key)
53
+							}	
54
+                         
55
+						  if (res.data.peers) {
56
+							  localStorage.setItem('peers', JSON.stringify(res.data.peers))
57
+					      }
58
+                      }
59
+                  })
60
+              } else {
61
+                  autoWriteServer()
62
+              }
63
+          }, 1000)
64
+      }
65
+		autoWriteServer()
66
+`
67
+	c.String(200, tmp)
68
+}

+ 30 - 0
http/http.go

@@ -0,0 +1,30 @@
1
+package http
2
+
3
+import (
4
+	"Gwen/global"
5
+	"Gwen/http/middleware"
6
+	"Gwen/http/router"
7
+	"github.com/gin-gonic/gin"
8
+	"github.com/sirupsen/logrus"
9
+	"net/http"
10
+)
11
+
12
+func ApiInit() {
13
+	gin.SetMode(global.Config.Gin.Mode)
14
+	g := gin.New()
15
+
16
+	if global.Config.Gin.Mode == gin.ReleaseMode {
17
+		//修改gin Recovery日志 输出为logger的输出点
18
+		if global.Logger != nil {
19
+			gin.DefaultErrorWriter = global.Logger.WriterLevel(logrus.ErrorLevel)
20
+		}
21
+	}
22
+	g.NoRoute(func(c *gin.Context) {
23
+		c.String(http.StatusNotFound, "404 not found")
24
+	})
25
+	g.Use(middleware.Logger(), gin.Recovery())
26
+	router.WebInit(g)
27
+	router.Init(g)
28
+	router.ApiInit(g)
29
+	Run(g, global.Config.Gin.ApiAddr)
30
+}

+ 32 - 0
http/middleware/admin.go

@@ -0,0 +1,32 @@
1
+package middleware
2
+
3
+import (
4
+	"Gwen/http/response"
5
+	"Gwen/service"
6
+	"github.com/gin-gonic/gin"
7
+)
8
+
9
+// AdminAuth 后台权限验证中间件
10
+func AdminAuth() gin.HandlerFunc {
11
+	return func(c *gin.Context) {
12
+
13
+		//测试先关闭
14
+		token := c.GetHeader("api-token")
15
+		if token == "" {
16
+			response.Fail(c, 403, "请先登录")
17
+			c.Abort()
18
+			return
19
+		}
20
+		user := service.AllService.UserService.InfoByAccessToken(token)
21
+		if user.Id == 0 {
22
+			response.Fail(c, 403, "请先登录")
23
+			c.Abort()
24
+			return
25
+		}
26
+
27
+		c.Set("curUser", user)
28
+		c.Set("token", token)
29
+
30
+		c.Next()
31
+	}
32
+}

+ 22 - 0
http/middleware/admin_privilege.go

@@ -0,0 +1,22 @@
1
+package middleware
2
+
3
+import (
4
+	"Gwen/http/response"
5
+	"Gwen/service"
6
+	"github.com/gin-gonic/gin"
7
+)
8
+
9
+// AdminPrivilege ...
10
+func AdminPrivilege() gin.HandlerFunc {
11
+	return func(c *gin.Context) {
12
+		u := service.AllService.UserService.CurUser(c)
13
+
14
+		if !service.AllService.UserService.IsAdmin(u) {
15
+			response.Fail(c, 403, "无权限")
16
+			c.Abort()
17
+			return
18
+		}
19
+
20
+		c.Next()
21
+	}
22
+}

+ 23 - 0
http/middleware/cors.go

@@ -0,0 +1,23 @@
1
+package middleware
2
+
3
+import (
4
+	"github.com/gin-gonic/gin"
5
+	"net/http"
6
+)
7
+
8
+// Cors 跨域
9
+func Cors() gin.HandlerFunc {
10
+	return func(c *gin.Context) {
11
+		origin := c.GetHeader("Origin")
12
+		//fmt.Println("origin", origin)
13
+		c.Header("Access-Control-Allow-Origin", origin)
14
+		c.Header("Access-Control-Allow-Headers", "api-token,content-type,authorization ")
15
+		c.Header("Access-Control-Allow-Methods", c.Request.Method)
16
+		c.Header("Access-Control-Allow-Credentials", "true")
17
+		if c.Request.Method == "OPTIONS" {
18
+			c.AbortWithStatus(http.StatusNoContent)
19
+			return
20
+		}
21
+		c.Next()
22
+	}
23
+}

+ 50 - 0
http/middleware/jwt.go

@@ -0,0 +1,50 @@
1
+package middleware
2
+
3
+import (
4
+	"Gwen/global"
5
+	"Gwen/http/response"
6
+	"Gwen/service"
7
+	"github.com/gin-gonic/gin"
8
+)
9
+
10
+func JwtAuth() gin.HandlerFunc {
11
+	return func(c *gin.Context) {
12
+		//测试先关闭
13
+		token := c.GetHeader("api-token")
14
+		if token == "" {
15
+			response.Fail(c, 403, "请先登录")
16
+			c.Abort()
17
+			return
18
+		}
19
+		uid, err := global.Jwt.ParseToken(token)
20
+		if err != nil {
21
+			response.Fail(c, 403, "请先登录")
22
+			c.Abort()
23
+			return
24
+		}
25
+		if uid == 0 {
26
+			response.Fail(c, 403, "请先登录")
27
+			c.Abort()
28
+			return
29
+		}
30
+
31
+		user := service.AllService.UserService.InfoById(uid)
32
+		//user := &model.User{
33
+		//	Id:       uid,
34
+		//	Username: "测试用户",
35
+		//}
36
+		if user.Id == 0 {
37
+			response.Fail(c, 403, "请先登录")
38
+			c.Abort()
39
+			return
40
+		}
41
+		if !service.AllService.UserService.CheckUserEnable(user) {
42
+			response.Fail(c, 101, "你已被禁用")
43
+			c.Abort()
44
+			return
45
+		}
46
+		c.Set("curUser", user)
47
+
48
+		c.Next()
49
+	}
50
+}

+ 20 - 0
http/middleware/logger.go

@@ -0,0 +1,20 @@
1
+package middleware
2
+
3
+import (
4
+	"Gwen/global"
5
+	"github.com/gin-gonic/gin"
6
+	"github.com/sirupsen/logrus"
7
+)
8
+
9
+// Logger 日志中间件
10
+func Logger() gin.HandlerFunc {
11
+	return func(c *gin.Context) {
12
+		global.Logger.WithFields(
13
+			logrus.Fields{
14
+				"uri":    c.Request.URL,
15
+				"ip":     c.ClientIP(),
16
+				"method": c.Request.Method,
17
+			}).Debug("Request")
18
+		c.Next()
19
+	}
20
+}

+ 44 - 0
http/middleware/rustauth.go

@@ -0,0 +1,44 @@
1
+package middleware
2
+
3
+import (
4
+	"Gwen/service"
5
+	"github.com/gin-gonic/gin"
6
+)
7
+
8
+func RustAuth() gin.HandlerFunc {
9
+	return func(c *gin.Context) {
10
+
11
+		//获取HTTP_AUTHORIZATION
12
+		token := c.GetHeader("Authorization")
13
+		if token == "" {
14
+			c.JSON(401, gin.H{
15
+				"error": "Unauthorized",
16
+			})
17
+			c.Abort()
18
+			return
19
+		}
20
+		//提取token,格式是Bearer {token}
21
+		//这里只是简单的提取
22
+		token = token[7:]
23
+		//验证token
24
+		user := service.AllService.UserService.InfoByAccessToken(token)
25
+		if user.Id == 0 {
26
+			c.JSON(401, gin.H{
27
+				"error": "Unauthorized",
28
+			})
29
+			c.Abort()
30
+			return
31
+		}
32
+		if !service.AllService.UserService.CheckUserEnable(user) {
33
+			c.JSON(401, gin.H{
34
+				"error": "账号已被禁用",
35
+			})
36
+			c.Abort()
37
+			return
38
+		}
39
+
40
+		c.Set("curUser", user)
41
+		c.Set("token", token)
42
+		c.Next()
43
+	}
44
+}

+ 56 - 0
http/request/admin/addressBook.go

@@ -0,0 +1,56 @@
1
+package admin
2
+
3
+import (
4
+	"Gwen/model"
5
+	"encoding/json"
6
+)
7
+
8
+type AddressBookForm struct {
9
+	RowId            uint     `json:"row_id"`
10
+	Id               string   `json:"id" validate:"required"`
11
+	Username         string   `json:"username" `
12
+	Password         string   `json:"password" `
13
+	Hostname         string   `json:"hostname" `
14
+	Alias            string   `json:"alias" `
15
+	Platform         string   `json:"platform" `
16
+	Tags             []string `json:"tags"`
17
+	Hash             string   `json:"hash"`
18
+	UserId           uint     `json:"user_id"`
19
+	ForceAlwaysRelay bool     `json:"force_always_relay"`
20
+	RdpPort          string   `json:"rdp_port"`
21
+	RdpUsername      string   `json:"rdp_username"`
22
+	Online           bool     `json:"online"`
23
+	LoginName        string   `json:"login_name" `
24
+	SameServer       bool     `json:"same_server"`
25
+}
26
+
27
+func (a AddressBookForm) ToAddressBook() *model.AddressBook {
28
+	//tags转换
29
+	tags, _ := json.Marshal(a.Tags)
30
+
31
+	return &model.AddressBook{
32
+		RowId:            a.RowId,
33
+		Id:               a.Id,
34
+		Username:         a.Username,
35
+		Password:         a.Password,
36
+		Hostname:         a.Hostname,
37
+		Alias:            a.Alias,
38
+		Platform:         a.Platform,
39
+		Tags:             tags,
40
+		Hash:             a.Hash,
41
+		UserId:           a.UserId,
42
+		ForceAlwaysRelay: a.ForceAlwaysRelay,
43
+		RdpPort:          a.RdpPort,
44
+		RdpUsername:      a.RdpUsername,
45
+		Online:           a.Online,
46
+		LoginName:        a.LoginName,
47
+		SameServer:       a.SameServer,
48
+	}
49
+
50
+}
51
+
52
+type AddressBookQuery struct {
53
+	UserId int `form:"user_id"`
54
+	IsMy   int `form:"is_my"`
55
+	PageQuery
56
+}

+ 21 - 0
http/request/admin/group.go

@@ -0,0 +1,21 @@
1
+package admin
2
+
3
+import "Gwen/model"
4
+
5
+type GroupForm struct {
6
+	Id   uint   `json:"id"`
7
+	Name string `json:"name" validate:"required"`
8
+}
9
+
10
+func (gf *GroupForm) FromGroup(group *model.Group) *GroupForm {
11
+	gf.Id = group.Id
12
+	gf.Name = group.Name
13
+	return gf
14
+}
15
+
16
+func (gf *GroupForm) ToGroup() *model.Group {
17
+	group := &model.Group{}
18
+	group.Id = gf.Id
19
+	group.Name = gf.Name
20
+	return group
21
+}

+ 6 - 0
http/request/admin/login.go

@@ -0,0 +1,6 @@
1
+package admin
2
+
3
+type Login struct {
4
+	Username string `json:"username" validate:"required" label:"用户名"`
5
+	Password string `json:"password,omitempty" validate:"required" label:"密码"`
6
+}

+ 30 - 0
http/request/admin/peer.go

@@ -0,0 +1,30 @@
1
+package admin
2
+
3
+import "Gwen/model"
4
+
5
+type PeerForm struct {
6
+	RowId    uint   `json:"row_id" `
7
+	Id       string `json:"id"`
8
+	Cpu      string `json:"cpu"`
9
+	Hostname string `json:"hostname"`
10
+	Memory   string `json:"memory"`
11
+	Os       string `json:"os"`
12
+	Username string `json:"username"`
13
+	Uuid     string `json:"uuid"`
14
+	Version  string `json:"version"`
15
+}
16
+
17
+// ToPeer
18
+func (f *PeerForm) ToPeer() *model.Peer {
19
+	return &model.Peer{
20
+		RowId:    f.RowId,
21
+		Id:       f.Id,
22
+		Cpu:      f.Cpu,
23
+		Hostname: f.Hostname,
24
+		Memory:   f.Memory,
25
+		Os:       f.Os,
26
+		Username: f.Username,
27
+		Uuid:     f.Uuid,
28
+		Version:  f.Version,
29
+	}
30
+}

+ 33 - 0
http/request/admin/tag.go

@@ -0,0 +1,33 @@
1
+package admin
2
+
3
+import "Gwen/model"
4
+
5
+type TagForm struct {
6
+	Id     uint   `json:"id"`
7
+	Name   string `json:"name" validate:"required"`
8
+	Color  uint   `json:"color" validate:"required"`
9
+	UserId uint   `json:"user_id" validate:"required"`
10
+}
11
+
12
+func (f *TagForm) FromTag(group *model.Tag) *TagForm {
13
+	f.Id = group.Id
14
+	f.Name = group.Name
15
+	f.Color = group.Color
16
+	f.UserId = group.UserId
17
+	return f
18
+}
19
+
20
+func (f *TagForm) ToTag() *model.Tag {
21
+	i := &model.Tag{}
22
+	i.Id = f.Id
23
+	i.Name = f.Name
24
+	i.Color = f.Color
25
+	i.UserId = f.UserId
26
+	return i
27
+}
28
+
29
+type TagQuery struct {
30
+	UserId int `form:"user_id"`
31
+	IsMy   int `form:"is_my"`
32
+	PageQuery
33
+}

+ 57 - 0
http/request/admin/user.go

@@ -0,0 +1,57 @@
1
+package admin
2
+
3
+import (
4
+	"Gwen/model"
5
+)
6
+
7
+type UserForm struct {
8
+	Id       uint   `json:"id"`
9
+	Username string `json:"username" validate:"required,gte=4,lte=10"`
10
+	//Password string           `json:"password" validate:"required,gte=4,lte=20"`
11
+	Nickname string           `json:"nickname" validate:"required"`
12
+	Avatar   string           `json:"avatar"`
13
+	GroupId  uint             `json:"group_id" validate:"required"`
14
+	IsAdmin  *bool            `json:"is_admin" `
15
+	Status   model.StatusCode `json:"status" validate:"required,gte=0"`
16
+}
17
+
18
+func (uf *UserForm) FromUser(user *model.User) *UserForm {
19
+	uf.Id = user.Id
20
+	uf.Username = user.Username
21
+	uf.Nickname = user.Nickname
22
+	uf.Avatar = user.Avatar
23
+	uf.GroupId = user.GroupId
24
+	uf.IsAdmin = user.IsAdmin
25
+	uf.Status = user.Status
26
+	return uf
27
+}
28
+func (uf *UserForm) ToUser() *model.User {
29
+	user := &model.User{}
30
+	user.Id = uf.Id
31
+	user.Username = uf.Username
32
+	user.Nickname = uf.Nickname
33
+	user.Avatar = uf.Avatar
34
+	user.GroupId = uf.GroupId
35
+	user.IsAdmin = uf.IsAdmin
36
+	user.Status = uf.Status
37
+	return user
38
+}
39
+
40
+type PageQuery struct {
41
+	Page     uint `form:"page"`
42
+	PageSize uint `form:"page_size"`
43
+}
44
+
45
+type UserQuery struct {
46
+	PageQuery
47
+	Username string `form:"username"`
48
+}
49
+type UserPasswordForm struct {
50
+	Id       uint   `json:"id" validate:"required"`
51
+	Password string `json:"password" validate:"required,gte=4,lte=20"`
52
+}
53
+
54
+type ChangeCurPasswordForm struct {
55
+	OldPassword string `json:"old_password" validate:"required,gte=4,lte=20"`
56
+	NewPassword string `json:"new_password" validate:"required,gte=4,lte=20"`
57
+}

+ 37 - 0
http/request/api/peer.go

@@ -0,0 +1,37 @@
1
+package api
2
+
3
+import "Gwen/model"
4
+
5
+type AddressBookFormData struct {
6
+	Tags      []string             `json:"tags"`
7
+	Peers     []*model.AddressBook `json:"peers"`
8
+	TagColors string               `json:"tag_colors"`
9
+}
10
+
11
+type AddressBookForm struct {
12
+	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}\"}"`
13
+}
14
+
15
+type PeerForm struct {
16
+	Cpu      string `json:"cpu"`
17
+	Hostname string `json:"hostname"`
18
+	Id       string `json:"id"`
19
+	Memory   string `json:"memory"`
20
+	Os       string `json:"os"`
21
+	Username string `json:"username"`
22
+	Uuid     string `json:"uuid"`
23
+	Version  string `json:"version"`
24
+}
25
+
26
+func (pf *PeerForm) ToPeer() *model.Peer {
27
+	return &model.Peer{
28
+		Cpu:      pf.Cpu,
29
+		Hostname: pf.Hostname,
30
+		Id:       pf.Id,
31
+		Memory:   pf.Memory,
32
+		Os:       pf.Os,
33
+		Username: pf.Username,
34
+		Uuid:     pf.Uuid,
35
+		Version:  pf.Version,
36
+	}
37
+}

+ 41 - 0
http/request/api/user.go

@@ -0,0 +1,41 @@
1
+package api
2
+
3
+/*
4
+*
5
+
6
+	message LoginRequest {
7
+	  string username = 1;
8
+	  bytes password = 2;
9
+	  string my_id = 4;
10
+	  string my_name = 5;
11
+	  OptionMessage option = 6;
12
+	  oneof union {
13
+	    FileTransfer file_transfer = 7;
14
+	    PortForward port_forward = 8;
15
+	  }
16
+	  bool video_ack_required = 9;
17
+	  uint64 session_id = 10;
18
+	  string version = 11;
19
+	  OSLogin os_login = 12;
20
+	  string my_platform = 13;
21
+	  bytes hwid = 14;
22
+	}
23
+*/
24
+type LoginForm struct {
25
+	Username string `json:"username" validate:"required,gte=4,lte=10" label:"用户名"`
26
+	Password string `json:"password,omitempty" validate:"gte=4,lte=20" label:"密码"`
27
+}
28
+
29
+type UserListQuery struct {
30
+	Page       uint   `json:"page" form:"page" validate:"required" label:"页码"`
31
+	PageSize   uint   `json:"page_size" form:"page_size" validate:"required" label:"每页数量"`
32
+	Status     int    `json:"status" form:"status" label:"状态"`
33
+	Accessible string `json:"accessible" form:"accessible"`
34
+}
35
+
36
+type PeerListQuery struct {
37
+	Page       uint   `json:"page" form:"page" validate:"required" label:"页码"`
38
+	PageSize   uint   `json:"page_size" form:"page_size" validate:"required" label:"每页数量"`
39
+	Status     int    `json:"status" form:"status" label:"状态"`
40
+	Accessible string `json:"accessible" form:"accessible"`
41
+}

+ 13 - 0
http/response/admin/user.go

@@ -0,0 +1,13 @@
1
+package admin
2
+
3
+type LoginPayload struct {
4
+	Username   string   `json:"username"`
5
+	Token      string   `json:"token"`
6
+	RouteNames []string `json:"route_names"`
7
+	Nickname   string   `json:"nickname"`
8
+}
9
+
10
+var UserRouteNames = []string{
11
+	"MyTagList", "MyAddressBookList",
12
+}
13
+var AdminRouteNames = []string{"*"}

+ 9 - 0
http/response/api/ab.go

@@ -0,0 +1,9 @@
1
+package api
2
+
3
+import "Gwen/model"
4
+
5
+type AbList struct {
6
+	Peers     []*model.AddressBook `json:"peers,omitempty"`
7
+	Tags      []string             `json:"tags,omitempty"`
8
+	TagColors string               `json:"tag_colors,omitempty"`
9
+}

+ 74 - 0
http/response/api/peer.go

@@ -0,0 +1,74 @@
1
+package api
2
+
3
+import "Gwen/model"
4
+
5
+/*
6
+GroupPeerPayload
7
+https://github.com/rustdesk/rustdesk/blob/master/flutter/lib/common/hbbs/hbbs.dart#L64
8
+
9
+		String id = '';
10
+		Map<String, dynamic> info = {};
11
+		int? status;
12
+		String user = '';
13
+		String user_name = '';
14
+		String note = '';
15
+
16
+	 PeerPayload.fromJson(Map<String, dynamic> json)
17
+	      : id = json['id'] ?? '',
18
+	        info = (json['info'] is Map<String, dynamic>) ? json['info'] : {},
19
+	        status = json['status'],
20
+	        user = json['user'] ?? '',
21
+	        user_name = json['user_name'] ?? '',
22
+	        note = json['note'] ?? '';
23
+
24
+		static Peer toPeer(GroupPeerPayload p) {
25
+		    return Peer.fromJson({
26
+		      "id": p.id,
27
+		      'loginName': p.user_name,
28
+		      "username": p.info['username'] ?? '',
29
+		      "platform": _platform(p.info['os']),
30
+		      "hostname": p.info['device_name'],
31
+		    });
32
+		  }
33
+*/
34
+type GroupPeerPayload struct {
35
+	Id       string           `json:"id"`
36
+	Info     *PeerPayloadInfo `json:"info"`
37
+	Status   int              `json:"status"`
38
+	User     string           `json:"user"`
39
+	UserName string           `json:"user_name"`
40
+	Note     string           `json:"note"`
41
+}
42
+type PeerPayloadInfo struct {
43
+	DeviceName string `json:"device_name"`
44
+	Os         string `json:"os"`
45
+	Username   string `json:"username"`
46
+}
47
+
48
+func (gpp *GroupPeerPayload) FromAddressBook(a *model.AddressBook, username string) {
49
+	gpp.Id = a.Id
50
+	os := a.Platform
51
+	if a.Platform == "Mac OS" {
52
+		os = "MacOS"
53
+	}
54
+	gpp.Info = &PeerPayloadInfo{
55
+		DeviceName: a.Hostname,
56
+		Os:         os,
57
+		Username:   a.Username,
58
+	}
59
+	gpp.UserName = username
60
+}
61
+
62
+//func (gpp *GroupPeerPayload) FromPeer(p *model.Peer) {
63
+//	gpp.Id = p.Id
64
+//	gpp.Info = &PeerPayloadInfo{
65
+//		DeviceName: p.Hostname,
66
+//		Os:         p.Os,
67
+//		Username:   p.Username,
68
+//	}
69
+//	gpp.Note = ""
70
+//	if p.User.Id != 0 {
71
+//		//gpp.User = p.User.Username
72
+//		gpp.UserName = p.User.Username
73
+//	}
74
+//}

+ 55 - 0
http/response/api/user.go

@@ -0,0 +1,55 @@
1
+package api
2
+
3
+import "Gwen/model"
4
+
5
+/*
6
+	pub enum UserStatus {
7
+	    Disabled = 0,
8
+	    Normal = 1,
9
+	    Unverified = -1,
10
+	}
11
+*/
12
+
13
+/*
14
+UserPayload
15
+String name = ”;
16
+String email = ”;
17
+String note = ”;
18
+UserStatus status;
19
+bool isAdmin = false;
20
+*/
21
+type UserPayload struct {
22
+	Name    string `json:"name"`
23
+	Email   string `json:"email"`
24
+	Note    string `json:"note"`
25
+	IsAdmin *bool  `json:"is_admin"`
26
+	Status  int    `json:"status"`
27
+}
28
+
29
+func (up *UserPayload) FromUser(user *model.User) *UserPayload {
30
+	up.Name = user.Username
31
+	up.IsAdmin = user.IsAdmin
32
+	up.Status = int(user.Status)
33
+	return up
34
+}
35
+
36
+/*
37
+	class HttpType {
38
+	  static const kAuthReqTypeAccount = "account";
39
+	  static const kAuthReqTypeMobile = "mobile";
40
+	  static const kAuthReqTypeSMSCode = "sms_code";
41
+	  static const kAuthReqTypeEmailCode = "email_code";
42
+	  static const kAuthReqTypeTfaCode = "tfa_code";
43
+
44
+	  static const kAuthResTypeToken = "access_token";
45
+	  static const kAuthResTypeEmailCheck = "email_check";
46
+	  static const kAuthResTypeTfaCheck = "tfa_check";
47
+	}
48
+*/
49
+type LoginRes struct {
50
+	Type        string      `json:"type"`
51
+	AccessToken string      `json:"access_token"`
52
+	User        UserPayload `json:"user"`
53
+	Secret      string      `json:"secret"`
54
+	TfaType     string      `json:"tfa_type"`
55
+}

+ 55 - 0
http/response/api/webClient.go

@@ -0,0 +1,55 @@
1
+package api
2
+
3
+import (
4
+	"Gwen/model"
5
+	"time"
6
+)
7
+
8
+//	type T struct {
9
+//		Field1 struct {
10
+//			ViewStyle string `json:"view-style"`
11
+//			Tm        int64  `json:"tm"`
12
+//			Info      struct {
13
+//				Username string `json:"username"`
14
+//				Hostname string `json:"hostname"`
15
+//				Platform string `json:"platform"`
16
+//				Displays []struct {
17
+//					X      int    `json:"x"`
18
+//					Y      int    `json:"y"`
19
+//					Width  int    `json:"width"`
20
+//					Height int    `json:"height"`
21
+//					Name   string `json:"name"`
22
+//					Online bool   `json:"online"`
23
+//				} `json:"displays"`
24
+//				CurrentDisplay int    `json:"current_display"`
25
+//				SasEnabled     bool   `json:"sas_enabled"`
26
+//				Version        string `json:"version"`
27
+//				ConnId         int    `json:"conn_id"`
28
+//				Features       struct {
29
+//					PrivacyMode bool `json:"privacy_mode"`
30
+//				} `json:"features"`
31
+//			} `json:"info"`
32
+//		} `json:"1799928825"`
33
+//	}
34
+
35
+type WebClientPeerPayload struct {
36
+	ViewStyle string                   `json:"view-style"`
37
+	Tm        int64                    `json:"tm"`
38
+	Info      WebClientPeerInfoPayload `json:"info"`
39
+}
40
+
41
+type WebClientPeerInfoPayload struct {
42
+	Username string `json:"username"`
43
+	Hostname string `json:"hostname"`
44
+	Platform string `json:"platform"`
45
+}
46
+
47
+func (wcpp *WebClientPeerPayload) FromAddressBook(a *model.AddressBook) {
48
+	wcpp.ViewStyle = "shrink"
49
+	wcpp.Tm = time.Now().UnixNano()
50
+	wcpp.Info = WebClientPeerInfoPayload{
51
+		Username: a.Username,
52
+		Hostname: a.Hostname,
53
+		Platform: a.Platform,
54
+	}
55
+}

+ 53 - 0
http/response/response.go

@@ -0,0 +1,53 @@
1
+package response
2
+
3
+import (
4
+	"github.com/gin-gonic/gin"
5
+	"net/http"
6
+)
7
+
8
+type Response struct {
9
+	Code    int         `json:"code"`
10
+	Message string      `json:"message"`
11
+	Data    interface{} `json:"data"`
12
+}
13
+type PageData struct {
14
+	Page  int         `json:"page"`
15
+	Total int         `json:"total"`
16
+	List  interface{} `json:"list"`
17
+}
18
+
19
+type DataResponse struct {
20
+	Total uint        `json:"total"`
21
+	Data  interface{} `json:"data"`
22
+}
23
+
24
+type ErrorResponse struct {
25
+	Error string `json:"error"`
26
+}
27
+
28
+func SendResponse(c *gin.Context, code int, message string, data interface{}) {
29
+	c.JSON(http.StatusOK, Response{
30
+		code, message, data,
31
+	})
32
+}
33
+
34
+func Success(c *gin.Context, data interface{}) {
35
+	SendResponse(c, 0, "success", data)
36
+}
37
+
38
+func Fail(c *gin.Context, code int, message string) {
39
+	SendResponse(c, code, message, nil)
40
+}
41
+
42
+func Error(c *gin.Context, message string) {
43
+	c.JSON(http.StatusBadRequest, ErrorResponse{
44
+		Error: message,
45
+	})
46
+}
47
+
48
+type ServerConfigResponse struct {
49
+	IdServer    string `json:"id_server"`
50
+	Key         string `json:"key"`
51
+	RelayServer string `json:"relay_server"`
52
+	ApiServer   string `json:"api_server"`
53
+}

+ 118 - 0
http/router/admin.go

@@ -0,0 +1,118 @@
1
+package router
2
+
3
+import (
4
+	_ "Gwen/docs/admin"
5
+	"Gwen/http/controller/admin"
6
+	"Gwen/http/middleware"
7
+	"github.com/gin-gonic/gin"
8
+	swaggerFiles "github.com/swaggo/files"
9
+	ginSwagger "github.com/swaggo/gin-swagger"
10
+)
11
+
12
+func Init(g *gin.Engine) {
13
+
14
+	//swagger
15
+	//g.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
16
+	g.GET("/admin/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.InstanceName("admin")))
17
+
18
+	adg := g.Group("/api/admin")
19
+	LoginBind(adg)
20
+
21
+	adg.Use(middleware.AdminAuth())
22
+	//FileBind(adg)
23
+	UserBind(adg)
24
+	GroupBind(adg)
25
+	TagBind(adg)
26
+	AddressBookBind(adg)
27
+	PeerBind(adg)
28
+
29
+	rs := &admin.Rustdesk{}
30
+	adg.GET("/server-config", rs.ServerConfig)
31
+
32
+	//访问静态文件
33
+	//g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/upload"))
34
+}
35
+func LoginBind(rg *gin.RouterGroup) {
36
+	cont := &admin.Login{}
37
+	rg.POST("/login", cont.Login)
38
+	rg.POST("/logout", cont.Logout)
39
+}
40
+
41
+func UserBind(rg *gin.RouterGroup) {
42
+	aR := rg.Group("/user")
43
+	{
44
+		cont := &admin.User{}
45
+		aR.GET("/current", cont.Current)
46
+		aR.POST("/changeCurPwd", cont.ChangeCurPwd)
47
+	}
48
+	aRP := rg.Group("/user").Use(middleware.AdminPrivilege())
49
+	{
50
+		cont := &admin.User{}
51
+		aRP.GET("/list", cont.List)
52
+		aRP.GET("/detail/:id", cont.Detail)
53
+		aRP.POST("/create", cont.Create)
54
+		aRP.POST("/update", cont.Update)
55
+		aRP.POST("/delete", cont.Delete)
56
+		aRP.POST("/changePwd", cont.UpdatePassword)
57
+	}
58
+}
59
+
60
+func GroupBind(rg *gin.RouterGroup) {
61
+	aR := rg.Group("/group").Use(middleware.AdminPrivilege())
62
+	{
63
+		cont := &admin.Group{}
64
+		aR.GET("/list", cont.List)
65
+		aR.GET("/detail/:id", cont.Detail)
66
+		aR.POST("/create", cont.Create)
67
+		aR.POST("/update", cont.Update)
68
+		aR.POST("/delete", cont.Delete)
69
+	}
70
+}
71
+
72
+func TagBind(rg *gin.RouterGroup) {
73
+	aR := rg.Group("/tag")
74
+	{
75
+		cont := &admin.Tag{}
76
+		aR.GET("/list", cont.List)
77
+		aR.GET("/detail/:id", cont.Detail)
78
+		aR.POST("/create", cont.Create)
79
+		aR.POST("/update", cont.Update)
80
+		aR.POST("/delete", cont.Delete)
81
+	}
82
+}
83
+
84
+func AddressBookBind(rg *gin.RouterGroup) {
85
+	aR := rg.Group("/address_book")
86
+	{
87
+		cont := &admin.AddressBook{}
88
+		aR.GET("/list", cont.List)
89
+		aR.GET("/detail/:id", cont.Detail)
90
+		aR.POST("/create", cont.Create)
91
+		aR.POST("/update", cont.Update)
92
+		aR.POST("/delete", cont.Delete)
93
+	}
94
+}
95
+func PeerBind(rg *gin.RouterGroup) {
96
+	aR := rg.Group("/peer")
97
+	{
98
+		cont := &admin.Peer{}
99
+		aR.GET("/list", cont.List)
100
+		aR.GET("/detail/:id", cont.Detail)
101
+		aR.POST("/create", cont.Create)
102
+		aR.POST("/update", cont.Update)
103
+		aR.POST("/delete", cont.Delete)
104
+	}
105
+}
106
+
107
+/*
108
+func FileBind(rg *gin.RouterGroup) {
109
+	aR := rg.Group("/file")
110
+	{
111
+		cont := &admin.File{}
112
+		aR.POST("/notify", cont.Notify)
113
+		aR.OPTIONS("/oss_token", nil)
114
+		aR.OPTIONS("/upload", nil)
115
+		aR.GET("/oss_token", cont.OssToken)
116
+		aR.POST("/upload", cont.Upload)
117
+	}
118
+}*/

+ 73 - 0
http/router/api.go

@@ -0,0 +1,73 @@
1
+package router
2
+
3
+import (
4
+	_ "Gwen/docs/api"
5
+	"Gwen/global"
6
+	"Gwen/http/controller/api"
7
+	"Gwen/http/middleware"
8
+	"github.com/gin-gonic/gin"
9
+	swaggerFiles "github.com/swaggo/files"
10
+	ginSwagger "github.com/swaggo/gin-swagger"
11
+	"net/http"
12
+)
13
+
14
+func ApiInit(g *gin.Engine) {
15
+
16
+	//g.Use(middleware.Cors())
17
+	//swagger
18
+	g.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.InstanceName("api")))
19
+
20
+	frg := g.Group("/api")
21
+
22
+	frg.Use(middleware.Cors())
23
+	frg.OPTIONS("/*any", nil)
24
+
25
+	i := &api.Index{}
26
+	frg.GET("/", i.Index)
27
+
28
+	frg.POST("/heartbeat", i.Heartbeat)
29
+
30
+	{
31
+		l := &api.Login{}
32
+		// 如果返回oidc则可以通过oidc登录
33
+		frg.GET("/login-options", l.LoginOptions)
34
+		frg.POST("/login", l.Login)
35
+
36
+	}
37
+	{
38
+		pe := &api.Peer{}
39
+		//提交系统信息
40
+		frg.POST("/sysinfo", pe.SysInfo)
41
+	}
42
+	frg.Use(middleware.RustAuth())
43
+	{
44
+		w := &api.WebClient{}
45
+		frg.POST("/server-config", w.ServerConfig)
46
+	}
47
+
48
+	{
49
+		u := &api.User{}
50
+		frg.GET("/user/info", u.Info)
51
+		frg.POST("/currentUser", u.Info)
52
+	}
53
+	{
54
+		l := &api.Login{}
55
+		frg.POST("/logout", l.Logout)
56
+	}
57
+	{
58
+		gr := &api.Group{}
59
+		frg.GET("/users", gr.Users)
60
+		frg.GET("/peers", gr.Peers)
61
+	}
62
+
63
+	{
64
+		ab := &api.Ab{}
65
+		//获取地址
66
+		frg.GET("/ab", ab.Ab)
67
+		//更新地址
68
+		frg.POST("/ab", ab.UpAb)
69
+	}
70
+
71
+	//访问静态文件
72
+	g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/public/upload"))
73
+}

+ 15 - 0
http/router/router.go

@@ -0,0 +1,15 @@
1
+package router
2
+
3
+import (
4
+	"Gwen/global"
5
+	"Gwen/http/controller/web"
6
+	"github.com/gin-gonic/gin"
7
+	"net/http"
8
+)
9
+
10
+func WebInit(g *gin.Engine) {
11
+	i := &web.Index{}
12
+	g.GET("/webclient-config/index.js", i.ConfigJs)
13
+	g.StaticFS("/webclient", http.Dir(global.Config.Gin.ResourcesPath+"/web"))
14
+	g.StaticFS("/_admin", http.Dir(global.Config.Gin.ResourcesPath+"/admin"))
15
+}

+ 12 - 0
http/run.go

@@ -0,0 +1,12 @@
1
+//go:build !windows
2
+
3
+package http
4
+
5
+import (
6
+	"github.com/fvbock/endless"
7
+	"github.com/gin-gonic/gin"
8
+)
9
+
10
+func Run(g *gin.Engine, addr string) {
11
+	endless.ListenAndServe(addr, g)
12
+}

+ 11 - 0
http/run_win.go

@@ -0,0 +1,11 @@
1
+//go:build windows
2
+
3
+package http
4
+
5
+import (
6
+	"github.com/gin-gonic/gin"
7
+)
8
+
9
+func Run(g *gin.Engine, addr string) {
10
+	g.Run(addr)
11
+}

+ 71 - 0
lib/cache/cache.go

@@ -0,0 +1,71 @@
1
+package cache
2
+
3
+import (
4
+	"encoding/json"
5
+)
6
+
7
+type Handler interface {
8
+	Get(key string, value interface{}) error
9
+	Set(key string, value interface{}, exp int) error
10
+	Gc() error
11
+}
12
+
13
+// MaxTimeOut 最大超时时间
14
+
15
+const (
16
+	TypeMem    = "memory"
17
+	TypeRedis  = "redis"
18
+	TypeFile   = "file"
19
+	MaxTimeOut = 365 * 24 * 3600
20
+)
21
+
22
+func New(typ string) Handler {
23
+	var cache Handler
24
+	switch typ {
25
+	case TypeFile:
26
+		cache = NewFileCache()
27
+	case TypeRedis:
28
+		cache = new(RedisCache)
29
+	case TypeMem: // memory
30
+		cache = NewMemoryCache(0)
31
+	default:
32
+		cache = NewMemoryCache(0)
33
+	}
34
+	return cache
35
+}
36
+
37
+func EncodeValue(value interface{}) (string, error) {
38
+	/*if v, ok := value.(string); ok {
39
+		return v, nil
40
+	}
41
+	if v, ok := value.([]byte); ok {
42
+		return string(v), nil
43
+	}*/
44
+	b, err := json.Marshal(value)
45
+	if err != nil {
46
+		return "", err
47
+	}
48
+	return string(b), nil
49
+}
50
+
51
+func DecodeValue(value string, rtv interface{}) error {
52
+	//判断rtv的类型是否是string,如果是string,直接赋值并返回
53
+	/*switch rtv.(type) {
54
+	case *string:
55
+		*(rtv.(*string)) = value
56
+		return nil
57
+	case *[]byte:
58
+		*(rtv.(*[]byte)) = []byte(value)
59
+		return nil
60
+	//struct
61
+	case *interface{}:
62
+		err := json.Unmarshal(([]byte)(value), rtv)
63
+		return err
64
+	default:
65
+		err := json.Unmarshal(([]byte)(value), rtv)
66
+		return err
67
+	}
68
+	*/
69
+	err := json.Unmarshal(([]byte)(value), rtv)
70
+	return err
71
+}

+ 92 - 0
lib/cache/cache_test.go

@@ -0,0 +1,92 @@
1
+package cache
2
+
3
+import (
4
+	"fmt"
5
+	"github.com/go-redis/redis/v8"
6
+	"reflect"
7
+	"testing"
8
+)
9
+
10
+func TestSimpleCache(t *testing.T) {
11
+
12
+	type st struct {
13
+		A string
14
+		B string
15
+	}
16
+
17
+	items := map[string]interface{}{}
18
+	items["a"] = "b"
19
+	items["b"] = "c"
20
+
21
+	ab := &st{
22
+		A: "a",
23
+		B: "b",
24
+	}
25
+	items["ab"] = *ab
26
+
27
+	a := items["a"]
28
+	fmt.Println(a)
29
+
30
+	b := items["b"]
31
+	fmt.Println(b)
32
+
33
+	ab.A = "aa"
34
+	ab2 := st{}
35
+	ab2 = (items["ab"]).(st)
36
+	fmt.Println(ab2, reflect.TypeOf(ab2))
37
+
38
+}
39
+
40
+func TestFileCacheSet(t *testing.T) {
41
+	fc := New("file")
42
+	err := fc.Set("123", "ddd", 0)
43
+	if err != nil {
44
+		fmt.Println(err.Error())
45
+		t.Fatalf("写入失败")
46
+	}
47
+}
48
+
49
+func TestFileCacheGet(t *testing.T) {
50
+	fc := New("file")
51
+	err := fc.Set("123", "45156", 300)
52
+	if err != nil {
53
+		t.Fatalf("写入失败")
54
+	}
55
+	res := ""
56
+	err = fc.Get("123", &res)
57
+	if err != nil {
58
+		t.Fatalf("读取失败")
59
+	}
60
+	fmt.Println("res", res)
61
+}
62
+
63
+func TestRedisCacheSet(t *testing.T) {
64
+	rc := NewRedis(&redis.Options{
65
+		Addr:     "192.168.1.168:6379",
66
+		Password: "", // no password set
67
+		DB:       0,  // use default DB
68
+	})
69
+	err := rc.Set("123", "ddd", 0)
70
+	if err != nil {
71
+		fmt.Println(err.Error())
72
+		t.Fatalf("写入失败")
73
+	}
74
+}
75
+
76
+func TestRedisCacheGet(t *testing.T) {
77
+	rc := NewRedis(&redis.Options{
78
+		Addr:     "192.168.1.168:6379",
79
+		Password: "", // no password set
80
+		DB:       0,  // use default DB
81
+	})
82
+	err := rc.Set("123", "451156", 300)
83
+	if err != nil {
84
+		t.Fatalf("写入失败")
85
+	}
86
+	res := ""
87
+	err = rc.Get("123", &res)
88
+	if err != nil {
89
+		t.Fatalf("读取失败")
90
+	}
91
+	fmt.Println("res", res)
92
+}

+ 103 - 0
lib/cache/file.go

@@ -0,0 +1,103 @@
1
+package cache
2
+
3
+import (
4
+	"crypto/md5"
5
+	"fmt"
6
+	"os"
7
+	"sync"
8
+	"time"
9
+)
10
+
11
+type FileCache struct {
12
+	mu    sync.Mutex
13
+	locks map[string]*sync.Mutex
14
+	Dir   string
15
+}
16
+
17
+func (fc *FileCache) getLock(key string) *sync.Mutex {
18
+	fc.mu.Lock()
19
+	defer fc.mu.Unlock()
20
+	if fc.locks == nil {
21
+		fc.locks = make(map[string]*sync.Mutex)
22
+	}
23
+	if _, ok := fc.locks[key]; !ok {
24
+		fc.locks[key] = new(sync.Mutex)
25
+	}
26
+	return fc.locks[key]
27
+}
28
+
29
+func (c *FileCache) Get(key string, value interface{}) error {
30
+	data, _ := c.getValue(key)
31
+	err := DecodeValue(data, value)
32
+	return err
33
+}
34
+
35
+// 获取值,如果文件不存在或者过期,返回空,过滤掉错误
36
+func (c *FileCache) getValue(key string) (string, error) {
37
+	f := c.fileName(key)
38
+	fileInfo, err := os.Stat(f)
39
+	if err != nil {
40
+		//文件不存在
41
+		return "", nil
42
+	}
43
+	difT := time.Now().Sub(fileInfo.ModTime())
44
+	if difT >= 0 {
45
+		os.Remove(f)
46
+		return "", nil
47
+	}
48
+	data, err := os.ReadFile(f)
49
+	if err != nil {
50
+		return "", nil
51
+	}
52
+	return string(data), nil
53
+}
54
+
55
+// 保存值
56
+func (c *FileCache) saveValue(key string, value string, exp int) error {
57
+	f := c.fileName(key)
58
+	lock := c.getLock(f)
59
+	lock.Lock()
60
+	defer lock.Unlock()
61
+
62
+	err := os.WriteFile(f, ([]byte)(value), 0644)
63
+	if err != nil {
64
+		return err
65
+	}
66
+	if exp <= 0 {
67
+		exp = MaxTimeOut
68
+	}
69
+	expFromNow := time.Now().Add(time.Duration(exp) * time.Second)
70
+	err = os.Chtimes(f, expFromNow, expFromNow)
71
+	return err
72
+}
73
+
74
+func (c *FileCache) Set(key string, value interface{}, exp int) error {
75
+	str, err := EncodeValue(value)
76
+	if err != nil {
77
+		return err
78
+	}
79
+
80
+	err = c.saveValue(key, str, exp)
81
+	return err
82
+}
83
+
84
+func (c *FileCache) SetDir(path string) {
85
+	c.Dir = path
86
+}
87
+
88
+func (c *FileCache) fileName(key string) string {
89
+	f := c.Dir + string(os.PathSeparator) + fmt.Sprintf("%x", md5.Sum([]byte(key)))
90
+	return f
91
+}
92
+
93
+func (c *FileCache) Gc() error {
94
+	//检查文件过期时间,并删除
95
+	return nil
96
+}
97
+
98
+func NewFileCache() *FileCache {
99
+	return &FileCache{
100
+		locks: make(map[string]*sync.Mutex),
101
+		Dir:   os.TempDir(),
102
+	}
103
+}

+ 94 - 0
lib/cache/file_test.go

@@ -0,0 +1,94 @@
1
+package cache
2
+
3
+import (
4
+	"fmt"
5
+	"reflect"
6
+	"testing"
7
+)
8
+
9
+func TestFileSet(t *testing.T) {
10
+	fc := NewFileCache()
11
+	err := fc.Set("123", "ddd", 0)
12
+	if err != nil {
13
+		fmt.Println(err.Error())
14
+		t.Fatalf("写入失败")
15
+	}
16
+}
17
+
18
+func TestFileGet(t *testing.T) {
19
+	fc := NewFileCache()
20
+	res := ""
21
+	err := fc.Get("123", &res)
22
+	if err != nil {
23
+		fmt.Println(err.Error())
24
+		t.Fatalf("读取失败")
25
+	}
26
+	fmt.Println("res", res)
27
+}
28
+func TestFileSetGet(t *testing.T) {
29
+	fc := NewFileCache()
30
+	err := fc.Set("key1", "ddd", 0)
31
+	res := ""
32
+	err = fc.Get("key1", &res)
33
+	if err != nil {
34
+		fmt.Println(err.Error())
35
+		t.Fatalf("读取失败")
36
+	}
37
+	fmt.Println("res", res)
38
+}
39
+func TestFileGetJson(t *testing.T) {
40
+	fc := NewFileCache()
41
+	old := &r{
42
+		A: "a", B: "b",
43
+	}
44
+	fc.Set("123", old, 0)
45
+	res := &r{}
46
+	err2 := fc.Get("123", res)
47
+	fmt.Println("res", res)
48
+	if err2 != nil {
49
+		t.Fatalf("读取失败" + err2.Error())
50
+	}
51
+}
52
+func TestFileSetGetJson(t *testing.T) {
53
+	fc := NewFileCache()
54
+
55
+	old_rr := &rr{AA: "aa", BB: "bb"}
56
+	old := &r{
57
+		A: "a", B: "b",
58
+		R: old_rr,
59
+	}
60
+	err := fc.Set("123", old, 300)
61
+	if err != nil {
62
+		t.Fatalf("写入失败")
63
+	}
64
+	//old_rr.AA = "aaa"
65
+	fmt.Println("old_rr", old)
66
+
67
+	res := &r{}
68
+	err2 := fc.Get("123", res)
69
+	fmt.Println("res", res)
70
+	if err2 != nil {
71
+		t.Fatalf("读取失败" + err2.Error())
72
+	}
73
+	if !reflect.DeepEqual(res, old) {
74
+		t.Fatalf("读取错误")
75
+	}
76
+
77
+}
78
+
79
+func BenchmarkSet(b *testing.B) {
80
+	fc := NewFileCache()
81
+	b.ResetTimer()
82
+	for i := 0; i < b.N; i++ {
83
+		fc.Set("123", "{dsv}", 1000)
84
+	}
85
+}
86
+
87
+func BenchmarkGet(b *testing.B) {
88
+	fc := NewFileCache()
89
+	b.ResetTimer()
90
+	v := ""
91
+	for i := 0; i < b.N; i++ {
92
+		fc.Get("123", &v)
93
+	}
94
+}

+ 215 - 0
lib/cache/memory.go

@@ -0,0 +1,215 @@
1
+package cache
2
+
3
+import (
4
+	"container/heap"
5
+	"container/list"
6
+	"errors"
7
+	"reflect"
8
+	"sync"
9
+	"time"
10
+)
11
+
12
+type MemoryCache struct {
13
+	data      map[string]*CacheItem
14
+	ll        *list.List    // 用于实现LRU
15
+	pq        PriorityQueue // 用于实现TTL
16
+	quit      chan struct{}
17
+	mu        sync.Mutex
18
+	maxBytes  int64
19
+	usedBytes int64
20
+}
21
+
22
+type CacheItem struct {
23
+	Key        string
24
+	Value      string
25
+	Expiration int64
26
+	Index      int
27
+	ListEle    *list.Element
28
+}
29
+
30
+type PriorityQueue []*CacheItem
31
+
32
+func (pq PriorityQueue) Len() int { return len(pq) }
33
+
34
+func (pq PriorityQueue) Less(i, j int) bool {
35
+	return pq[i].Expiration < pq[j].Expiration
36
+}
37
+
38
+func (pq PriorityQueue) Swap(i, j int) {
39
+	pq[i], pq[j] = pq[j], pq[i]
40
+	pq[i].Index = i
41
+	pq[j].Index = j
42
+}
43
+
44
+func (pq *PriorityQueue) Push(x interface{}) {
45
+	item := x.(*CacheItem)
46
+	item.Index = len(*pq)
47
+	*pq = append(*pq, item)
48
+}
49
+
50
+func (pq *PriorityQueue) Pop() interface{} {
51
+	old := *pq
52
+	n := len(old)
53
+	item := old[n-1]
54
+	old[n-1] = nil  // avoid memory leak
55
+	item.Index = -1 // for safety
56
+	*pq = old[0 : n-1]
57
+	return item
58
+}
59
+
60
+func (m *MemoryCache) Get(key string, value interface{}) error {
61
+	// 使用反射将存储的值设置到传入的指针变量中
62
+	val := reflect.ValueOf(value)
63
+	if val.Kind() != reflect.Ptr {
64
+		return errors.New("value must be a pointer")
65
+	}
66
+	//设为空值
67
+	val.Elem().Set(reflect.Zero(val.Elem().Type()))
68
+
69
+	m.mu.Lock()
70
+	defer m.mu.Unlock()
71
+
72
+	if m.data == nil {
73
+		return nil
74
+	}
75
+
76
+	if item, ok := m.data[key]; ok {
77
+		if item.Expiration < time.Now().UnixNano() {
78
+			m.deleteItem(item)
79
+			return nil
80
+		}
81
+		//移动到队列尾部
82
+		m.ll.MoveToBack(item.ListEle)
83
+
84
+		err := DecodeValue(item.Value, value)
85
+		if err != nil {
86
+			return err
87
+		}
88
+	}
89
+	return nil
90
+}
91
+
92
+func (m *MemoryCache) Set(key string, value interface{}, exp int) error {
93
+	m.mu.Lock()
94
+	defer m.mu.Unlock()
95
+
96
+	v, err := EncodeValue(value)
97
+	if err != nil {
98
+		return err
99
+	}
100
+	//key 所占用的内存
101
+	keyBytes := int64(len(key))
102
+	//value所占用的内存空间大小
103
+	valueBytes := int64(len(v))
104
+	//判断是否超过最大内存限制
105
+	if m.maxBytes != 0 && m.maxBytes < keyBytes+valueBytes {
106
+		return errors.New("exceed maxBytes")
107
+	}
108
+	m.usedBytes += keyBytes + valueBytes
109
+	if m.maxBytes != 0 && m.usedBytes > m.maxBytes {
110
+		m.RemoveOldest()
111
+	}
112
+	if exp <= 0 {
113
+		exp = MaxTimeOut
114
+	}
115
+	expiration := time.Now().Add(time.Duration(exp) * time.Second).UnixNano()
116
+	item, exists := m.data[key]
117
+	if exists {
118
+		item.Value = v
119
+		item.Expiration = expiration
120
+		heap.Fix(&m.pq, item.Index)
121
+		m.ll.MoveToBack(item.ListEle)
122
+	} else {
123
+		ele := m.ll.PushBack(key)
124
+		item = &CacheItem{
125
+			Key:        key,
126
+			Value:      v,
127
+			Expiration: expiration,
128
+			ListEle:    ele,
129
+		}
130
+		m.data[key] = item
131
+		heap.Push(&m.pq, item)
132
+	}
133
+
134
+	return nil
135
+}
136
+
137
+func (m *MemoryCache) RemoveOldest() {
138
+	for m.maxBytes != 0 && m.usedBytes > m.maxBytes {
139
+		elem := m.ll.Front()
140
+		if elem != nil {
141
+			key := elem.Value.(string)
142
+			item := m.data[key]
143
+			m.deleteItem(item)
144
+		}
145
+	}
146
+}
147
+
148
+// evictExpiredItems removes all expired items from the cache.
149
+func (m *MemoryCache) evictExpiredItems() {
150
+	m.mu.Lock()
151
+	defer m.mu.Unlock()
152
+	now := time.Now().UnixNano()
153
+	for m.pq.Len() > 0 {
154
+		item := m.pq[0]
155
+		if item.Expiration > now {
156
+			break
157
+		}
158
+		m.deleteItem(item)
159
+	}
160
+}
161
+
162
+// startEviction starts a goroutine that evicts expired items from the cache.
163
+func (m *MemoryCache) startEviction() {
164
+	ticker := time.NewTicker(1 * time.Second)
165
+
166
+	go func() {
167
+		for {
168
+			select {
169
+			case <-ticker.C:
170
+				m.evictExpiredItems()
171
+			case <-m.quit:
172
+				ticker.Stop()
173
+				return
174
+			}
175
+		}
176
+	}()
177
+}
178
+
179
+// stopEviction 停止定时清理
180
+func (m *MemoryCache) stopEviction() {
181
+	close(m.quit)
182
+}
183
+
184
+// deleteItem removes a key from the cache.
185
+func (m *MemoryCache) deleteItem(item *CacheItem) {
186
+	m.ll.Remove(item.ListEle)
187
+	m.usedBytes -= int64(len(item.Key)) + int64(len(item.Value))
188
+	heap.Remove(&m.pq, item.Index)
189
+	delete(m.data, item.Key)
190
+}
191
+
192
+func (m *MemoryCache) Gc() error {
193
+	m.mu.Lock()
194
+	defer m.mu.Unlock()
195
+	m.data = make(map[string]*CacheItem)
196
+	m.ll = list.New()
197
+	m.pq = make(PriorityQueue, 0)
198
+	heap.Init(&m.pq)
199
+	m.usedBytes = 0
200
+	return nil
201
+}
202
+
203
+// NewMemoryCache creates a new MemoryCache.default maxBytes is 0, means no limit.
204
+func NewMemoryCache(maxBytes int64) *MemoryCache {
205
+	cache := &MemoryCache{
206
+		data:     make(map[string]*CacheItem),
207
+		pq:       make(PriorityQueue, 0),
208
+		quit:     make(chan struct{}),
209
+		ll:       list.New(),
210
+		maxBytes: maxBytes,
211
+	}
212
+	heap.Init(&cache.pq)
213
+	cache.startEviction()
214
+	return cache
215
+}

+ 107 - 0
lib/cache/memory_test.go

@@ -0,0 +1,107 @@
1
+package cache
2
+
3
+import (
4
+	"fmt"
5
+	"testing"
6
+	"time"
7
+)
8
+
9
+func TestMemorySet(t *testing.T) {
10
+	mc := NewMemoryCache(0)
11
+	err := mc.Set("123", "44567", 0)
12
+	if err != nil {
13
+		fmt.Println(err.Error())
14
+		t.Fatalf("写入失败")
15
+	}
16
+}
17
+
18
+func TestMemoryGet(t *testing.T) {
19
+	mc := NewMemoryCache(0)
20
+	mc.Set("123", "44567", 0)
21
+	res := ""
22
+	err := mc.Get("123", &res)
23
+	fmt.Println("res", res)
24
+	if err != nil {
25
+		t.Fatalf("读取失败 " + err.Error())
26
+	}
27
+	if res != "44567" {
28
+		t.Fatalf("读取错误")
29
+	}
30
+
31
+}
32
+
33
+func TestMemorySetExpGet(t *testing.T) {
34
+	mc := NewMemoryCache(0)
35
+	//mc.stopEviction()
36
+	mc.Set("1", "10", 10)
37
+	mc.Set("2", "5", 5)
38
+	err := mc.Set("3", "3", 3)
39
+	if err != nil {
40
+		t.Fatalf("写入失败")
41
+	}
42
+
43
+	res := ""
44
+	err = mc.Get("3", &res)
45
+	if err != nil {
46
+		t.Fatalf("读取失败" + err.Error())
47
+	}
48
+	fmt.Println("res 3", res)
49
+	time.Sleep(4 * time.Second)
50
+	//res = ""
51
+	err = mc.Get("3", &res)
52
+	if err != nil {
53
+		t.Fatalf("读取失败" + err.Error())
54
+	}
55
+	fmt.Println("res 3", res)
56
+	err = mc.Get("2", &res)
57
+	if err != nil {
58
+		t.Fatalf("读取失败" + err.Error())
59
+	}
60
+	fmt.Println("res 2", res)
61
+	err = mc.Get("1", &res)
62
+	if err != nil {
63
+		t.Fatalf("读取失败" + err.Error())
64
+	}
65
+	fmt.Println("res 1", res)
66
+
67
+}
68
+func TestMemoryLru(t *testing.T) {
69
+	mc := NewMemoryCache(18)
70
+	mc.Set("1", "1111", 10)
71
+	mc.Set("2", "2222", 5)
72
+	//读取一次,2就会被放到最后
73
+	mc.Get("1", nil)
74
+	err := mc.Set("3", "三", 3)
75
+	if err != nil {
76
+		//t.Fatalf("写入失败")
77
+	}
78
+
79
+	res := ""
80
+	err = mc.Get("3", &res)
81
+	if err != nil {
82
+		t.Fatalf("读取失败" + err.Error())
83
+	}
84
+	fmt.Println("res3", res)
85
+	res = ""
86
+	err = mc.Get("2", &res)
87
+	if err != nil {
88
+		t.Fatalf("读取失败" + err.Error())
89
+	}
90
+	fmt.Println("res2", res)
91
+	res = ""
92
+	err = mc.Get("1", &res)
93
+	if err != nil {
94
+		t.Fatalf("读取失败" + err.Error())
95
+	}
96
+	fmt.Println("res1", res)
97
+
98
+}
99
+func BenchmarkMemorySet(b *testing.B) {
100
+	mc := NewMemoryCache(0)
101
+	b.ResetTimer()
102
+	for i := 0; i < b.N; i++ {
103
+		key := fmt.Sprintf("key%d", i)
104
+		value := fmt.Sprintf("value%d", i)
105
+		mc.Set(key, value, 1000)
106
+	}
107
+}

+ 49 - 0
lib/cache/redis.go

@@ -0,0 +1,49 @@
1
+package cache
2
+
3
+import (
4
+	"context"
5
+	"github.com/go-redis/redis/v8"
6
+	"time"
7
+)
8
+
9
+var ctx = context.Background()
10
+
11
+type RedisCache struct {
12
+	rdb *redis.Client
13
+}
14
+
15
+func RedisCacheInit(conf *redis.Options) *RedisCache {
16
+	c := &RedisCache{}
17
+	c.rdb = redis.NewClient(conf)
18
+	return c
19
+}
20
+
21
+func (c *RedisCache) Get(key string, value interface{}) error {
22
+	data, err := c.rdb.Get(ctx, key).Result()
23
+	if err != nil {
24
+		return err
25
+	}
26
+	err1 := DecodeValue(data, value)
27
+	return err1
28
+}
29
+
30
+func (c *RedisCache) Set(key string, value interface{}, exp int) error {
31
+	str, err := EncodeValue(value)
32
+	if err != nil {
33
+		return err
34
+	}
35
+	if exp <= 0 {
36
+		exp = MaxTimeOut
37
+	}
38
+	_, err1 := c.rdb.Set(ctx, key, str, time.Duration(exp)*time.Second).Result()
39
+	return err1
40
+}
41
+
42
+func (c *RedisCache) Gc() error {
43
+	return nil
44
+}
45
+
46
+func NewRedis(conf *redis.Options) *RedisCache {
47
+	cache := RedisCacheInit(conf)
48
+	return cache
49
+}

+ 94 - 0
lib/cache/redis_test.go

@@ -0,0 +1,94 @@
1
+package cache
2
+
3
+import (
4
+	"fmt"
5
+	"github.com/go-redis/redis/v8"
6
+	"reflect"
7
+	"testing"
8
+)
9
+
10
+func TestRedisSet(t *testing.T) {
11
+	//rc := New("redis")
12
+	rc := RedisCacheInit(&redis.Options{
13
+		Addr:     "192.168.1.168:6379",
14
+		Password: "", // no password set
15
+		DB:       0,  // use default DB
16
+	})
17
+	err := rc.Set("123", "ddd", 0)
18
+	if err != nil {
19
+		fmt.Println(err.Error())
20
+		t.Fatalf("写入失败")
21
+	}
22
+}
23
+
24
+func TestRedisGet(t *testing.T) {
25
+	rc := RedisCacheInit(&redis.Options{
26
+		Addr:     "192.168.1.168:6379",
27
+		Password: "", // no password set
28
+		DB:       0,  // use default DB
29
+	})
30
+	err := rc.Set("123", "451156", 300)
31
+	if err != nil {
32
+		t.Fatalf("写入失败")
33
+	}
34
+	res := ""
35
+	err = rc.Get("123", &res)
36
+	if err != nil {
37
+		t.Fatalf("读取失败")
38
+	}
39
+	fmt.Println("res", res)
40
+}
41
+
42
+func TestRedisGetJson(t *testing.T) {
43
+	rc := RedisCacheInit(&redis.Options{
44
+		Addr:     "192.168.1.168:6379",
45
+		Password: "", // no password set
46
+		DB:       0,  // use default DB
47
+	})
48
+	type r struct {
49
+		Aa string `json:"a"`
50
+		B  string `json:"c"`
51
+	}
52
+	old := &r{
53
+		Aa: "ab", B: "cdc",
54
+	}
55
+	err := rc.Set("1233", old, 300)
56
+	if err != nil {
57
+		t.Fatalf("写入失败")
58
+	}
59
+
60
+	res := &r{}
61
+	err2 := rc.Get("1233", res)
62
+	if err2 != nil {
63
+		t.Fatalf("读取失败")
64
+	}
65
+	if !reflect.DeepEqual(res, old) {
66
+		t.Fatalf("读取错误")
67
+	}
68
+	fmt.Println(res, res.Aa)
69
+}
70
+
71
+func BenchmarkRSet(b *testing.B) {
72
+	rc := RedisCacheInit(&redis.Options{
73
+		Addr:     "192.168.1.168:6379",
74
+		Password: "", // no password set
75
+		DB:       0,  // use default DB
76
+	})
77
+	b.ResetTimer()
78
+	for i := 0; i < b.N; i++ {
79
+		rc.Set("123", "{dsv}", 1000)
80
+	}
81
+}
82
+
83
+func BenchmarkRGet(b *testing.B) {
84
+	rc := RedisCacheInit(&redis.Options{
85
+		Addr:     "192.168.1.168:6379",
86
+		Password: "", // no password set
87
+		DB:       0,  // use default DB
88
+	})
89
+	b.ResetTimer()
90
+	v := ""
91
+	for i := 0; i < b.N; i++ {
92
+		rc.Get("123", &v)
93
+	}
94
+}

+ 65 - 0
lib/cache/simple_cache.go

@@ -0,0 +1,65 @@
1
+package cache
2
+
3
+import (
4
+	"errors"
5
+	"reflect"
6
+	"sync"
7
+)
8
+
9
+// 此处实现了一个简单的缓存,用于测试
10
+// SimpleCache is a simple cache implementation
11
+type SimpleCache struct {
12
+	data      map[string]interface{}
13
+	mu        sync.Mutex
14
+	maxBytes  int64
15
+	usedBytes int64
16
+}
17
+
18
+func (s *SimpleCache) Get(key string, value interface{}) error {
19
+	s.mu.Lock()
20
+	defer s.mu.Unlock()
21
+
22
+	// 使用反射将存储的值设置到传入的指针变量中
23
+	val := reflect.ValueOf(value)
24
+	if val.Kind() != reflect.Ptr {
25
+		return errors.New("value must be a pointer")
26
+	}
27
+	v, ok := s.data[key]
28
+	if !ok {
29
+		//设为空值
30
+		val.Elem().Set(reflect.Zero(val.Elem().Type()))
31
+		return nil
32
+	}
33
+
34
+	vval := reflect.ValueOf(v)
35
+	if val.Elem().Type() != vval.Type() {
36
+		//设为空值
37
+		val.Elem().Set(reflect.Zero(val.Elem().Type()))
38
+		return nil
39
+	}
40
+
41
+	val.Elem().Set(reflect.ValueOf(v))
42
+	return nil
43
+}
44
+
45
+func (s *SimpleCache) Set(key string, value interface{}, exp int) error {
46
+	s.mu.Lock()
47
+	defer s.mu.Unlock()
48
+	// 检查传入的值是否是指针,如果是则取其值
49
+	val := reflect.ValueOf(value)
50
+	if val.Kind() == reflect.Ptr {
51
+		val = val.Elem()
52
+	}
53
+
54
+	s.data[key] = val.Interface()
55
+	return nil
56
+}
57
+func (s *SimpleCache) Gc() error {
58
+	return nil
59
+}
60
+
61
+func NewSimpleCache() *SimpleCache {
62
+	return &SimpleCache{
63
+		data: make(map[string]interface{}),
64
+	}
65
+}

+ 108 - 0
lib/cache/simple_cache_test.go

@@ -0,0 +1,108 @@
1
+package cache
2
+
3
+import (
4
+	"fmt"
5
+	"testing"
6
+)
7
+
8
+func TestSimpleCache_Set(t *testing.T) {
9
+	s := NewSimpleCache()
10
+	err := s.Set("key", "value", 0)
11
+	if err != nil {
12
+		t.Fatalf("写入失败")
13
+	}
14
+	err = s.Set("key", 111, 0)
15
+	if err != nil {
16
+		t.Fatalf("写入失败")
17
+	}
18
+}
19
+
20
+func TestSimpleCache_Get(t *testing.T) {
21
+	s := NewSimpleCache()
22
+	err := s.Set("key", "value", 0)
23
+	value := ""
24
+	err = s.Get("key", &value)
25
+	fmt.Println("value", value)
26
+	if err != nil {
27
+		t.Fatalf("读取失败")
28
+	}
29
+
30
+	err = s.Set("key1", 11, 0)
31
+	value1 := 0
32
+	err = s.Get("key1", &value1)
33
+	fmt.Println("value1", value1)
34
+	if err != nil {
35
+		t.Fatalf("读取失败")
36
+	}
37
+
38
+	err = s.Set("key2", []byte{'a', 'b'}, 0)
39
+	value2 := []byte{}
40
+	err = s.Get("key2", &value2)
41
+	fmt.Println("value2", string(value2))
42
+	if err != nil {
43
+		t.Fatalf("读取失败")
44
+	}
45
+
46
+	err = s.Set("key3", 33.33, 0)
47
+	var value3 int
48
+	err = s.Get("key3", &value3)
49
+	fmt.Println("value3", value3)
50
+	if err != nil {
51
+		t.Fatalf("读取失败")
52
+	}
53
+
54
+}
55
+
56
+type r struct {
57
+	A string `json:"a"`
58
+	B string `json:"b"`
59
+	R *rr    `json:"r"`
60
+}
61
+type r2 struct {
62
+	A string `json:"a"`
63
+	B string `json:"b"`
64
+}
65
+type rr struct {
66
+	AA string `json:"aa"`
67
+	BB string `json:"bb"`
68
+}
69
+
70
+func TestSimpleCache_GetStruct(t *testing.T) {
71
+	s := NewSimpleCache()
72
+
73
+	old_rr := &rr{
74
+		AA: "aa", BB: "bb",
75
+	}
76
+
77
+	old := &r{
78
+		A: "ab", B: "cdc",
79
+		R: old_rr,
80
+	}
81
+	err := s.Set("key", old, 300)
82
+	if err != nil {
83
+		t.Fatalf("写入失败")
84
+	}
85
+
86
+	res := &r{}
87
+	err2 := s.Get("key", res)
88
+	fmt.Println("res", res)
89
+	if err2 != nil {
90
+		t.Fatalf("读取失败" + err2.Error())
91
+
92
+	}
93
+
94
+	//修改原始值,看后面是否会变化
95
+	old.A = "aa"
96
+	old_rr.AA = "aaa"
97
+	fmt.Println("old", old)
98
+	res2 := &r{}
99
+	err3 := s.Get("key", res2)
100
+	fmt.Println("res2", res2, res2.R.AA, res2.R.BB)
101
+	if err3 != nil {
102
+		t.Fatalf("读取失败" + err3.Error())
103
+
104
+	}
105
+	//if reflect.DeepEqual(res, old) {
106
+	//	t.Fatalf("读取错误")
107
+	//}
108
+}

+ 61 - 0
lib/jwt/jwt.go

@@ -0,0 +1,61 @@
1
+package jwt
2
+
3
+import (
4
+	"crypto/rsa"
5
+	"github.com/golang-jwt/jwt/v5"
6
+	"os"
7
+	"time"
8
+)
9
+
10
+type Jwt struct {
11
+	privateKey          *rsa.PrivateKey
12
+	TokenExpireDuration time.Duration
13
+}
14
+
15
+type UserClaims struct {
16
+	UserId uint `json:"user_id"`
17
+	jwt.RegisteredClaims
18
+}
19
+
20
+func NewJwt(privateKeyFile string, tokenExpireDuration time.Duration) *Jwt {
21
+	privateKeyContent, err := os.ReadFile(privateKeyFile)
22
+	if err != nil {
23
+		panic(err)
24
+	}
25
+	privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyContent)
26
+	if err != nil {
27
+		panic(err)
28
+	}
29
+	return &Jwt{
30
+		privateKey:          privateKey,
31
+		TokenExpireDuration: tokenExpireDuration,
32
+	}
33
+}
34
+
35
+func (s *Jwt) GenerateToken(userId uint) string {
36
+	t := jwt.NewWithClaims(jwt.SigningMethodRS256,
37
+		UserClaims{
38
+			UserId: userId,
39
+			RegisteredClaims: jwt.RegisteredClaims{
40
+				ExpiresAt: jwt.NewNumericDate(time.Now().Add(s.TokenExpireDuration)),
41
+			},
42
+		})
43
+	token, err := t.SignedString(s.privateKey)
44
+	if err != nil {
45
+		return ""
46
+	}
47
+	return token
48
+}
49
+
50
+func (s *Jwt) ParseToken(tokenString string) (uint, error) {
51
+	token, err := jwt.ParseWithClaims(tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
52
+		return s.privateKey.Public(), nil
53
+	})
54
+	if err != nil {
55
+		return 0, err
56
+	}
57
+	if claims, ok := token.Claims.(*UserClaims); ok && token.Valid {
58
+		return claims.UserId, nil
59
+	}
60
+	return 0, err
61
+}

+ 80 - 0
lib/jwt/jwt_test.go

@@ -0,0 +1,80 @@
1
+package jwt
2
+
3
+import (
4
+	"fmt"
5
+	"testing"
6
+	"time"
7
+)
8
+
9
+var pk = `-----BEGIN RSA PRIVATE KEY-----
10
+MIIEowIBAAKCAQEAnJpq2Sy91iGW3+EuG4V2ke59tITpGINzht0rO8WiRwu11W4p
11
+wakS4K4BbjvmC8YjaxXhKE5LHDw0IXvTdIDN7Fuu4qs9xWXIoK+nC3qWrVBtj/1o
12
+RJrYme1NenTXEgPlN1FOU6/9XQGgvb+1MSNqxknYo7183mHACvsIIuSTMEFhUbUw
13
+XYVQrCtACUILZ9wIDOEzclIY2ZPMTnL1vkvfj629KwGtAvpEyc96Y/HMSH5/VkiG
14
+p6L+k+NSjco9HntAGYTiQkfranvdqxRDUsKS53SbV3QSz1zc0l5OEyZDuxFTL7UC
15
+7v0G/HVqz6mLpMje756PG/WEpwa/lADc/8FJ5QIDAQABAoIBAEsqUt6qevOsa55J
16
+lrfe92pT7kIXCUqazXiN75Jg6eLv2/b1SVWKsWTmIAmo9mHwWE+t0MRnz+VdgCgS
17
+JwxkRnKMDwT87Eky8Xku1h7MWEYXtH7IQqOrLwuyut1r907OT9adT9sbPaDGh0CM
18
+I4vSVA2YpELzUFvszyB2HRGiZINkHfdLsNxUKsHJOdXbv82RItwzmCYcZismnR3J
19
+P8THn06eoBNtlqwdFziuREOzjNnj6J/3glhR5mu4c4+AJoj0hmVaBDfac3GsQsbP
20
+x79QQPrUqH9UZ4szubYHXP0uRi/ARlHQ+GNp6foYIsevC0OtLdau0/ouFlfGkEep
21
+3aIV5oECgYEAyyWrNhw+BhNFXsyPzEQ4/mO5ucup3cE/tAAtLiSckoXjmY8K7PQr
22
+xfKRCkuM1qpcxtYkbTs35aOdK48gL0NVd50QzrWFrQkQkVnpnJ1lYeVgEL1DmalD
23
+B55bwTdShcs0gEoKefZCvmotrmYdSpMGsapqqbZFrysFFzRDyDxnHfcCgYEAxVjA
24
+/dXxCEUjYFVC3i833lI/yiycJrhjIeffc6DqpSReuTU+i8Nh3sLiytaSqPFVASDS
25
+08K3JwVguMTzDgrYkl365lm50WxcBuNgLkSqA90vE/H6gkRZVkuzOb7T+ZdDxf0s
26
+7RH4aqeeOSiOcZ3uC+d53UArJFidETXbgguXkAMCgYA22Ynbx05b15IwYW0mCvmU
27
+fhqkdr/7lvT7RdztC4eW7D2itYOOrPKwtKjCrdluEHuSWDlnoMib4UxLeY6IFFcc
28
+P7VNCqf4K21kwXEZD0pTX1pLyr5Y2+G0SeaeSbCnXVFknhksCvjEbui8oOehvgbd
29
+q5S3E/bGsAfk1wDCLMTuywKBgACHrH0CBhOvm9i2YeeW2N+P+PviAslX1WxR4xe8
30
+ZuTqpBZ7Ph/B9pFSlKlWyi4J9+B45hgLfdJtAUV9welXvh0mg3X657TYRab/FVMK
31
+fCpmfangDHwtEtBYg7K0AH27GkN92pEIa1JeAN7GbRuBARKnHHyrn3IJiuJw8pX2
32
+0gFhAoGBAIquI9sAB2dKEOMW+iQJkLH8Hh8/EWyslow+QJiyIsRe1l9jtkOxC5D3
33
+Hj4yO4j5LOWDMTgDcLsZTxbGiTzkNc/HghrNIevDAQdgjJQNl84zDjyyCA4r/MA7
34
+bYJTtYj8q6J0EDbRdT9b6hMclyzjNXdx2loJxR0R8WUeL1lDEPq8
35
+-----END RSA PRIVATE KEY-----`
36
+
37
+// 测试token生成
38
+func TestGenerateToken(t *testing.T) {
39
+	jwtService := NewJwt(pk, time.Second*1000)
40
+	token := jwtService.GenerateToken(1)
41
+	if token == "" {
42
+		t.Fatal("token生成失败")
43
+	}
44
+	fmt.Println(pk, token)
45
+}
46
+
47
+// 测试token解析
48
+func TestParseToken(t *testing.T) {
49
+	jwtService := NewJwt(pk, time.Second*1000)
50
+	token := jwtService.GenerateToken(999)
51
+	if token == "" {
52
+		t.Fatal("token生成失败")
53
+	}
54
+	uid, err := jwtService.ParseToken(token)
55
+	if err != nil {
56
+
57
+		t.Fatal("token解析失败", err)
58
+	}
59
+	if uid != 999 {
60
+		t.Fatal("token解析失败")
61
+	}
62
+}
63
+
64
+func BenchmarkJwtService_GenerateToken(b *testing.B) {
65
+	jwtService := NewJwt(pk, time.Second*1000)
66
+	b.ResetTimer()
67
+	for i := 0; i < b.N; i++ {
68
+		jwtService.GenerateToken(999)
69
+	}
70
+}
71
+
72
+func BenchmarkJwtService_ParseToken(b *testing.B) {
73
+	jwtService := NewJwt(pk, time.Second*1000)
74
+	token := jwtService.GenerateToken(999)
75
+	b.ResetTimer()
76
+	for i := 0; i < b.N; i++ {
77
+		_, _ = jwtService.ParseToken(token)
78
+	}
79
+
80
+}

+ 32 - 0
lib/lock/local.go

@@ -0,0 +1,32 @@
1
+package lock
2
+
3
+import (
4
+	"sync"
5
+)
6
+
7
+type Local struct {
8
+	Locks *sync.Map
9
+}
10
+
11
+func (l *Local) Lock(key string) {
12
+	lock := l.GetLock(key)
13
+	lock.Lock()
14
+}
15
+
16
+func (l *Local) UnLock(key string) {
17
+	lock, ok := l.Locks.Load(key)
18
+	if ok {
19
+		lock.(*sync.Mutex).Unlock()
20
+	}
21
+}
22
+
23
+func (l *Local) GetLock(key string) *sync.Mutex {
24
+	lock, _ := l.Locks.LoadOrStore(key, &sync.Mutex{})
25
+	return lock.(*sync.Mutex)
26
+}
27
+
28
+func NewLocal() *Local {
29
+	return &Local{
30
+		Locks: &sync.Map{},
31
+	}
32
+}

+ 100 - 0
lib/lock/local_test.go

@@ -0,0 +1,100 @@
1
+package lock
2
+
3
+import (
4
+	"fmt"
5
+	"sync"
6
+	"testing"
7
+)
8
+
9
+func TestLocal_GetLock(t *testing.T) {
10
+	l := NewLocal()
11
+	wg := sync.WaitGroup{}
12
+	wg.Add(3)
13
+	var l1 *sync.Mutex
14
+	var l2 *sync.Mutex
15
+	var l3 *sync.Mutex
16
+	i := 0
17
+	go func() {
18
+		l1 = l.GetLock("key")
19
+		fmt.Println("l1", l1, i)
20
+		l1.Lock()
21
+		fmt.Println("l1", i)
22
+		i++
23
+		l1.Unlock()
24
+		wg.Done()
25
+	}()
26
+	go func() {
27
+		l2 = l.GetLock("key")
28
+		fmt.Println("l2", l2, i)
29
+		l2.Lock()
30
+		fmt.Println("l2", i)
31
+		i++
32
+		l2.Unlock()
33
+		wg.Done()
34
+	}()
35
+	go func() {
36
+		l3 = l.GetLock("key")
37
+		fmt.Println("l3", l3, i)
38
+		l3.Lock()
39
+		fmt.Println("l3", i)
40
+		i++
41
+		l3.Unlock()
42
+		wg.Done()
43
+	}()
44
+	wg.Wait()
45
+
46
+	fmt.Println(l1, l2, l3)
47
+	fmt.Println(l1 == l2, l2 == l3)
48
+	fmt.Println(&sync.Mutex{} == &sync.Mutex{})
49
+}
50
+
51
+func TestLocal_Lock(t *testing.T) {
52
+	l := NewLocal()
53
+	wg := sync.WaitGroup{}
54
+	wg.Add(3)
55
+	i := 0
56
+	go func() {
57
+		l.Lock("key")
58
+		fmt.Println("l1", i)
59
+		i++
60
+		l.UnLock("key")
61
+		wg.Done()
62
+	}()
63
+	go func() {
64
+		l.Lock("key")
65
+		fmt.Println("l2", i)
66
+		i++
67
+		l.UnLock("key")
68
+		wg.Done()
69
+	}()
70
+	go func() {
71
+		l.Lock("key")
72
+		fmt.Println("l3", i)
73
+		i++
74
+		l.UnLock("key")
75
+		wg.Done()
76
+	}()
77
+	wg.Wait()
78
+
79
+}
80
+func TestSyncMap(t *testing.T) {
81
+	m := sync.Map{}
82
+	wg := sync.WaitGroup{}
83
+	wg.Add(3)
84
+	go func() {
85
+		v, ok := m.LoadOrStore("key", 1)
86
+		fmt.Println(1, v, ok)
87
+		wg.Done()
88
+	}()
89
+	go func() {
90
+		v, ok := m.LoadOrStore("key", 2)
91
+		fmt.Println(2, v, ok)
92
+		wg.Done()
93
+	}()
94
+	go func() {
95
+		v, ok := m.LoadOrStore("key", 3)
96
+		fmt.Println(3, v, ok)
97
+		wg.Done()
98
+	}()
99
+	wg.Wait()
100
+}

+ 9 - 0
lib/lock/lock.go

@@ -0,0 +1,9 @@
1
+package lock
2
+
3
+import "sync"
4
+
5
+type Locker interface {
6
+	GetLock(key string) *sync.Mutex
7
+	Lock(key string)
8
+	UnLock(key string)
9
+}

+ 54 - 0
lib/logger/logger.go

@@ -0,0 +1,54 @@
1
+package logger
2
+
3
+import (
4
+	nested "github.com/antonfisher/nested-logrus-formatter"
5
+	log "github.com/sirupsen/logrus"
6
+	"io"
7
+	"os"
8
+)
9
+
10
+const (
11
+	DebugMode   = "debug"
12
+	ReleaseMode = "release"
13
+)
14
+
15
+type Config struct {
16
+	Path         string
17
+	Level        string
18
+	ReportCaller bool
19
+}
20
+
21
+func New(c *Config) *log.Logger {
22
+	log.SetFormatter(&nested.Formatter{
23
+		// HideKeys:        true,
24
+		TimestampFormat: "2006-01-02 15:04:05",
25
+		NoColors:        true,
26
+		NoFieldsColors:  true,
27
+		//FieldsOrder:     []string{"name", "age"},
28
+	})
29
+
30
+	// 日志文件
31
+	f := c.Path
32
+	var write io.Writer
33
+	if f != "" {
34
+		fwriter, err := os.OpenFile(f, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
35
+		if err != nil {
36
+			panic("open log file fail!")
37
+		}
38
+		write = io.MultiWriter(fwriter, os.Stdout)
39
+	} else {
40
+		write = os.Stdout
41
+	}
42
+
43
+	log.SetOutput(write)
44
+
45
+	log.SetReportCaller(c.ReportCaller)
46
+
47
+	level, err2 := log.ParseLevel(c.Level)
48
+	if err2 != nil {
49
+		level = log.DebugLevel
50
+	}
51
+	log.SetLevel(level)
52
+
53
+	return log.StandardLogger()
54
+}

+ 40 - 0
lib/orm/mysql.go

@@ -0,0 +1,40 @@
1
+package orm
2
+
3
+import (
4
+	"fmt"
5
+	"gorm.io/driver/mysql"
6
+	"gorm.io/gorm"
7
+)
8
+
9
+type MysqlConfig struct {
10
+	Dns          string
11
+	MaxIdleConns int
12
+	MaxOpenConns int
13
+}
14
+
15
+func NewMysql(mysqlConf *MysqlConfig) *gorm.DB {
16
+	db, err := gorm.Open(mysql.New(mysql.Config{
17
+		DSN:               mysqlConf.Dns, // DSN data source name
18
+		DefaultStringSize: 256,           // string 类型字段的默认长度
19
+		//DisableDatetimePrecision:  true,                    // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
20
+		//DontSupportRenameIndex:    true,                    // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
21
+		//DontSupportRenameColumn:   true,                    // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
22
+		//SkipInitializeWithVersion: false,                   // 根据当前 MySQL 版本自动配置
23
+	}), &gorm.Config{
24
+		DisableForeignKeyConstraintWhenMigrating: true,
25
+	})
26
+	if err != nil {
27
+		fmt.Println(err)
28
+	}
29
+	sqlDB, err2 := db.DB()
30
+	if err2 != nil {
31
+		fmt.Println(err2)
32
+	}
33
+	// SetMaxIdleConns 设置空闲连接池中连接的最大数量
34
+	sqlDB.SetMaxIdleConns(mysqlConf.MaxIdleConns)
35
+
36
+	// SetMaxOpenConns 设置打开数据库连接的最大数量。
37
+	sqlDB.SetMaxOpenConns(mysqlConf.MaxOpenConns)
38
+
39
+	return db
40
+}

+ 30 - 0
lib/orm/sqlite.go

@@ -0,0 +1,30 @@
1
+package orm
2
+
3
+import (
4
+	"fmt"
5
+	"gorm.io/driver/sqlite"
6
+	"gorm.io/gorm"
7
+)
8
+
9
+type SqliteConfig struct {
10
+	MaxIdleConns int
11
+	MaxOpenConns int
12
+}
13
+
14
+func NewSqlite(sqliteConf *SqliteConfig) *gorm.DB {
15
+	db, err := gorm.Open(sqlite.Open("./data/rustdeskapi.db"), &gorm.Config{})
16
+	if err != nil {
17
+		fmt.Println(err)
18
+	}
19
+	sqlDB, err2 := db.DB()
20
+	if err2 != nil {
21
+		fmt.Println(err2)
22
+	}
23
+	// SetMaxIdleConns 设置空闲连接池中连接的最大数量
24
+	sqlDB.SetMaxIdleConns(sqliteConf.MaxIdleConns)
25
+
26
+	// SetMaxOpenConns 设置打开数据库连接的最大数量。
27
+	sqlDB.SetMaxOpenConns(sqliteConf.MaxOpenConns)
28
+
29
+	return db
30
+}

+ 4 - 0
lib/upload/local.go

@@ -0,0 +1,4 @@
1
+package upload
2
+
3
+type Local struct {
4
+}

+ 475 - 0
lib/upload/oss.go

@@ -0,0 +1,475 @@
1
+package upload
2
+
3
+import (
4
+	"bytes"
5
+	"crypto"
6
+	"crypto/hmac"
7
+	"crypto/md5"
8
+	"crypto/rsa"
9
+	"crypto/sha1"
10
+	"crypto/x509"
11
+	"encoding/base64"
12
+	"encoding/json"
13
+	"encoding/pem"
14
+	"errors"
15
+	"fmt"
16
+	"hash"
17
+	"io"
18
+	"io/ioutil"
19
+	"net/http"
20
+	"strconv"
21
+	"time"
22
+)
23
+
24
+type Oss struct {
25
+	AccessKeyId     string
26
+	AccessKeySecret string
27
+	Host            string
28
+	CallbackUrl     string
29
+	ExpireTime      int64
30
+	MaxByte         int64
31
+}
32
+
33
+const (
34
+	base64Table = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"
35
+)
36
+
37
+var coder = base64.NewEncoding(base64Table)
38
+
39
+func base64Encode(src []byte) []byte {
40
+	return []byte(coder.EncodeToString(src))
41
+}
42
+
43
+func get_gmt_iso8601(expire_end int64) string {
44
+	var tokenExpire = time.Unix(expire_end, 0).UTC().Format("2006-01-02T15:04:05Z")
45
+	return tokenExpire
46
+}
47
+
48
+type ConfigStruct struct {
49
+	Expiration string          `json:"expiration"`
50
+	Conditions [][]interface{} `json:"conditions"`
51
+}
52
+
53
+type PolicyToken struct {
54
+	AccessKeyId string `json:"accessid"`
55
+	Host        string `json:"host"`
56
+	Expire      int64  `json:"expire"`
57
+	Signature   string `json:"signature"`
58
+	Policy      string `json:"policy"`
59
+	Directory   string `json:"dir"`
60
+	Callback    string `json:"callback"`
61
+}
62
+
63
+type CallbackParam struct {
64
+	CallbackUrl      string `json:"callbackUrl"`
65
+	CallbackBody     string `json:"callbackBody"`
66
+	CallbackBodyType string `json:"callbackBodyType"`
67
+}
68
+
69
+type CallbackBaseForm struct {
70
+	Bucket         string `json:"bucket" form:"bucket"`
71
+	Etag           string `json:"etag" form:"etag"`
72
+	Filename       string `json:"filename" form:"filename"`
73
+	Size           string `json:"size" form:"size"`
74
+	MimeType       string `json:"mime_type" form:"mime_type"`
75
+	Height         string `json:"height" form:"height"`
76
+	Width          string `json:"width" form:"width"`
77
+	Format         string `json:"format" form:"format"`
78
+	OriginFilename string `json:"origin_filename" form:"origin_filename"`
79
+}
80
+
81
+func (oc *Oss) GetPolicyToken(uploadDir string) string {
82
+	now := time.Now().Unix()
83
+	expire_end := now + oc.ExpireTime
84
+	var tokenExpire = get_gmt_iso8601(expire_end)
85
+
86
+	//create post policy json
87
+	var config ConfigStruct
88
+	config.Expiration = tokenExpire
89
+	var condition = []interface{}{"starts-with", "$key", uploadDir}
90
+	var condition_limit = []interface{}{"content-length-range", 0, oc.MaxByte}
91
+	config.Conditions = append(config.Conditions, condition, condition_limit)
92
+
93
+	//calucate signature
94
+	result, err := json.Marshal(config)
95
+	debyte := base64.StdEncoding.EncodeToString(result)
96
+	h := hmac.New(func() hash.Hash {
97
+		return sha1.New()
98
+	}, []byte(oc.AccessKeySecret))
99
+	io.WriteString(h, debyte)
100
+	signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil))
101
+
102
+	var callbackParam CallbackParam
103
+	callbackParam.CallbackUrl = oc.CallbackUrl
104
+
105
+	callbackParam.CallbackBody =
106
+		"bucket=${bucket}&" +
107
+			"etag=${etag}&" +
108
+			"filename=${object}&" +
109
+			"size=${size}&" +
110
+			"mime_type=${mimeType}&" +
111
+			"height=${imageInfo.height}&" +
112
+			"width=${imageInfo.width}&" +
113
+			"format=${imageInfo.format}&" +
114
+			"origin_filename=${x:origin_filename}"
115
+	callbackParam.CallbackBodyType = "application/x-www-form-urlencoded"
116
+	callback_str, err := json.Marshal(callbackParam)
117
+	if err != nil {
118
+		fmt.Println("callback json err:", err)
119
+	}
120
+	callbackBase64 := base64.StdEncoding.EncodeToString(callback_str)
121
+
122
+	var policyToken PolicyToken
123
+	policyToken.AccessKeyId = oc.AccessKeyId
124
+	policyToken.Host = oc.Host
125
+	policyToken.Expire = expire_end
126
+	policyToken.Signature = string(signedStr)
127
+	policyToken.Directory = uploadDir
128
+	policyToken.Policy = string(debyte)
129
+	policyToken.Callback = string(callbackBase64)
130
+	response, err := json.Marshal(policyToken)
131
+	if err != nil {
132
+		fmt.Println("json err:", err)
133
+	}
134
+	return string(response)
135
+}
136
+
137
+func (oc *Oss) Verify(r *http.Request) bool {
138
+
139
+	// Get PublicKey bytes
140
+	bytePublicKey, err := getPublicKey(r)
141
+	if err != nil {
142
+		return false
143
+	}
144
+
145
+	// Get Authorization bytes : decode from Base64String
146
+	byteAuthorization, err := getAuthorization(r)
147
+	if err != nil {
148
+		return false
149
+	}
150
+
151
+	// Get MD5 bytes from Newly Constructed Authrization String.
152
+	byteMD5, err := getMD5FromNewAuthString(r)
153
+	if err != nil {
154
+		return false
155
+	}
156
+
157
+	// verifySignature and response to client
158
+	if verifySignature(bytePublicKey, byteMD5, byteAuthorization) {
159
+		// do something you want accoding to callback_body ...
160
+		return true
161
+	} else {
162
+		return false
163
+	}
164
+}
165
+
166
+// getPublicKey : Get PublicKey bytes from Request.URL
167
+func getPublicKey(r *http.Request) ([]byte, error) {
168
+	var bytePublicKey []byte
169
+	// get PublicKey URL
170
+	publicKeyURLBase64 := r.Header.Get("x-oss-pub-key-url")
171
+	if publicKeyURLBase64 == "" {
172
+		fmt.Println("GetPublicKey from Request header failed :  No x-oss-pub-key-url field. ")
173
+		return bytePublicKey, errors.New("no x-oss-pub-key-url field in Request header ")
174
+	}
175
+	publicKeyURL, _ := base64.StdEncoding.DecodeString(publicKeyURLBase64)
176
+	// fmt.Printf("publicKeyURL={%s}\n", publicKeyURL)
177
+	// get PublicKey Content from URL
178
+	responsePublicKeyURL, err := http.Get(string(publicKeyURL))
179
+	if err != nil {
180
+		fmt.Printf("Get PublicKey Content from URL failed : %s \n", err.Error())
181
+		return bytePublicKey, err
182
+	}
183
+	bytePublicKey, err = ioutil.ReadAll(responsePublicKeyURL.Body)
184
+	if err != nil {
185
+		fmt.Printf("Read PublicKey Content from URL failed : %s \n", err.Error())
186
+		return bytePublicKey, err
187
+	}
188
+	defer responsePublicKeyURL.Body.Close()
189
+	// fmt.Printf("publicKey={%s}\n", bytePublicKey)
190
+	return bytePublicKey, nil
191
+}
192
+
193
+// getAuthorization : decode from Base64String
194
+func getAuthorization(r *http.Request) ([]byte, error) {
195
+	var byteAuthorization []byte
196
+	// Get Authorization bytes : decode from Base64String
197
+	strAuthorizationBase64 := r.Header.Get("authorization")
198
+	if strAuthorizationBase64 == "" {
199
+		fmt.Println("Failed to get authorization field from request header. ")
200
+		return byteAuthorization, errors.New("no authorization field in Request header")
201
+	}
202
+	byteAuthorization, _ = base64.StdEncoding.DecodeString(strAuthorizationBase64)
203
+	return byteAuthorization, nil
204
+}
205
+
206
+// getMD5FromNewAuthString : Get MD5 bytes from Newly Constructed Authrization String.
207
+func getMD5FromNewAuthString(r *http.Request) ([]byte, error) {
208
+	var byteMD5 []byte
209
+	// Construct the New Auth String from URI+Query+Body
210
+	bodyContent, err := ioutil.ReadAll(r.Body)
211
+	r.Body.Close()
212
+	r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyContent))
213
+	if err != nil {
214
+		fmt.Printf("Read Request Body failed : %s \n", err.Error())
215
+		return byteMD5, err
216
+	}
217
+	strCallbackBody := string(bodyContent)
218
+	// fmt.Printf("r.URL.RawPath={%s}, r.URL.Query()={%s}, strCallbackBody={%s}\n", r.URL.RawPath, r.URL.Query(), strCallbackBody)
219
+	strURLPathDecode, errUnescape := unescapePath(r.URL.Path, encodePathSegment) //url.PathUnescape(r.URL.Path) for Golang v1.8.2+
220
+	if errUnescape != nil {
221
+		fmt.Printf("url.PathUnescape failed : URL.Path=%s, error=%s \n", r.URL.Path, err.Error())
222
+		return byteMD5, errUnescape
223
+	}
224
+
225
+	// Generate New Auth String prepare for MD5
226
+	strAuth := ""
227
+	if r.URL.RawQuery == "" {
228
+		strAuth = fmt.Sprintf("%s\n%s", strURLPathDecode, strCallbackBody)
229
+	} else {
230
+		strAuth = fmt.Sprintf("%s?%s\n%s", strURLPathDecode, r.URL.RawQuery, strCallbackBody)
231
+	}
232
+	// fmt.Printf("NewlyConstructedAuthString={%s}\n", strAuth)
233
+
234
+	// Generate MD5 from the New Auth String
235
+	md5Ctx := md5.New()
236
+	md5Ctx.Write([]byte(strAuth))
237
+	byteMD5 = md5Ctx.Sum(nil)
238
+
239
+	return byteMD5, nil
240
+}
241
+
242
+/*  VerifySignature
243
+*   VerifySignature需要三个重要的数据信息来进行签名验证: 1>获取公钥PublicKey;  2>生成新的MD5鉴权串;  3>解码Request携带的鉴权串;
244
+*   1>获取公钥PublicKey : 从RequestHeader的"x-oss-pub-key-url"字段中获取 URL, 读取URL链接的包含的公钥内容, 进行解码解析, 将其作为rsa.VerifyPKCS1v15的入参。
245
+*   2>生成新的MD5鉴权串 : 把Request中的url中的path部分进行urldecode, 加上url的query部分, 再加上body, 组合之后进行MD5编码, 得到MD5鉴权字节串。
246
+*   3>解码Request携带的鉴权串 : 获取RequestHeader的"authorization"字段, 对其进行Base64解码,作为签名验证的鉴权对比串。
247
+*   rsa.VerifyPKCS1v15进行签名验证,返回验证结果。
248
+* */
249
+func verifySignature(bytePublicKey []byte, byteMd5 []byte, authorization []byte) bool {
250
+	pubBlock, _ := pem.Decode(bytePublicKey)
251
+	if pubBlock == nil {
252
+		fmt.Printf("Failed to parse PEM block containing the public key")
253
+		return false
254
+	}
255
+	pubInterface, err := x509.ParsePKIXPublicKey(pubBlock.Bytes)
256
+	if (pubInterface == nil) || (err != nil) {
257
+		fmt.Printf("x509.ParsePKIXPublicKey(publicKey) failed : %s \n", err.Error())
258
+		return false
259
+	}
260
+	pub := pubInterface.(*rsa.PublicKey)
261
+
262
+	errorVerifyPKCS1v15 := rsa.VerifyPKCS1v15(pub, crypto.MD5, byteMd5, authorization)
263
+	if errorVerifyPKCS1v15 != nil {
264
+		fmt.Printf("\nSignature Verification is Failed : %s \n", errorVerifyPKCS1v15.Error())
265
+		//printByteArray(byteMd5, "AuthMd5(fromNewAuthString)")
266
+		//printByteArray(bytePublicKey, "PublicKeyBase64")
267
+		//printByteArray(authorization, "AuthorizationFromRequest")
268
+		return false
269
+	}
270
+
271
+	fmt.Printf("\nSignature Verification is Successful. \n")
272
+	return true
273
+}
274
+
275
+func printByteArray(byteArrary []byte, arrName string) {
276
+	fmt.Printf("++++++++ printByteArray :  ArrayName=%s, ArrayLength=%d \n", arrName, len(byteArrary))
277
+	for i := 0; i < len(byteArrary); i++ {
278
+		fmt.Printf("%02x", byteArrary[i])
279
+	}
280
+	fmt.Printf("\n-------- printByteArray :  End . \n")
281
+}
282
+
283
+type EscapeError string
284
+
285
+func (e EscapeError) Error() string {
286
+	return "invalid URL escape " + strconv.Quote(string(e))
287
+}
288
+
289
+type InvalidHostError string
290
+
291
+func (e InvalidHostError) Error() string {
292
+	return "invalid character " + strconv.Quote(string(e)) + " in host name"
293
+}
294
+
295
+type encoding int
296
+
297
+const (
298
+	encodePath encoding = 1 + iota
299
+	encodePathSegment
300
+	encodeHost
301
+	encodeZone
302
+	encodeUserPassword
303
+	encodeQueryComponent
304
+	encodeFragment
305
+)
306
+
307
+// unescapePath : unescapes a string; the mode specifies, which section of the URL string is being unescaped.
308
+func unescapePath(s string, mode encoding) (string, error) {
309
+	// Count %, check that they're well-formed.
310
+	mode = encodePathSegment
311
+	n := 0
312
+	hasPlus := false
313
+	for i := 0; i < len(s); {
314
+		switch s[i] {
315
+		case '%':
316
+			n++
317
+			if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
318
+				s = s[i:]
319
+				if len(s) > 3 {
320
+					s = s[:3]
321
+				}
322
+				return "", EscapeError(s)
323
+			}
324
+			// Per https://tools.ietf.org/html/rfc3986#page-21
325
+			// in the host component %-encoding can only be used
326
+			// for non-ASCII bytes.
327
+			// But https://tools.ietf.org/html/rfc6874#section-2
328
+			// introduces %25 being allowed to escape a percent sign
329
+			// in IPv6 scoped-address literals. Yay.
330
+			if mode == encodeHost && unhex(s[i+1]) < 8 && s[i:i+3] != "%25" {
331
+				return "", EscapeError(s[i : i+3])
332
+			}
333
+			if mode == encodeZone {
334
+				// RFC 6874 says basically "anything goes" for zone identifiers
335
+				// and that even non-ASCII can be redundantly escaped,
336
+				// but it seems prudent to restrict %-escaped bytes here to those
337
+				// that are valid host name bytes in their unescaped form.
338
+				// That is, you can use escaping in the zone identifier but not
339
+				// to introduce bytes you couldn't just write directly.
340
+				// But Windows puts spaces here! Yay.
341
+				v := unhex(s[i+1])<<4 | unhex(s[i+2])
342
+				if s[i:i+3] != "%25" && v != ' ' && shouldEscape(v, encodeHost) {
343
+					return "", EscapeError(s[i : i+3])
344
+				}
345
+			}
346
+			i += 3
347
+		case '+':
348
+			hasPlus = mode == encodeQueryComponent
349
+			i++
350
+		default:
351
+			if (mode == encodeHost || mode == encodeZone) && s[i] < 0x80 && shouldEscape(s[i], mode) {
352
+				return "", InvalidHostError(s[i : i+1])
353
+			}
354
+			i++
355
+		}
356
+	}
357
+
358
+	if n == 0 && !hasPlus {
359
+		return s, nil
360
+	}
361
+
362
+	t := make([]byte, len(s)-2*n)
363
+	j := 0
364
+	for i := 0; i < len(s); {
365
+		switch s[i] {
366
+		case '%':
367
+			t[j] = unhex(s[i+1])<<4 | unhex(s[i+2])
368
+			j++
369
+			i += 3
370
+		case '+':
371
+			if mode == encodeQueryComponent {
372
+				t[j] = ' '
373
+			} else {
374
+				t[j] = '+'
375
+			}
376
+			j++
377
+			i++
378
+		default:
379
+			t[j] = s[i]
380
+			j++
381
+			i++
382
+		}
383
+	}
384
+	return string(t), nil
385
+}
386
+
387
+// Please be informed that for now shouldEscape does not check all
388
+// reserved characters correctly. See golang.org/issue/5684.
389
+func shouldEscape(c byte, mode encoding) bool {
390
+	// §2.3 Unreserved characters (alphanum)
391
+	if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' {
392
+		return false
393
+	}
394
+
395
+	if mode == encodeHost || mode == encodeZone {
396
+		// §3.2.2 Host allows
397
+		//	sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
398
+		// as part of reg-name.
399
+		// We add : because we include :port as part of host.
400
+		// We add [ ] because we include [ipv6]:port as part of host.
401
+		// We add < > because they're the only characters left that
402
+		// we could possibly allow, and Parse will reject them if we
403
+		// escape them (because hosts can't use %-encoding for
404
+		// ASCII bytes).
405
+		switch c {
406
+		case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '[', ']', '<', '>', '"':
407
+			return false
408
+		}
409
+	}
410
+
411
+	switch c {
412
+	case '-', '_', '.', '~': // §2.3 Unreserved characters (mark)
413
+		return false
414
+
415
+	case '$', '&', '+', ',', '/', ':', ';', '=', '?', '@': // §2.2 Reserved characters (reserved)
416
+		// Different sections of the URL allow a few of
417
+		// the reserved characters to appear unescaped.
418
+		switch mode {
419
+		case encodePath: // §3.3
420
+			// The RFC allows : @ & = + $ but saves / ; , for assigning
421
+			// meaning to individual path segments. This package
422
+			// only manipulates the path as a whole, so we allow those
423
+			// last three as well. That leaves only ? to escape.
424
+			return c == '?'
425
+
426
+		case encodePathSegment: // §3.3
427
+			// The RFC allows : @ & = + $ but saves / ; , for assigning
428
+			// meaning to individual path segments.
429
+			return c == '/' || c == ';' || c == ',' || c == '?'
430
+
431
+		case encodeUserPassword: // §3.2.1
432
+			// The RFC allows ';', ':', '&', '=', '+', '$', and ',' in
433
+			// userinfo, so we must escape only '@', '/', and '?'.
434
+			// The parsing of userinfo treats ':' as special so we must escape
435
+			// that too.
436
+			return c == '@' || c == '/' || c == '?' || c == ':'
437
+
438
+		case encodeQueryComponent: // §3.4
439
+			// The RFC reserves (so we must escape) everything.
440
+			return true
441
+
442
+		case encodeFragment: // §4.1
443
+			// The RFC text is silent but the grammar allows
444
+			// everything, so escape nothing.
445
+			return false
446
+		}
447
+	}
448
+
449
+	// Everything else must be escaped.
450
+	return true
451
+}
452
+
453
+func ishex(c byte) bool {
454
+	switch {
455
+	case '0' <= c && c <= '9':
456
+		return true
457
+	case 'a' <= c && c <= 'f':
458
+		return true
459
+	case 'A' <= c && c <= 'F':
460
+		return true
461
+	}
462
+	return false
463
+}
464
+
465
+func unhex(c byte) byte {
466
+	switch {
467
+	case '0' <= c && c <= '9':
468
+		return c - '0'
469
+	case 'a' <= c && c <= 'f':
470
+		return c - 'a' + 10
471
+	case 'A' <= c && c <= 'F':
472
+		return c - 'A' + 10
473
+	}
474
+	return 0
475
+}

+ 44 - 0
model/addressBook.go

@@ -0,0 +1,44 @@
1
+package model
2
+
3
+import "Gwen/model/custom_types"
4
+
5
+// final String id;
6
+// String hash; // personal ab hash password
7
+// String password; // shared ab password
8
+// String username; // pc username
9
+// String hostname;
10
+// String platform;
11
+// String alias;
12
+// List<dynamic> tags;
13
+// bool forceAlwaysRelay = false;
14
+// String rdpPort;
15
+// String rdpUsername;
16
+// bool online = false;
17
+// String loginName; //login username
18
+// bool? sameServer;
19
+
20
+// AddressBook 有些字段是Personal才会上传的
21
+type AddressBook struct {
22
+	RowId            uint                  `gorm:"primaryKey" json:"row_id"`
23
+	Id               string                `json:"id" gorm:"default:0;not null;index"`
24
+	Username         string                `json:"username" gorm:"default:'';not null;"`
25
+	Password         string                `json:"password" gorm:"default:'';not null;"`
26
+	Hostname         string                `json:"hostname" gorm:"default:'';not null;"`
27
+	Alias            string                `json:"alias" gorm:"default:'';not null;"`
28
+	Platform         string                `json:"platform" gorm:"default:'';not null;"`
29
+	Tags             custom_types.AutoJson `json:"tags" gorm:"not null;" swaggertype:"array,string"`
30
+	Hash             string                `json:"hash" gorm:"default:'';not null;"`
31
+	UserId           uint                  `json:"user_id" gorm:"default:0;not null;index"`
32
+	ForceAlwaysRelay bool                  `json:"forceAlwaysRelay" gorm:"default:0;not null;"`
33
+	RdpPort          string                `json:"rdpPort" gorm:"default:'';not null;"`
34
+	RdpUsername      string                `json:"rdpUsername" gorm:"default:'';not null;"`
35
+	Online           bool                  `json:"online" gorm:"default:0;not null;"`
36
+	LoginName        string                `json:"loginName" gorm:"default:'';not null;"`
37
+	SameServer       bool                  `json:"sameServer" gorm:"default:0;not null;"`
38
+	TimeModel
39
+}
40
+
41
+type AddressBookList struct {
42
+	AddressBooks []*AddressBook `json:"list"`
43
+	Pagination
44
+}

+ 66 - 0
model/custom_types/auto_json.go

@@ -0,0 +1,66 @@
1
+package custom_types
2
+
3
+import (
4
+	"database/sql/driver"
5
+	"encoding/json"
6
+	"errors"
7
+	"fmt"
8
+)
9
+
10
+// AutoJson 数据类型
11
+type AutoJson json.RawMessage
12
+
13
+func (j *AutoJson) Scan(value interface{}) error {
14
+
15
+	var strValue string
16
+	switch v := value.(type) {
17
+	case []byte:
18
+		strValue = string(v)
19
+	case string:
20
+		strValue = v
21
+	default:
22
+		return errors.New(fmt.Sprintf("Failed Scan AutoJson value: %v", value))
23
+	}
24
+	bytes := []byte(strValue)
25
+	//bytes, ok := value.([]byte)
26
+	//if !ok {
27
+	//	return errors.New(fmt.Sprint("Failed Scan AutoJson value:", value))
28
+	//}
29
+
30
+	if bytes == nil || len(bytes) == 0 {
31
+		*j = AutoJson(json.RawMessage{'[', ']'})
32
+		return nil
33
+	}
34
+	result := &json.RawMessage{}
35
+	err := json.Unmarshal(bytes, result)
36
+	//解析json错误 返回空
37
+	if err != nil {
38
+		*j = AutoJson(json.RawMessage{'[', ']'})
39
+		return nil
40
+	}
41
+	*j = AutoJson(*result)
42
+	return err
43
+}
44
+func (j AutoJson) Value() (driver.Value, error) {
45
+	bytes, err := json.RawMessage(j).MarshalJSON()
46
+	return string(bytes), err
47
+}
48
+func (j AutoJson) MarshalJSON() ([]byte, error) {
49
+	b, err := json.RawMessage(j).MarshalJSON()
50
+	if err != nil {
51
+		return nil, err
52
+	}
53
+	return b, err
54
+}
55
+
56
+func (j *AutoJson) UnmarshalJSON(b []byte) error {
57
+	result := json.RawMessage{}
58
+	err := result.UnmarshalJSON(b)
59
+	*j = AutoJson(result)
60
+	return err
61
+}
62
+
63
+func (j AutoJson) String() string {
64
+	s, _ := j.MarshalJSON()
65
+	return (string)(s)
66
+}

+ 24 - 0
model/custom_types/auto_time.go

@@ -0,0 +1,24 @@
1
+package custom_types
2
+
3
+import (
4
+	"database/sql/driver"
5
+	"time"
6
+)
7
+
8
+// AutoTime 自定义时间格式
9
+type AutoTime time.Time
10
+
11
+func (mt AutoTime) Value() (driver.Value, error) {
12
+	var zeroTime time.Time
13
+	t := time.Time(mt)
14
+	if t.UnixNano() == zeroTime.UnixNano() {
15
+		return nil, nil
16
+	}
17
+	return t, nil
18
+}
19
+
20
+func (mt AutoTime) MarshalJSON() ([]byte, error) {
21
+	//b := make([]byte, 0, len("2006-01-02 15:04:05")+2)
22
+	b := time.Time(mt).AppendFormat([]byte{}, "\"2006-01-02 15:04:05\"")
23
+	return b, nil
24
+}

+ 18 - 0
model/group.go

@@ -0,0 +1,18 @@
1
+package model
2
+
3
+const (
4
+	GroupTypeDefault = 1 // 默认
5
+	GroupTypeShare   = 2 // 共享
6
+)
7
+
8
+type Group struct {
9
+	IdModel
10
+	Name string `json:"name" gorm:"default:'';not null;"`
11
+	Type int    `json:"type" gorm:"default:1;not null;"`
12
+	TimeModel
13
+}
14
+
15
+type GroupList struct {
16
+	Groups []*Group `json:"list"`
17
+	Pagination
18
+}

+ 0 - 0
model/model.go


Некоторые файлы не были показаны из-за большого количества измененных файлов