mirror of
https://github.com/TeaOSLab/EdgeAdmin.git
synced 2025-11-03 04:10:27 +08:00
优化代码/支持IP名单的更多格式的导入、导出
This commit is contained in:
2
go.mod
2
go.mod
@@ -9,7 +9,6 @@ require (
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/iwind/TeaGo v0.0.0-20210720011303-fc255c995afa
|
||||
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3
|
||||
@@ -21,5 +20,6 @@ require (
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22
|
||||
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced // indirect
|
||||
google.golang.org/grpc v1.38.0
|
||||
google.golang.org/protobuf v1.26.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
||||
)
|
||||
|
||||
@@ -39,6 +39,10 @@ func (this *IndexAction) RunGet(params struct {
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
recipientMaps := []maps.Map{}
|
||||
for _, recipient := range recipientsResp.MessageRecipients {
|
||||
|
||||
@@ -35,6 +35,10 @@ func (this *IndexAction) RunGet(params struct {
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
instanceMaps := []maps.Map{}
|
||||
for _, instance := range instancesResp.MessageMediaInstances {
|
||||
|
||||
@@ -62,6 +62,10 @@ func (this *LogsAction) RunGet(params struct {
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
logs := []maps.Map{}
|
||||
for _, log := range logsResp.NodeLogs {
|
||||
|
||||
@@ -51,6 +51,10 @@ func (this *IndexAction) RunGet(params struct {
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
logs := []maps.Map{}
|
||||
for _, log := range logsResp.NodeLogs {
|
||||
|
||||
@@ -3,10 +3,15 @@
|
||||
package iplists
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/tealeg/xlsx/v3"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
@@ -20,10 +25,55 @@ func (this *ExportDataAction) Init() {
|
||||
|
||||
func (this *ExportDataAction) RunGet(params struct {
|
||||
ListId int64
|
||||
Format string
|
||||
}) {
|
||||
defer this.CreateLogInfo("导出IP名单 %d", params.ListId)
|
||||
|
||||
resp := &pb.ListIPItemsWithListIdResponse{}
|
||||
var err error
|
||||
var ext = ""
|
||||
var jsonMaps = []maps.Map{}
|
||||
var xlsxFile *xlsx.File
|
||||
var xlsxSheet *xlsx.Sheet
|
||||
var csvWriter *csv.Writer
|
||||
var csvBuffer *bytes.Buffer
|
||||
|
||||
var data []byte
|
||||
|
||||
switch params.Format {
|
||||
case "xlsx":
|
||||
ext = ".xlsx"
|
||||
xlsxFile = xlsx.NewFile()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
xlsxSheet, err = xlsxFile.AddSheet("IP名单")
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
row := xlsxSheet.AddRow()
|
||||
row.SetHeight(26)
|
||||
row.AddCell().SetValue("开始IP")
|
||||
row.AddCell().SetValue("结束IP")
|
||||
row.AddCell().SetValue("过期时间戳")
|
||||
row.AddCell().SetValue("类型")
|
||||
row.AddCell().SetValue("级别")
|
||||
row.AddCell().SetValue("备注")
|
||||
case "csv":
|
||||
ext = ".csv"
|
||||
csvBuffer = &bytes.Buffer{}
|
||||
csvWriter = csv.NewWriter(csvBuffer)
|
||||
case "txt":
|
||||
ext = ".txt"
|
||||
case "json":
|
||||
ext = ".json"
|
||||
default:
|
||||
this.WriteString("请选择正确的导出格式")
|
||||
return
|
||||
}
|
||||
|
||||
var offset int64 = 0
|
||||
var size int64 = 1000
|
||||
for {
|
||||
@@ -40,18 +90,60 @@ func (this *ExportDataAction) RunGet(params struct {
|
||||
break
|
||||
}
|
||||
for _, item := range itemsResp.IpItems {
|
||||
resp.IpItems = append(resp.IpItems, item)
|
||||
switch params.Format {
|
||||
case "xlsx":
|
||||
row := xlsxSheet.AddRow()
|
||||
row.SetHeight(26)
|
||||
row.AddCell().SetValue(item.IpFrom)
|
||||
row.AddCell().SetValue(item.IpTo)
|
||||
row.AddCell().SetValue(types.String(item.ExpiredAt))
|
||||
row.AddCell().SetValue(item.Type)
|
||||
row.AddCell().SetValue(item.EventLevel)
|
||||
row.AddCell().SetValue(item.Reason)
|
||||
case "csv":
|
||||
err = csvWriter.Write([]string{item.IpFrom, item.IpTo, types.String(item.ExpiredAt), item.Type, item.EventLevel, item.Reason})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
case "txt":
|
||||
data = append(data, item.IpFrom+","+item.IpTo+","+types.String(item.ExpiredAt)+","+item.Type+","+item.EventLevel+","+item.Reason...)
|
||||
data = append(data, '\n')
|
||||
case "json":
|
||||
jsonMaps = append(jsonMaps, maps.Map{
|
||||
"ipFrom": item.IpFrom,
|
||||
"ipTo": item.IpTo,
|
||||
"expiredAt": item.ExpiredAt,
|
||||
"type": item.Type,
|
||||
"eventLevel": item.EventLevel,
|
||||
"reason": item.Reason,
|
||||
})
|
||||
}
|
||||
}
|
||||
offset += size
|
||||
}
|
||||
|
||||
data, err := proto.Marshal(resp)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
switch params.Format {
|
||||
case "xlsx":
|
||||
var buf = &bytes.Buffer{}
|
||||
err = xlsxFile.Write(buf)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
data = buf.Bytes()
|
||||
case "csv":
|
||||
csvWriter.Flush()
|
||||
data = csvBuffer.Bytes()
|
||||
case "json":
|
||||
data, err = json.Marshal(jsonMaps)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.AddHeader("Content-Disposition", "attachment; filename=\"ip-list-"+numberutils.FormatInt64(params.ListId)+".data\";")
|
||||
this.AddHeader("Content-Disposition", "attachment; filename=\"ip-list-"+numberutils.FormatInt64(params.ListId)+ext+"\";")
|
||||
this.AddHeader("Content-Length", strconv.Itoa(len(data)))
|
||||
this.Write(data)
|
||||
}
|
||||
|
||||
@@ -3,10 +3,19 @@
|
||||
package iplists
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/tealeg/xlsx/v3"
|
||||
"io"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ImportAction struct {
|
||||
@@ -51,20 +60,92 @@ func (this *ImportAction) RunPost(params struct {
|
||||
this.Fail("请选择要导入的IP文件")
|
||||
}
|
||||
|
||||
// 检查文件扩展名
|
||||
if !regexp.MustCompile(`(?i)\.(xlsx|csv|json|txt)$`).MatchString(params.File.Filename) {
|
||||
this.Fail("不支持当前格式的文件导入")
|
||||
}
|
||||
|
||||
var ext = strings.ToLower(params.File.Ext)
|
||||
|
||||
data, err := params.File.Read()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
resp := &pb.ListIPItemsWithListIdResponse{}
|
||||
err = proto.Unmarshal(data, resp)
|
||||
if err != nil {
|
||||
this.Fail("导入失败,文件格式错误:" + err.Error())
|
||||
|
||||
var countIgnore = 0
|
||||
var items = []*pb.IPItem{}
|
||||
switch ext {
|
||||
case ".xlsx":
|
||||
file, err := xlsx.OpenBinary(data)
|
||||
if err != nil {
|
||||
this.Fail("Excel读取错误:" + err.Error())
|
||||
}
|
||||
if len(file.Sheets) > 0 {
|
||||
var sheet = file.Sheets[0]
|
||||
err = sheet.ForEachRow(func(r *xlsx.Row) error {
|
||||
var values = []string{}
|
||||
err = r.ForEachCell(func(c *xlsx.Cell) error {
|
||||
values = append(values, c.Value)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
if values[0] == "开始IP" || values[0] == "IP" {
|
||||
return nil
|
||||
}
|
||||
item := this.createItemFromValues(values, &countIgnore)
|
||||
if item != nil {
|
||||
items = append(items, item)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
this.Fail("Excel读取错误:" + err.Error())
|
||||
}
|
||||
}
|
||||
case ".csv":
|
||||
reader := csv.NewReader(bytes.NewBuffer(data))
|
||||
for {
|
||||
values, err := reader.Read()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
this.Fail("CSV读取错误:" + err.Error())
|
||||
}
|
||||
item := this.createItemFromValues(values, &countIgnore)
|
||||
if item != nil {
|
||||
items = append(items, item)
|
||||
}
|
||||
}
|
||||
case ".json":
|
||||
err = json.Unmarshal(data, &items)
|
||||
if err != nil {
|
||||
this.Fail("导入失败:" + err.Error())
|
||||
}
|
||||
case ".txt":
|
||||
lines := bytes.Split(data, []byte{'\n'})
|
||||
for _, line := range lines {
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
item := this.createItemFromValues(strings.SplitN(string(line), ",", 6), &countIgnore)
|
||||
if item != nil {
|
||||
items = append(items, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var count = 0
|
||||
var countIgnore = 0
|
||||
for _, item := range resp.IpItems {
|
||||
|
||||
lists.Reverse(items)
|
||||
|
||||
for _, item := range items {
|
||||
_, err = this.RPC().IPItemRPC().CreateIPItem(this.AdminContext(), &pb.CreateIPItemRequest{
|
||||
IpListId: params.ListId,
|
||||
IpFrom: item.IpFrom,
|
||||
@@ -85,3 +166,48 @@ func (this *ImportAction) RunPost(params struct {
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
func (this *ImportAction) createItemFromValues(values []string, countIgnore *int) *pb.IPItem {
|
||||
// ipFrom, ipTo, expiredAt, type, eventType, reason
|
||||
|
||||
var item = &pb.IPItem{}
|
||||
switch len(values) {
|
||||
case 1:
|
||||
item.IpFrom = values[0]
|
||||
case 2:
|
||||
item.IpFrom = values[0]
|
||||
item.IpTo = values[1]
|
||||
case 3:
|
||||
item.IpFrom = values[0]
|
||||
item.IpTo = values[1]
|
||||
item.ExpiredAt = types.Int64(values[2])
|
||||
case 4:
|
||||
item.IpFrom = values[0]
|
||||
item.IpTo = values[1]
|
||||
item.ExpiredAt = types.Int64(values[2])
|
||||
item.Type = values[3]
|
||||
case 5:
|
||||
item.IpFrom = values[0]
|
||||
item.IpTo = values[1]
|
||||
item.ExpiredAt = types.Int64(values[2])
|
||||
item.Type = values[3]
|
||||
item.EventLevel = values[4]
|
||||
case 6:
|
||||
item.IpFrom = values[0]
|
||||
item.IpTo = values[1]
|
||||
item.ExpiredAt = types.Int64(values[2])
|
||||
item.Type = values[3]
|
||||
item.EventLevel = values[4]
|
||||
item.Reason = values[5]
|
||||
}
|
||||
|
||||
if net.ParseIP(item.IpFrom) == nil {
|
||||
*countIgnore++
|
||||
return nil
|
||||
}
|
||||
if len(item.IpTo) > 0 && net.ParseIP(item.IpTo) == nil {
|
||||
*countIgnore++
|
||||
return nil
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ func (this *SortAction) RunPost(params struct {
|
||||
}
|
||||
if webConfig == nil {
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
refMap := map[int64]*serverconfigs.HTTPLocationRef{}
|
||||
for _, ref := range webConfig.LocationRefs {
|
||||
|
||||
@@ -121,6 +121,10 @@ func (this *SettingAction) RunPost(params struct {
|
||||
MaxConns: types.Int32(reverseProxyConfig.MaxConns),
|
||||
MaxIdleConns: types.Int32(reverseProxyConfig.MaxIdleConns),
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -2,12 +2,24 @@
|
||||
{$template "list_menu"}
|
||||
|
||||
<div class="margin"></div>
|
||||
<form class="ui form">
|
||||
<form class="ui form" method="get" action="/servers/iplists/exportData">
|
||||
<input type="hidden" name="listId" :value="list.id"/>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">说明</td>
|
||||
<td>导出所有的IP</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>格式</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" name="format">
|
||||
<option value="xlsx">Excel</option>
|
||||
<option value="csv">CSV</option>
|
||||
<option value="txt">TXT</option>
|
||||
<option value="json">JSON</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<a :href="'/servers/iplists/exportData?listId=' + list.id" class="ui button primary">导出</a>
|
||||
<submit-btn>导出</submit-btn>
|
||||
</form>
|
||||
@@ -3,15 +3,15 @@
|
||||
|
||||
<div class="margin"></div>
|
||||
|
||||
<form class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<form class="ui form" data-tea-action="$" data-tea-success="success" data-tea-timeout="300">
|
||||
<input type="hidden" name="listId" :value="list.id"/>
|
||||
<csrf-token></csrf-token>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">选择IP文件 *</td>
|
||||
<td>
|
||||
<input type="file" name="file" accept=".data"/>
|
||||
<p class="comment">文件名类似于<code-label>ip-list-123.data</code-label>。</p>
|
||||
<input type="file" name="file" accept=".xlsx, .json, .txt, .csv"/>
|
||||
<p class="comment">文件名类似于<code-label>ip-list-123.xxx</code-label>,支持Excel、CSV、JSON和纯文本TXT。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
Reference in New Issue
Block a user