mirror of
				https://github.com/bestnite/sub2clash.git
				synced 2025-11-04 04:40:36 +00:00 
			
		
		
		
	v0.0.5
feat: 增加节点去重 feat: 增加节点重命名 feat: 增加节点过滤 feat: 增加短链密码 modify: 修改模板解析逻辑,现在需要添加 <all>,<countries> 来让程序解析模板 modify: 修改短链请求逻辑,不再跳转链接,而是服务器内部请求 modify: 完善 Meta 默认模板 如果你从旧版升级,请务必修改或删除程序目录下的模板
This commit is contained in:
		
							
								
								
									
										36
									
								
								API_README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								API_README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
# `/clash`, `/meta`
 | 
			
		||||
 | 
			
		||||
获取 Clash/Clash.Meta 配置链接
 | 
			
		||||
 | 
			
		||||
| Query 参数     | 类型     | 是否必须              | 默认值       | 说明                                                                                                                                                                          |
 | 
			
		||||
|--------------|--------|-------------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
 | 
			
		||||
| sub          | string | sub/proxy 至少有一项存在 | -         | 订阅链接(可以输入多个,用 `,` 分隔)                                                                                                                                                       |
 | 
			
		||||
| proxy        | string | sub/proxy 至少有一项存在 | -         | 节点分享链接(可以输入多个,用 `,` 分隔)                                                                                                                                                     |
 | 
			
		||||
| refresh      | bool   | 否                 | `false`   | 强制刷新配置(默认缓存 5 分钟)                                                                                                                                                           |
 | 
			
		||||
| template     | string | 否                 | -         | 外部模板链接或内部模板名称                                                                                                                                                               |
 | 
			
		||||
| ruleProvider | string | 否                 | -         | 格式 `[Behavior,Url,Group,Prepend,Name],[Behavior,Url,Group,Prepend,Name]...`,其中 `Group` 是该规则集使用的策略组名,`Prepend` 为 bool 类型,如果为 `true` 规则将被添加到规则列表顶部,否则添加到规则列表底部(会调整到MATCH规则之前) | 
 | 
			
		||||
| rule         | string | 否                 | -         | 格式 `[Rule,Prepend],[Rule,Prepend]...`,其中 `Prepend` 为 bool 类型,如果为 `true` 规则将被添加到规则列表顶部,否则添加到规则列表底部(会调整到MATCH规则之前)                                                            | 
 | 
			
		||||
| autoTest     | bool   | 否                 | `false`   | 国家策略组是否自动测速                                                                                                                                                                 |
 | 
			
		||||
| lazy         | bool   | 否                 | `false`   | 自动测速是否启用 lazy                                                                                                                                                               |
 | 
			
		||||
| sort         | string | 否                 | `nameasc` | 国家策略组排序策略,可选值 `nameasc`、`namedesc`、`sizeasc`、`sizedesc`                                                                                                                     |
 | 
			
		||||
| replace      | string | 否                 | -         | 通过正则表达式重命名节点,格式 `[<ReplaceKey>,<ReplaceTo>],[<ReplaceKey>,<ReplaceTo>]...`                                                                                                  |
 | 
			
		||||
| remove       | string | 否                 | -         | 通过正则表达式删除节点                                                                                                                                                                 |
 | 
			
		||||
 | 
			
		||||
# `/short`
 | 
			
		||||
 | 
			
		||||
获取短链,Content-Type 为 `application/json`
 | 
			
		||||
具体参考使用可以参考 [api\templates\index.html](./api/templates/index.html)
 | 
			
		||||
 | 
			
		||||
| Body 参数  | 类型     | 是否必须 | 默认值 | 说明               |
 | 
			
		||||
|----------|--------|------|-----|------------------|
 | 
			
		||||
| url      | string | 是    | -   | 需要转换的 Query 参数部分 |
 | 
			
		||||
| password | string | 否    | -   | 短链密码             |
 | 
			
		||||
 | 
			
		||||
# `/s/:hash`
 | 
			
		||||
 | 
			
		||||
短链跳转
 | 
			
		||||
`hash` 为动态路由参数,可以通过 `/short` 接口获取
 | 
			
		||||
 | 
			
		||||
| Query 参数 | 类型     | 是否必须 | 默认值 | 说明   |
 | 
			
		||||
|----------|--------|------|-----|------|
 | 
			
		||||
| password | string | 否    | -   | 短链密码 |
 | 
			
		||||
							
								
								
									
										24
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								README.md
									
									
									
									
									
								
							@@ -39,23 +39,17 @@
 | 
			
		||||
 | 
			
		||||
### API
 | 
			
		||||
 | 
			
		||||
#### `/clash`, `/meta`
 | 
			
		||||
[API文档](./API_README.md)
 | 
			
		||||
 | 
			
		||||
获取 Clash/Clash.Meta 配置链接
 | 
			
		||||
### 模板
 | 
			
		||||
 | 
			
		||||
| Query 参数     | 类型     | 是否必须              | 默认值       | 说明                                                                                                                                                                          |
 | 
			
		||||
|--------------|--------|-------------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
 | 
			
		||||
| sub          | string | sub/proxy 至少有一项存在 | -         | 订阅链接(可以输入多个,用 `,` 分隔)                                                                                                                                                       |
 | 
			
		||||
| proxy        | string | sub/proxy 至少有一项存在 | -         | 节点分享链接(可以输入多个,用 `,` 分隔)                                                                                                                                                     |
 | 
			
		||||
| refresh      | bool   | 否                 | `false`   | 强制刷新配置(默认缓存 5 分钟)                                                                                                                                                           |
 | 
			
		||||
| template     | string | 否                 | -         | 外部模板链接或内部模板名称                                                                                                                                                               |
 | 
			
		||||
| ruleProvider | string | 否                 | -         | 格式 `[Behavior,Url,Group,Prepend,Name],[Behavior,Url,Group,Prepend,Name]...`,其中 `Group` 是该规则集所走的策略组名,`Prepend` 为 bool 类型,如果为 `true` 规则将被添加到规则列表顶部,否则添加到规则列表底部(会调整到MATCH规则之前) | 
 | 
			
		||||
| rule         | string | 否                 | -         | 格式 `[Rule,Prepend],[Rule,Prepend]...`,其中 `Prepend` 为 bool 类型,如果为 `true` 规则将被添加到规则列表顶部,否则添加到规则列表底部(会调整到MATCH规则之前)                                                            | 
 | 
			
		||||
| autoTest     | bool   | 否                 | `false`   | 国家策略组是否自动测速                                                                                                                                                                 |
 | 
			
		||||
| lazy         | bool   | 否                 | `false`   | 自动测速是否启用 lazy                                                                                                                                                               |
 | 
			
		||||
| sort         | string | 否                 | `nameasc` | 国家策略组排序策略,可选值 `nameasc`、`namedesc`、`sizeasc`、`sizedesc`                                                                                                                     |
 | 
			
		||||
可以通过变量自定义模板中的策略组代理节点
 | 
			
		||||
解释的不太清楚,可以参考下方默认模板
 | 
			
		||||
 | 
			
		||||
## 默认模板
 | 
			
		||||
- `<all>` 为添加所有节点
 | 
			
		||||
- `<countries>` 为添加所有国家策略组
 | 
			
		||||
 | 
			
		||||
#### 默认模板
 | 
			
		||||
 | 
			
		||||
- [Clash](./templates/template_clash.yaml)
 | 
			
		||||
- [Clash.Meta](./templates/template_meta.yaml)
 | 
			
		||||
@@ -63,5 +57,3 @@
 | 
			
		||||
## 已知问题
 | 
			
		||||
 | 
			
		||||
[代理链接解析](./parser)还没有经过严格测试,可能会出现解析错误的情况,如果出现问题请提交 issue
 | 
			
		||||
 | 
			
		||||
## TODO
 | 
			
		||||
 
 | 
			
		||||
@@ -4,11 +4,14 @@ import (
 | 
			
		||||
	"crypto/sha256"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"go.uber.org/zap"
 | 
			
		||||
	"gopkg.in/yaml.v3"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sub2clash/logger"
 | 
			
		||||
	"sub2clash/model"
 | 
			
		||||
	"sub2clash/parser"
 | 
			
		||||
	"sub2clash/utils"
 | 
			
		||||
@@ -31,17 +34,24 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		templateBytes, err = utils.LoadTemplate(template)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logger.Logger.Debug(
 | 
			
		||||
				"load template failed", zap.String("template", template), zap.Error(err),
 | 
			
		||||
			)
 | 
			
		||||
			return nil, errors.New("加载模板失败: " + err.Error())
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		templateBytes, err = utils.LoadSubscription(template, query.Refresh)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logger.Logger.Debug(
 | 
			
		||||
				"load template failed", zap.String("template", template), zap.Error(err),
 | 
			
		||||
			)
 | 
			
		||||
			return nil, errors.New("加载模板失败: " + err.Error())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// 解析模板
 | 
			
		||||
	err = yaml.Unmarshal(templateBytes, &temp)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Logger.Debug("parse template failed", zap.Error(err))
 | 
			
		||||
		return nil, errors.New("解析模板失败: " + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	var proxyList []model.Proxy
 | 
			
		||||
@@ -49,13 +59,15 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
 | 
			
		||||
	for i := range query.Subs {
 | 
			
		||||
		data, err := utils.LoadSubscription(query.Subs[i], query.Refresh)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logger.Logger.Debug(
 | 
			
		||||
				"load subscription failed", zap.String("url", query.Subs[i]), zap.Error(err),
 | 
			
		||||
			)
 | 
			
		||||
			return nil, errors.New("加载订阅失败: " + err.Error())
 | 
			
		||||
		}
 | 
			
		||||
		// 解析订阅
 | 
			
		||||
 | 
			
		||||
		err = yaml.Unmarshal(data, &sub)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			reg, _ := regexp.Compile("(ssr|ss|vmess|trojan|http|https)://")
 | 
			
		||||
			reg, _ := regexp.Compile("(ssr|ss|vmess|trojan|vless)://")
 | 
			
		||||
			if reg.Match(data) {
 | 
			
		||||
				p := utils.ParseProxy(strings.Split(string(data), "\n")...)
 | 
			
		||||
				proxyList = append(proxyList, p...)
 | 
			
		||||
@@ -63,6 +75,11 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
 | 
			
		||||
				// 如果无法直接解析,尝试Base64解码
 | 
			
		||||
				base64, err := parser.DecodeBase64(string(data))
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logger.Logger.Debug(
 | 
			
		||||
						"parse subscription failed", zap.String("url", query.Subs[i]),
 | 
			
		||||
						zap.String("data", string(data)),
 | 
			
		||||
						zap.Error(err),
 | 
			
		||||
					)
 | 
			
		||||
					return nil, errors.New("加载订阅失败: " + err.Error())
 | 
			
		||||
				}
 | 
			
		||||
				p := utils.ParseProxy(strings.Split(base64, "\n")...)
 | 
			
		||||
@@ -72,14 +89,80 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
 | 
			
		||||
			proxyList = append(proxyList, sub.Proxies...)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// 添加自定义节点
 | 
			
		||||
	if len(query.Proxies) != 0 {
 | 
			
		||||
		proxyList = append(proxyList, utils.ParseProxy(query.Proxies...)...)
 | 
			
		||||
	}
 | 
			
		||||
	// 去掉配置相同的节点
 | 
			
		||||
	proxies := make(map[string]*model.Proxy)
 | 
			
		||||
	newProxies := make([]model.Proxy, 0, len(proxyList))
 | 
			
		||||
	for i := range proxyList {
 | 
			
		||||
		key := proxyList[i].Server + ":" + strconv.Itoa(proxyList[i].Port) + ":" + proxyList[i].Type
 | 
			
		||||
		if _, exist := proxies[key]; !exist {
 | 
			
		||||
			proxies[key] = &proxyList[i]
 | 
			
		||||
			newProxies = append(newProxies, proxyList[i])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	proxyList = newProxies
 | 
			
		||||
	// 删除节点
 | 
			
		||||
	if strings.TrimSpace(query.Remove) != "" {
 | 
			
		||||
		newProxyList := make([]model.Proxy, 0, len(proxyList))
 | 
			
		||||
		for i := range proxyList {
 | 
			
		||||
			removeReg, err := regexp.Compile(query.Remove)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.Logger.Debug("remove regexp compile failed", zap.Error(err))
 | 
			
		||||
				return nil, errors.New("remove 参数非法: " + err.Error())
 | 
			
		||||
			}
 | 
			
		||||
			// 删除匹配到的节点
 | 
			
		||||
			if removeReg.MatchString(proxyList[i].Name) {
 | 
			
		||||
				continue // 如果匹配到要删除的元素,跳过该元素,不添加到新切片中
 | 
			
		||||
			}
 | 
			
		||||
			newProxyList = append(newProxyList, proxyList[i]) // 将要保留的元素添加到新切片中
 | 
			
		||||
		}
 | 
			
		||||
		proxyList = newProxyList
 | 
			
		||||
	}
 | 
			
		||||
	// 重命名
 | 
			
		||||
	if len(query.ReplaceKeys) != 0 {
 | 
			
		||||
		// 创建重命名正则表达式
 | 
			
		||||
		replaceRegs := make([]*regexp.Regexp, 0, len(query.ReplaceKeys))
 | 
			
		||||
		for _, v := range query.ReplaceKeys {
 | 
			
		||||
			replaceReg, err := regexp.Compile(v)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.Logger.Debug("replace regexp compile failed", zap.Error(err))
 | 
			
		||||
				return nil, errors.New("replace 参数非法: " + err.Error())
 | 
			
		||||
			}
 | 
			
		||||
			replaceRegs = append(replaceRegs, replaceReg)
 | 
			
		||||
		}
 | 
			
		||||
		for i := range proxyList {
 | 
			
		||||
			// 重命名匹配到的节点
 | 
			
		||||
			for j, v := range replaceRegs {
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logger.Logger.Debug("replace regexp compile failed", zap.Error(err))
 | 
			
		||||
					return nil, errors.New("replaceName 参数非法: " + err.Error())
 | 
			
		||||
				}
 | 
			
		||||
				if v.MatchString(proxyList[i].Name) {
 | 
			
		||||
					proxyList[i].Name = v.ReplaceAllString(
 | 
			
		||||
						proxyList[i].Name, query.ReplaceTo[j],
 | 
			
		||||
					)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// 重名检测
 | 
			
		||||
	names := make(map[string]int)
 | 
			
		||||
	for i := range proxyList {
 | 
			
		||||
		if _, exist := names[proxyList[i].Name]; exist {
 | 
			
		||||
			proxyList[i].Name = proxyList[i].Name + " " + strconv.Itoa(names[proxyList[i].Name])
 | 
			
		||||
		}
 | 
			
		||||
		names[proxyList[i].Name] = names[proxyList[i].Name] + 1
 | 
			
		||||
	}
 | 
			
		||||
	// trim
 | 
			
		||||
	for i := range proxyList {
 | 
			
		||||
		proxyList[i].Name = strings.TrimSpace(proxyList[i].Name)
 | 
			
		||||
	}
 | 
			
		||||
	// 将新增节点都添加到临时变量 t 中,防止策略组排序错乱
 | 
			
		||||
	var t = &model.Subscription{}
 | 
			
		||||
	utils.AddProxy(t, query.AutoTest, query.Lazy, clashType, proxyList...)
 | 
			
		||||
	// 处理自定义代理
 | 
			
		||||
	utils.AddProxy(
 | 
			
		||||
		t, query.AutoTest, query.Lazy, clashType,
 | 
			
		||||
		utils.ParseProxy(query.Proxies...)...,
 | 
			
		||||
	)
 | 
			
		||||
	// 排序策略组
 | 
			
		||||
	switch query.Sort {
 | 
			
		||||
	case "sizeasc":
 | 
			
		||||
@@ -138,28 +221,28 @@ func MergeSubAndTemplate(temp *model.Subscription, sub *model.Subscription) {
 | 
			
		||||
			)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	var proxyNames []string
 | 
			
		||||
	for _, proxy := range sub.Proxies {
 | 
			
		||||
		proxyNames = append(proxyNames, proxy.Name)
 | 
			
		||||
	}
 | 
			
		||||
	// 将订阅中的节点添加到模板中
 | 
			
		||||
	temp.Proxies = append(temp.Proxies, sub.Proxies...)
 | 
			
		||||
	// 将订阅中的策略组添加到模板中
 | 
			
		||||
	skipGroups := []string{"全球直连", "广告拦截", "手动切换"}
 | 
			
		||||
	for i := range temp.ProxyGroups {
 | 
			
		||||
		skip := false
 | 
			
		||||
		for _, v := range skipGroups {
 | 
			
		||||
			if strings.Contains(temp.ProxyGroups[i].Name, v) {
 | 
			
		||||
				if v == "手动切换" {
 | 
			
		||||
					proxies := make([]string, 0, len(sub.Proxies))
 | 
			
		||||
					for _, p := range sub.Proxies {
 | 
			
		||||
						proxies = append(proxies, p.Name)
 | 
			
		||||
					}
 | 
			
		||||
					temp.ProxyGroups[i].Proxies = proxies
 | 
			
		||||
				}
 | 
			
		||||
				skip = true
 | 
			
		||||
				continue
 | 
			
		||||
		if temp.ProxyGroups[i].IsCountryGrop {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		newProxies := make([]string, 0, len(temp.ProxyGroups[i].Proxies))
 | 
			
		||||
		for j := range temp.ProxyGroups[i].Proxies {
 | 
			
		||||
			if temp.ProxyGroups[i].Proxies[j] == "<all>" {
 | 
			
		||||
				newProxies = append(newProxies, proxyNames...)
 | 
			
		||||
			} else if temp.ProxyGroups[i].Proxies[j] == "<countries>" {
 | 
			
		||||
				newProxies = append(newProxies, countryGroupNames...)
 | 
			
		||||
			} else {
 | 
			
		||||
				newProxies = append(newProxies, temp.ProxyGroups[i].Proxies[j])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !skip {
 | 
			
		||||
			temp.ProxyGroups[i].Proxies = append(temp.ProxyGroups[i].Proxies, countryGroupNames...)
 | 
			
		||||
		}
 | 
			
		||||
		temp.ProxyGroups[i].Proxies = newProxies
 | 
			
		||||
	}
 | 
			
		||||
	temp.ProxyGroups = append(temp.ProxyGroups, sub.ProxyGroups...)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,10 +3,14 @@ package controller
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"go.uber.org/zap"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sub2clash/config"
 | 
			
		||||
	"sub2clash/logger"
 | 
			
		||||
	"sub2clash/model"
 | 
			
		||||
	"sub2clash/utils"
 | 
			
		||||
	"sub2clash/utils/database"
 | 
			
		||||
@@ -26,11 +30,16 @@ func ShortLinkGenHandler(c *gin.Context) {
 | 
			
		||||
	}
 | 
			
		||||
	// 生成hash
 | 
			
		||||
	hash := utils.RandomString(config.Default.ShortLinkLength)
 | 
			
		||||
	// 存入数据库
 | 
			
		||||
	var item model.ShortLink
 | 
			
		||||
	result := database.FindShortLinkByUrl(params.Url, &item)
 | 
			
		||||
	if result.Error == nil {
 | 
			
		||||
		c.String(200, item.Hash)
 | 
			
		||||
		if item.Password != params.Password {
 | 
			
		||||
			item.Password = params.Password
 | 
			
		||||
			database.SaveShortLink(&item)
 | 
			
		||||
			c.String(200, item.Hash+"?password="+params.Password)
 | 
			
		||||
		} else {
 | 
			
		||||
			c.String(200, item.Hash)
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	} else {
 | 
			
		||||
		if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
 | 
			
		||||
@@ -50,15 +59,20 @@ func ShortLinkGenHandler(c *gin.Context) {
 | 
			
		||||
			Hash:            hash,
 | 
			
		||||
			Url:             params.Url,
 | 
			
		||||
			LastRequestTime: -1,
 | 
			
		||||
			Password:        params.Password,
 | 
			
		||||
		},
 | 
			
		||||
	)
 | 
			
		||||
	// 返回短链接
 | 
			
		||||
	if params.Password != "" {
 | 
			
		||||
		hash += "?password=" + params.Password
 | 
			
		||||
	}
 | 
			
		||||
	c.String(200, hash)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ShortLinkGetHandler(c *gin.Context) {
 | 
			
		||||
	// 获取动态路由
 | 
			
		||||
	hash := c.Param("hash")
 | 
			
		||||
	password := c.Query("password")
 | 
			
		||||
	if strings.TrimSpace(hash) == "" {
 | 
			
		||||
		c.String(400, "参数错误")
 | 
			
		||||
		return
 | 
			
		||||
@@ -68,12 +82,27 @@ func ShortLinkGetHandler(c *gin.Context) {
 | 
			
		||||
	result := database.FindShortLinkByHash(hash, &shortLink)
 | 
			
		||||
	// 重定向
 | 
			
		||||
	if result.Error != nil {
 | 
			
		||||
		c.String(404, "未找到短链接")
 | 
			
		||||
		c.String(404, "未找到短链接或密码错误")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if shortLink.Password != "" && shortLink.Password != password {
 | 
			
		||||
		c.String(404, "未找到短链接或密码错误")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// 更新最后访问时间
 | 
			
		||||
	shortLink.LastRequestTime = time.Now().Unix()
 | 
			
		||||
	database.SaveShortLink(&shortLink)
 | 
			
		||||
	uri := config.Default.BasePath + shortLink.Url
 | 
			
		||||
	c.Redirect(http.StatusTemporaryRedirect, uri)
 | 
			
		||||
	get, err := utils.Get("http://localhost:" + strconv.Itoa(config.Default.Port) + "/" + shortLink.Url)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Logger.Debug("get short link data failed", zap.Error(err))
 | 
			
		||||
		c.String(500, "请求错误: "+err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	all, err := io.ReadAll(get.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Logger.Debug("read short link data failed", zap.Error(err))
 | 
			
		||||
		c.String(500, "读取错误: "+err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	c.String(http.StatusOK, string(all))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -168,6 +168,30 @@
 | 
			
		||||
            <option value="sizedesc">节点数量(降序)</option>
 | 
			
		||||
          </select>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- Remove -->
 | 
			
		||||
        <div class="form-group mb-3">
 | 
			
		||||
          <label for="remove">删除节点:</label>
 | 
			
		||||
          <input
 | 
			
		||||
            class="form-control"
 | 
			
		||||
            type="text"
 | 
			
		||||
            name="remove"
 | 
			
		||||
            id="remove"
 | 
			
		||||
            placeholder="正则表达式"
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- Rename  -->
 | 
			
		||||
        <div class="form-group mb-3" id="replaceGroup">
 | 
			
		||||
          <label>节点名称替换:</label>
 | 
			
		||||
          <button
 | 
			
		||||
            class="btn btn-primary mb-1 btn-xs"
 | 
			
		||||
            onclick="addReplace()"
 | 
			
		||||
            type="button"
 | 
			
		||||
          >
 | 
			
		||||
            +
 | 
			
		||||
          </button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </form>
 | 
			
		||||
 | 
			
		||||
      <!-- Display the API Link -->
 | 
			
		||||
@@ -188,6 +212,12 @@
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="input-group">
 | 
			
		||||
          <input class="form-control" id="apiShortLink" readonly type="text" />
 | 
			
		||||
          <input
 | 
			
		||||
            class="form-control"
 | 
			
		||||
            id="password"
 | 
			
		||||
            type="text"
 | 
			
		||||
            placeholder="密码"
 | 
			
		||||
          />
 | 
			
		||||
          <button
 | 
			
		||||
            class="btn btn-primary"
 | 
			
		||||
            onclick="generateShortLink()"
 | 
			
		||||
@@ -246,6 +276,17 @@
 | 
			
		||||
        return div;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      function createReplace() {
 | 
			
		||||
        const div = document.createElement("div");
 | 
			
		||||
        div.classList.add("input-group", "mb-2");
 | 
			
		||||
        div.innerHTML = `
 | 
			
		||||
            <input type="text" class="form-control" name="replace" placeholder="原字符串(正则表达式)">
 | 
			
		||||
            <input type="text" class="form-control" name="replace" placeholder="替换为(可为空)">
 | 
			
		||||
            <button type="button" class="btn btn-danger" onclick="removeElement(this)">删除</button>
 | 
			
		||||
        `;
 | 
			
		||||
        return div;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      function createRule() {
 | 
			
		||||
        const div = document.createElement("div");
 | 
			
		||||
        div.classList.add("input-group", "mb-2");
 | 
			
		||||
@@ -268,6 +309,11 @@
 | 
			
		||||
        document.getElementById("ruleGroup").appendChild(div);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      function addReplace() {
 | 
			
		||||
        const div = createReplace();
 | 
			
		||||
        document.getElementById("replaceGroup").appendChild(div);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      function removeElement(button) {
 | 
			
		||||
        button.parentElement.remove();
 | 
			
		||||
      }
 | 
			
		||||
@@ -382,6 +428,29 @@
 | 
			
		||||
        // 获取排序策略
 | 
			
		||||
        const sort = document.getElementById("sort").value;
 | 
			
		||||
        queryParams.push(`sort=${sort}`);
 | 
			
		||||
 | 
			
		||||
        // 获取删除节点的正则表达式
 | 
			
		||||
        const remove = document.getElementById("remove").value;
 | 
			
		||||
        if (remove.trim() !== "") {
 | 
			
		||||
          queryParams.push(`remove=${encodeURIComponent(remove)}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 获取替换节点名称的正则表达式
 | 
			
		||||
        let replaceList = [];
 | 
			
		||||
        const replaces = document.getElementsByName("replace");
 | 
			
		||||
        for (let i = 0; i < replaces.length / 2; i++) {
 | 
			
		||||
          let replaceStr = `<${replaces[i * 2].value}>`;
 | 
			
		||||
          let replaceTo = `<${replaces[i * 2 + 1].value}>`;
 | 
			
		||||
          if (replaceStr.trim() === "") {
 | 
			
		||||
            alert("重命名设置中存在空值,请检查后重试!");
 | 
			
		||||
            return "";
 | 
			
		||||
          }
 | 
			
		||||
          replaceList.push(`[${replaceStr},${replaceTo}]`);
 | 
			
		||||
        }
 | 
			
		||||
        queryParams.push(
 | 
			
		||||
          `replace=${encodeURIComponent(replaceList.join(","))}`,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        return `${endpoint}?${queryParams.join("&")}`;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@@ -396,6 +465,7 @@
 | 
			
		||||
 | 
			
		||||
      function generateShortLink() {
 | 
			
		||||
        const apiShortLink = document.getElementById("apiShortLink");
 | 
			
		||||
        const password = document.getElementById("password");
 | 
			
		||||
        let uri = generateURI();
 | 
			
		||||
        if (uri === "") {
 | 
			
		||||
          return;
 | 
			
		||||
@@ -405,6 +475,7 @@
 | 
			
		||||
            "./short",
 | 
			
		||||
            {
 | 
			
		||||
              url: uri,
 | 
			
		||||
              password: password.value.trim(),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              headers: {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,5 +3,6 @@ package model
 | 
			
		||||
type ShortLink struct {
 | 
			
		||||
	Hash            string `gorm:"primary_key"`
 | 
			
		||||
	Url             string
 | 
			
		||||
	Password        string
 | 
			
		||||
	LastRequestTime int64
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,88 +8,104 @@ proxy-groups:
 | 
			
		||||
  - name: 节点选择
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 手动切换
 | 
			
		||||
      - DIRECT
 | 
			
		||||
  - name: 手动切换
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - <all>
 | 
			
		||||
  - name: 电报消息
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - 手动切换
 | 
			
		||||
      - DIRECT
 | 
			
		||||
  - name: OpenAi
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - 手动切换
 | 
			
		||||
      - DIRECT
 | 
			
		||||
  - name: 油管视频
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - 手动切换
 | 
			
		||||
      - DIRECT
 | 
			
		||||
  - name: 巴哈姆特
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - 手动切换
 | 
			
		||||
      - DIRECT
 | 
			
		||||
  - name: 哔哩哔哩
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 全球直连
 | 
			
		||||
  - name: 国外媒体
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - 手动切换
 | 
			
		||||
      - DIRECT
 | 
			
		||||
  - name: 国内媒体
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - DIRECT
 | 
			
		||||
      - 手动切换
 | 
			
		||||
  - name: 谷歌FCM
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - DIRECT
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - 手动切换
 | 
			
		||||
  - name: 微软云盘
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - DIRECT
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - 手动切换
 | 
			
		||||
  - name: 微软服务
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - DIRECT
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - 手动切换
 | 
			
		||||
  - name: 苹果服务
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - DIRECT
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - 手动切换
 | 
			
		||||
  - name: 游戏平台
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - DIRECT
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - 手动切换
 | 
			
		||||
  - name: 网易音乐
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - DIRECT
 | 
			
		||||
      - 节点选择
 | 
			
		||||
  - name: 全球直连
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - DIRECT
 | 
			
		||||
      - 节点选择
 | 
			
		||||
  - name: 广告拦截
 | 
			
		||||
@@ -105,6 +121,7 @@ proxy-groups:
 | 
			
		||||
  - name: 漏网之鱼
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - DIRECT
 | 
			
		||||
      - 手动切换
 | 
			
		||||
 
 | 
			
		||||
@@ -8,40 +8,90 @@ proxy-groups:
 | 
			
		||||
  - name: 节点选择
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 手动切换
 | 
			
		||||
      - DIRECT
 | 
			
		||||
  - name: 手动切换
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
  - name: 微软服务
 | 
			
		||||
      - <all>
 | 
			
		||||
  - name: 游戏平台(中国)
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 手动切换
 | 
			
		||||
      - DIRECT
 | 
			
		||||
  - name: 游戏平台
 | 
			
		||||
  - name: 游戏平台(全球)
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 手动切换
 | 
			
		||||
      - DIRECT
 | 
			
		||||
  - name: 巴哈姆特
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 手动切换
 | 
			
		||||
      - DIRECT
 | 
			
		||||
  - name: 哔哩哔哩
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 手动切换
 | 
			
		||||
      - DIRECT
 | 
			
		||||
  - name: 全球直连
 | 
			
		||||
  - name: Telegram
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - DIRECT
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 手动切换
 | 
			
		||||
      - DIRECT
 | 
			
		||||
  - name: OpenAI
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 手动切换
 | 
			
		||||
      - DIRECT
 | 
			
		||||
  - name: Youtube
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 手动切换
 | 
			
		||||
      - DIRECT
 | 
			
		||||
  - name: Microsoft
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 手动切换
 | 
			
		||||
      - DIRECT
 | 
			
		||||
  - name: Onedrive
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 手动切换
 | 
			
		||||
      - DIRECT
 | 
			
		||||
  - name: Apple
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 手动切换
 | 
			
		||||
      - DIRECT
 | 
			
		||||
  - name: Netflix
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 手动切换
 | 
			
		||||
      - DIRECT
 | 
			
		||||
  - name: 广告拦截
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
@@ -51,17 +101,25 @@ proxy-groups:
 | 
			
		||||
    type: select
 | 
			
		||||
    proxies:
 | 
			
		||||
      - 节点选择
 | 
			
		||||
      - <countries>
 | 
			
		||||
      - 手动切换
 | 
			
		||||
      - DIRECT
 | 
			
		||||
rules:
 | 
			
		||||
  - GEOSITE,private,全球直连,no-resolve
 | 
			
		||||
  - GEOIP,private,全球直连
 | 
			
		||||
  - GEOSITE,private,DIRECT,no-resolve
 | 
			
		||||
  - GEOIP,private,DIRECT
 | 
			
		||||
  - GEOSITE,category-ads-all,广告拦截
 | 
			
		||||
  - GEOSITE,microsoft,微软服务
 | 
			
		||||
  - GEOSITE,microsoft,Microsoft
 | 
			
		||||
  - GEOSITE,apple,Apple
 | 
			
		||||
  - GEOSITE,netflix,Netflix
 | 
			
		||||
  - GEOSITE,onedrive,Onedrive
 | 
			
		||||
  - GEOSITE,youtube,Youtube
 | 
			
		||||
  - GEOSITE,telegram,Telegram
 | 
			
		||||
  - GEOSITE,openai,OpenAI
 | 
			
		||||
  - GEOSITE,bilibili,哔哩哔哩
 | 
			
		||||
  - GEOSITE,bahamut,巴哈姆特
 | 
			
		||||
  - GEOSITE,category-games,游戏平台
 | 
			
		||||
  - GEOSITE,category-games@cn,游戏平台(中国)
 | 
			
		||||
  - GEOSITE,category-games,游戏平台(全球)
 | 
			
		||||
  - GEOSITE,geolocation-!cn,节点选择
 | 
			
		||||
  - GEOSITE,CN,全球直连
 | 
			
		||||
  - GEOIP,CN,全球直连
 | 
			
		||||
  - GEOSITE,CN,DIRECT
 | 
			
		||||
  - GEOIP,CN,DIRECT
 | 
			
		||||
  - MATCH,漏网之鱼
 | 
			
		||||
							
								
								
									
										10
									
								
								utils/get.go
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								utils/get.go
									
									
									
									
									
								
							@@ -12,7 +12,15 @@ func Get(url string) (resp *http.Response, err error) {
 | 
			
		||||
	haveTried := 0
 | 
			
		||||
	retryDelay := time.Second // 延迟1秒再重试
 | 
			
		||||
	for haveTried < retryTimes {
 | 
			
		||||
		get, err := http.Get(url)
 | 
			
		||||
		client := &http.Client{}
 | 
			
		||||
		//client.Timeout = time.Second * 10
 | 
			
		||||
		req, err := http.NewRequest("GET", url, nil)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			haveTried++
 | 
			
		||||
			time.Sleep(retryDelay)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		get, err := client.Do(req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			haveTried++
 | 
			
		||||
			time.Sleep(retryDelay)
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,6 @@ func AddProxy(
 | 
			
		||||
	sub *model.Subscription, autotest bool,
 | 
			
		||||
	lazy bool, clashType model.ClashType, proxies ...model.Proxy,
 | 
			
		||||
) {
 | 
			
		||||
	newCountryGroupNames := make([]string, 0)
 | 
			
		||||
	proxyTypes := model.GetSupportProxyTypes(clashType)
 | 
			
		||||
	// 添加节点
 | 
			
		||||
	for _, proxy := range proxies {
 | 
			
		||||
@@ -79,7 +78,6 @@ func AddProxy(
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			sub.ProxyGroups = append(sub.ProxyGroups, newGroup)
 | 
			
		||||
			newCountryGroupNames = append(newCountryGroupNames, countryName)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								utils/sub.go
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								utils/sub.go
									
									
									
									
									
								
							@@ -35,7 +35,9 @@ func LoadSubscription(url string, refresh bool) ([]byte, error) {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		defer func(file *os.File) {
 | 
			
		||||
			_ = file.Close()
 | 
			
		||||
			if file != nil {
 | 
			
		||||
				_ = file.Close()
 | 
			
		||||
			}
 | 
			
		||||
		}(file)
 | 
			
		||||
		fileLock.RLock()
 | 
			
		||||
		defer fileLock.RUnlock()
 | 
			
		||||
@@ -56,7 +58,9 @@ func FetchSubscriptionFromAPI(url string) ([]byte, error) {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer func(Body io.ReadCloser) {
 | 
			
		||||
		_ = Body.Close()
 | 
			
		||||
		if Body != nil {
 | 
			
		||||
			_ = Body.Close()
 | 
			
		||||
		}
 | 
			
		||||
	}(resp.Body)
 | 
			
		||||
	data, err := io.ReadAll(resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -67,7 +71,9 @@ func FetchSubscriptionFromAPI(url string) ([]byte, error) {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer func(file *os.File) {
 | 
			
		||||
		_ = file.Close()
 | 
			
		||||
		if file != nil {
 | 
			
		||||
			_ = file.Close()
 | 
			
		||||
		}
 | 
			
		||||
	}(file)
 | 
			
		||||
	fileLock.Lock()
 | 
			
		||||
	defer fileLock.Unlock()
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,9 @@ func LoadTemplate(template string) ([]byte, error) {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		defer func(file *os.File) {
 | 
			
		||||
			_ = file.Close()
 | 
			
		||||
			if file != nil {
 | 
			
		||||
				_ = file.Close()
 | 
			
		||||
			}
 | 
			
		||||
		}(file)
 | 
			
		||||
		result, err := io.ReadAll(file)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,9 @@ func writeTemplate(path string, template string) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		defer func(file *os.File) {
 | 
			
		||||
			_ = file.Close()
 | 
			
		||||
			if file != nil {
 | 
			
		||||
				_ = file.Close()
 | 
			
		||||
			}
 | 
			
		||||
		}(file)
 | 
			
		||||
		_, err = file.WriteString(template)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,11 @@
 | 
			
		||||
package validator
 | 
			
		||||
 | 
			
		||||
type ShortLinkGenValidator struct {
 | 
			
		||||
	Url string `form:"url" binding:"required"`
 | 
			
		||||
	Url      string `form:"url" binding:"required"`
 | 
			
		||||
	Password string `form:"password"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ShortLinkGetValidator struct {
 | 
			
		||||
	Hash string `form:"hash" binding:"required"`
 | 
			
		||||
	Hash     string `form:"hash" binding:"required"`
 | 
			
		||||
	Password string `form:"password"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,10 @@ type SubValidator struct {
 | 
			
		||||
	AutoTest      bool                 `form:"autoTest,default=false" binding:""`
 | 
			
		||||
	Lazy          bool                 `form:"lazy,default=false" binding:""`
 | 
			
		||||
	Sort          string               `form:"sort" binding:""`
 | 
			
		||||
	Remove        string               `form:"remove" binding:""`
 | 
			
		||||
	Replace       string               `form:"replace" binding:""`
 | 
			
		||||
	ReplaceKeys   []string             `form:"-" binding:""`
 | 
			
		||||
	ReplaceTo     []string             `form:"-" binding:""`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RuleProviderStruct struct {
 | 
			
		||||
@@ -135,5 +139,17 @@ func ParseQuery(c *gin.Context) (SubValidator, error) {
 | 
			
		||||
	} else {
 | 
			
		||||
		query.Rules = nil
 | 
			
		||||
	}
 | 
			
		||||
	if strings.TrimSpace(query.Replace) != "" {
 | 
			
		||||
		reg := regexp.MustCompile(`\[<(.*?)>,<(.*?)>\]`)
 | 
			
		||||
		replaces := reg.FindAllStringSubmatch(query.Replace, -1)
 | 
			
		||||
		for i := range replaces {
 | 
			
		||||
			length := len(replaces[i])
 | 
			
		||||
			if length != 3 {
 | 
			
		||||
				return SubValidator{}, errors.New("参数错误: replace 格式错误")
 | 
			
		||||
			}
 | 
			
		||||
			query.ReplaceKeys = append(query.ReplaceKeys, replaces[i][1])
 | 
			
		||||
			query.ReplaceTo = append(query.ReplaceTo, replaces[i][2])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return query, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user