🆕导入配置文件完成

This commit is contained in:
coward 2024-09-23 15:58:46 +08:00
parent edaf9ba770
commit f2dcb13e0d
10 changed files with 261 additions and 36 deletions

View File

@ -36,6 +36,18 @@ func Error(err error) string {
return errMsg
}
// Validate
// @description: 校验
// @param data
// @return string
func Validate(data any) error {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
return v.Struct(data)
}
return nil
}
// initValidatorTranslator
// @description: 初始化翻译机
// @receiver vli

View File

@ -2,9 +2,13 @@ package api
import (
"encoding/json"
"errors"
"fmt"
"gitee.ltd/lxh/logger/log"
"github.com/gin-gonic/gin"
"os"
"slices"
"wireguard-ui/component"
"wireguard-ui/http/param"
"wireguard-ui/http/response"
"wireguard-ui/http/vo"
@ -152,11 +156,62 @@ func (setting) Export(c *gin.Context) {
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Connection", "keep-alive")
c.File(filepath)
//if err = os.Remove(filepath); err != nil {
// log.Errorf("删除临时文件失败: %s", err.Error())
//}
if err = os.Remove(filepath); err != nil {
log.Errorf("删除临时文件失败: %s", err.Error())
}
}
// Import
// @description: 导入配置
// @receiver setting
// @param c
func (setting) Import(c *gin.Context) {
var p param.Import
if err := c.ShouldBind(&p); err != nil {
response.R(c).Validator(err)
return
}
// 校验文件是否合规
if p.File.Filename != "config.json" {
response.R(c).Validator(errors.New("文件名不合规"))
return
}
// 校验文件内容是否符合
fileBytes, err := p.File.Open()
if err != nil {
response.R(c).FailedWithError(err)
return
}
var data vo.Export
if err := json.NewDecoder(fileBytes).Decode(&data); err != nil {
response.R(c).FailedWithError(err)
return
}
// 校验json串是否合规
if err = component.Validate(&data); err != nil {
response.R(c).Validator(err)
return
}
// 获取当前登陆用户
var loginUser *vo.User
if loginUser = GetCurrentLoginUser(c); c.IsAborted() {
return
}
if loginUser.Account != "admin" {
response.R(c).FailedWithError("非法操作,你被捕啦!")
return
}
if err = service.Setting().Import(&data, loginUser); err != nil {
response.R(c).FailedWithError(fmt.Errorf("导入失败: %v", err.Error()))
return
}
response.R(c).OK()
}

View File

@ -1,5 +1,7 @@
package param
import "mime/multipart"
// SetSetting
// @description: 添加/编辑设置
type SetSetting struct {
@ -7,3 +9,9 @@ type SetSetting struct {
Data string `json:"data" form:"data" binding:"required"`
Describe string `json:"describe" form:"describe" binding:"omitempty"`
}
// Import
// @description: 导入
type Import struct {
File *multipart.FileHeader `json:"file" form:"file" binding:"required"`
}

View File

@ -18,5 +18,6 @@ func SettingApi(r *gin.RouterGroup) {
setting.GET("/all", api.Setting().GetAllSetting) // 获取全部配置
setting.GET("/public-addr", api.Setting().GetPublicAddr) // 获取公网IP
setting.GET("/export", api.Setting().Export) // 导出配置文件
setting.POST("/import", api.Setting().Import) // 导入配置
}
}

View File

@ -1,5 +1,7 @@
package vo
import "wireguard-ui/global/constant"
// SettingItem
// @description: 设置单项
type SettingItem struct {
@ -9,44 +11,44 @@ type SettingItem struct {
}
type Export struct {
Global Global `json:"global"`
Server Server `json:"server"`
Clients []Client `json:"clients"`
Global *Global `json:"global" label:"全局配置" binding:"required"`
Server *Server `json:"server" label:"服务端配置" binding:"required"`
Clients []Client `json:"clients" label:"客户端" binding:"omitempty"`
}
type Global struct {
MTU int `json:"MTU"`
ConfigFilePath string `json:"configFilePath"`
DnsServer []string `json:"dnsServer"`
EndpointAddress string `json:"endpointAddress"`
FirewallMark string `json:"firewallMark"`
PersistentKeepalive int `json:"persistentKeepalive"`
Table string `json:"table"`
MTU int `json:"MTU" label:"MTU" binding:"required"`
ConfigFilePath string `json:"configFilePath" label:"配置文件路径" binding:"required"`
DnsServer []string `json:"dnsServer" label:"DNS" binding:"omitempty"`
EndpointAddress string `json:"endpointAddress" label:"公网地址" binding:"required"`
FirewallMark string `json:"firewallMark" label:"firewallMark" binding:"omitempty"`
PersistentKeepalive int `json:"persistentKeepalive" label:"persistentKeepalive" binding:"required"`
Table string `json:"table" label:"table" binding:"omitempty"`
}
type Server struct {
IpScope []string `json:"ipScope"`
ListenPort int `json:"listenPort"`
PrivateKey string `json:"privateKey"`
PublicKey string `json:"publicKey"`
PostUpScript string `json:"postUpScript"`
PostDownScript string `json:"postDownScript"`
IpScope []string `json:"ipScope" label:"ipScope" binding:"min=1,dive,required"`
ListenPort int `json:"listenPort" label:"listenPort" binding:"required"`
PrivateKey string `json:"privateKey" label:"privateKey" binding:"required"`
PublicKey string `json:"publicKey" label:"publicKey" binding:"required"`
PostUpScript string `json:"postUpScript" label:"postUpScript" binding:"omitempty"`
PostDownScript string `json:"postDownScript" label:"postDownScript" binding:"omitempty"`
}
type Client struct {
Name string `json:"name"`
Email string `json:"email"`
SubnetRange string `json:"subnetRange"`
IpAllocation []string `json:"ipAllocation"`
AllowedIps []string `json:"allowedIps"`
ExtraAllowedIps []string `json:"extraAllowedIps"`
Endpoint string `json:"endpoint"`
UseServerDns int `json:"useServerDns"`
Name string `json:"name" label:"name" binding:"required"`
Email string `json:"email" label:"email" binding:"omitempty"`
SubnetRange string `json:"subnetRange" label:"subnetRange" binding:"omitempty"`
IpAllocation []string `json:"ipAllocation" label:"ipAllocation" binding:"min=1,dive,required"`
AllowedIps []string `json:"allowedIps" label:"allowedIps" binding:"min=1,dive,required"`
ExtraAllowedIps []string `json:"extraAllowedIps" label:"extraAllowedIps" binding:"omitempty"`
Endpoint string `json:"endpoint" label:"endpoint" binding:"endpoint"`
UseServerDns *constant.Status `json:"useServerDns" label:"useServerDns" binding:"omitempty"`
Keys struct {
PresharedKey string `json:"presharedKey"`
PrivateKey string `json:"privateKey"`
PublicKey string `json:"publicKey"`
} `json:"keys"`
Enabled int `json:"enabled"`
OfflineMonitoring int `json:"offlineMonitoring"`
PresharedKey string `json:"presharedKey" label:"presharedKey" binding:"required"`
PrivateKey string `json:"privateKey" label:"privateKey" binding:"required"`
PublicKey string `json:"publicKey" label:"publicKey" binding:"required"`
} `json:"keys" label:"keys" binding:"required"`
Enabled *constant.Status `json:"enabled" label:"enabled" binding:"required"`
OfflineMonitoring *constant.Status `json:"offlineMonitoring" label:"offlineMonitoring" binding:"required"`
}

View File

@ -2,9 +2,11 @@ package service
import (
"encoding/json"
slog "gitee.ltd/lxh/logger/log"
"gorm.io/gorm"
"strings"
gdb "wireguard-ui/global/client"
"wireguard-ui/http/param"
"wireguard-ui/http/vo"
"wireguard-ui/model"
"wireguard-ui/template/render_data"
@ -142,3 +144,60 @@ func (s setting) Export() (data vo.Export, err error) {
return
}
// Import
// @description: 导入
// @receiver s
// @param data
// @return err
func (s setting) Import(data *vo.Export, loginUser *vo.User) (err error) {
// 先更新global配置
gst, _ := json.Marshal(data.Global)
gs := &model.Setting{
Code: "WG_SETTING",
Data: string(gst),
Describe: "服务端全局配置",
}
if err = s.SetData(gs); err != nil {
return
}
st, _ := json.Marshal(data.Server)
ss := &model.Setting{
Code: "WG_SERVER",
Data: string(st),
Describe: "服务端配置",
}
if err = s.SetData(ss); err != nil {
return
}
// 更新client配置
for _, v := range data.Clients {
keys := &param.Keys{
PrivateKey: v.Keys.PrivateKey,
PublicKey: v.Keys.PublicKey,
PresharedKey: v.Keys.PresharedKey,
}
cc := param.SaveClient{
Name: v.Name,
Email: v.Email,
IpAllocation: v.IpAllocation,
AllowedIps: v.AllowedIps,
ExtraAllowedIps: v.ExtraAllowedIps,
Endpoint: v.Endpoint,
UseServerDns: v.UseServerDns,
Keys: keys,
Enabled: v.Enabled,
OfflineMonitoring: v.OfflineMonitoring,
}
if err := Client().SaveClient(cc, loginUser); err != nil {
slog.Errorf("客户端[%s]导入失败: %v", v.Name, err)
continue
}
}
return nil
}

View File

@ -32,6 +32,7 @@
},
"dependencies": {
"@unocss/eslint-config": "^0.55.7",
"@vicons/ionicons5": "^0.12.0",
"@vueuse/core": "^10.4.1",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "5.1.12",

8
web/pnpm-lock.yaml generated
View File

@ -11,6 +11,9 @@ importers:
'@unocss/eslint-config':
specifier: ^0.55.7
version: 0.55.7(eslint@8.50.0)(typescript@5.2.2)
'@vicons/ionicons5':
specifier: ^0.12.0
version: 0.12.0
'@vueuse/core':
specifier: ^10.4.1
version: 10.4.1(vue@3.3.4)
@ -1046,6 +1049,9 @@ packages:
'@vavt/util@1.4.0':
resolution: {integrity: sha512-qLhaokwifMTFqoo4UE2JZUyaxCzX9T4WcIt2KzznbtBrCM4CG119pY/cKqq6jDa3c1phUvPCoIfjWfnF9nj4NA==}
'@vicons/ionicons5@0.12.0':
resolution: {integrity: sha512-Iy1EUVRpX0WWxeu1VIReR1zsZLMc4fqpt223czR+Rpnrwu7pt46nbnC2ycO7ItI/uqDLJxnbcMC7FujKs9IfFA==}
'@vite-plugin-vue-devtools/core@1.0.0-rc.7':
resolution: {integrity: sha512-Tv9JeRZQ6KDwSkOQJvXc5TBcc4fkSazA96GDhi99v4VCthTgXjnhaah41CeZD3hFDKnNS0MHKFFqN+RHAgYDyQ==}
peerDependencies:
@ -5490,6 +5496,8 @@ snapshots:
'@vavt/util@1.4.0': {}
'@vicons/ionicons5@0.12.0': {}
'@vite-plugin-vue-devtools/core@1.0.0-rc.7(vite@4.4.11(@types/node@20.5.1)(sass@1.69.0)(terser@5.21.0))':
dependencies:
'@babel/parser': 7.23.0

View File

@ -1,5 +1,10 @@
import { request } from '@/utils'
export default {
exportConfig: () => request.get('/setting/export'), // 获取当前登陆用户信息
exportConfig: () => request.get('/setting/export'), // 导出配置
importConfig: (data) => request.post('/setting/import',data,{
headers: {
'Content-Type': 'multipart/form-data'
}
}),
}

View File

@ -5,13 +5,55 @@
<n-icon mr-20 size="18" style="cursor: pointer" @click="exportConfig()">
<icon-ph-export-bold />
</n-icon>
<n-modal
v-model:show="showImportUploader"
transform-origin="center"
preset="card"
title="导入配置"
:bordered="false"
size="large"
style="width: 400px"
header-style="text-align: center"
>
<n-upload
ref="uploadRef"
:multiple="false"
directory-dnd
:max="1"
:custom-request="customRequest"
name="file"
accept=".json"
:default-upload="false"
@before-upload="uploadCheck"
>
<n-upload-dragger>
<div style="margin-bottom: 12px">
<n-icon size="48" :depth="3">
<ArchiveIcon />
</n-icon>
</div>
<n-text style="font-size: 16px">
点击或者拖动文件到该区域来上传
</n-text>
<n-p depth="3" style="margin: 8px 0 0 0">
注意上传后将覆盖原有的配置以及客户端数据等请谨慎操作
</n-p>
</n-upload-dragger>
</n-upload>
<n-button type="primary" style="margin-left: 40%" @click="submitUpload">确定</n-button>
</n-modal>
</template>
<script setup>
import api from '@/api/setting'
import { ArchiveOutline as ArchiveIcon } from "@vicons/ionicons5";
const showImportUploader = ref(false)
const uploadRef = ref(null)
//
function importConfig() {
console.log('导入配置')
showImportUploader.value = true
}
//
@ -19,7 +61,7 @@ function exportConfig() {
$dialog.confirm({
type: 'warning',
title: '导出配置',
content: `是否导出需要导出系统全部配置?`,
content: `是否需要导出系统全部配置?`,
async confirm() {
api.exportConfig().then(response => {
const blob = new Blob([JSON.stringify(response.data)], {
@ -37,4 +79,36 @@ function exportConfig() {
},
})
}
//
function uploadCheck(data) {
if (data.file.file?.name !== "config.json") {
$message.error("导入文件只能是[config.json]");
return false;
}
if (data.file.file?.type !== "application/json") {
$message.error("只能上传json类型文件请重新上传");
return false;
}
return true;
}
//
function customRequest(file) {
api.importConfig({
file: file.file.file,
}).then(response => {
if (response.data.code === 200) {
showImportUploader.value = false;
} else {
$message.error(response.data.message);
}
})
}
//
function submitUpload() {
uploadRef.value?.submit();
}
</script>