mirror of
https://github.com/nitezs/sub2clash.git
synced 2024-12-23 21:34:41 -05:00
v0.0.5
feat: 增加节点去重 feat: 增加节点重命名 feat: 增加节点过滤 feat: 增加短链密码 modify: 修改模板解析逻辑,现在需要添加 <all>,<countries> 来让程序解析模板 modify: 修改短链请求逻辑,不再跳转链接,而是服务器内部请求 modify: 完善 Meta 默认模板 如果你从旧版升级,请务必修改或删除程序目录下的模板
This commit is contained in:
commit
34b85c8d63
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
|
### 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](./templates/template_clash.yaml)
|
||||||
- [Clash.Meta](./templates/template_meta.yaml)
|
- [Clash.Meta](./templates/template_meta.yaml)
|
||||||
@ -63,5 +57,3 @@
|
|||||||
## 已知问题
|
## 已知问题
|
||||||
|
|
||||||
[代理链接解析](./parser)还没有经过严格测试,可能会出现解析错误的情况,如果出现问题请提交 issue
|
[代理链接解析](./parser)还没有经过严格测试,可能会出现解析错误的情况,如果出现问题请提交 issue
|
||||||
|
|
||||||
## TODO
|
|
||||||
|
@ -4,11 +4,14 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
|
"go.uber.org/zap"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sub2clash/logger"
|
||||||
"sub2clash/model"
|
"sub2clash/model"
|
||||||
"sub2clash/parser"
|
"sub2clash/parser"
|
||||||
"sub2clash/utils"
|
"sub2clash/utils"
|
||||||
@ -31,17 +34,24 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
templateBytes, err = utils.LoadTemplate(template)
|
templateBytes, err = utils.LoadTemplate(template)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logger.Logger.Debug(
|
||||||
|
"load template failed", zap.String("template", template), zap.Error(err),
|
||||||
|
)
|
||||||
return nil, errors.New("加载模板失败: " + err.Error())
|
return nil, errors.New("加载模板失败: " + err.Error())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
templateBytes, err = utils.LoadSubscription(template, query.Refresh)
|
templateBytes, err = utils.LoadSubscription(template, query.Refresh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logger.Logger.Debug(
|
||||||
|
"load template failed", zap.String("template", template), zap.Error(err),
|
||||||
|
)
|
||||||
return nil, errors.New("加载模板失败: " + err.Error())
|
return nil, errors.New("加载模板失败: " + err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 解析模板
|
// 解析模板
|
||||||
err = yaml.Unmarshal(templateBytes, &temp)
|
err = yaml.Unmarshal(templateBytes, &temp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logger.Logger.Debug("parse template failed", zap.Error(err))
|
||||||
return nil, errors.New("解析模板失败: " + err.Error())
|
return nil, errors.New("解析模板失败: " + err.Error())
|
||||||
}
|
}
|
||||||
var proxyList []model.Proxy
|
var proxyList []model.Proxy
|
||||||
@ -49,13 +59,15 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
|
|||||||
for i := range query.Subs {
|
for i := range query.Subs {
|
||||||
data, err := utils.LoadSubscription(query.Subs[i], query.Refresh)
|
data, err := utils.LoadSubscription(query.Subs[i], query.Refresh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logger.Logger.Debug(
|
||||||
|
"load subscription failed", zap.String("url", query.Subs[i]), zap.Error(err),
|
||||||
|
)
|
||||||
return nil, errors.New("加载订阅失败: " + err.Error())
|
return nil, errors.New("加载订阅失败: " + err.Error())
|
||||||
}
|
}
|
||||||
// 解析订阅
|
// 解析订阅
|
||||||
|
|
||||||
err = yaml.Unmarshal(data, &sub)
|
err = yaml.Unmarshal(data, &sub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
reg, _ := regexp.Compile("(ssr|ss|vmess|trojan|http|https)://")
|
reg, _ := regexp.Compile("(ssr|ss|vmess|trojan|vless)://")
|
||||||
if reg.Match(data) {
|
if reg.Match(data) {
|
||||||
p := utils.ParseProxy(strings.Split(string(data), "\n")...)
|
p := utils.ParseProxy(strings.Split(string(data), "\n")...)
|
||||||
proxyList = append(proxyList, p...)
|
proxyList = append(proxyList, p...)
|
||||||
@ -63,6 +75,11 @@ func BuildSub(clashType model.ClashType, query validator.SubValidator, template
|
|||||||
// 如果无法直接解析,尝试Base64解码
|
// 如果无法直接解析,尝试Base64解码
|
||||||
base64, err := parser.DecodeBase64(string(data))
|
base64, err := parser.DecodeBase64(string(data))
|
||||||
if err != nil {
|
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())
|
return nil, errors.New("加载订阅失败: " + err.Error())
|
||||||
}
|
}
|
||||||
p := utils.ParseProxy(strings.Split(base64, "\n")...)
|
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...)
|
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 中,防止策略组排序错乱
|
// 将新增节点都添加到临时变量 t 中,防止策略组排序错乱
|
||||||
var t = &model.Subscription{}
|
var t = &model.Subscription{}
|
||||||
utils.AddProxy(t, query.AutoTest, query.Lazy, clashType, proxyList...)
|
utils.AddProxy(t, query.AutoTest, query.Lazy, clashType, proxyList...)
|
||||||
// 处理自定义代理
|
|
||||||
utils.AddProxy(
|
|
||||||
t, query.AutoTest, query.Lazy, clashType,
|
|
||||||
utils.ParseProxy(query.Proxies...)...,
|
|
||||||
)
|
|
||||||
// 排序策略组
|
// 排序策略组
|
||||||
switch query.Sort {
|
switch query.Sort {
|
||||||
case "sizeasc":
|
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...)
|
temp.Proxies = append(temp.Proxies, sub.Proxies...)
|
||||||
// 将订阅中的策略组添加到模板中
|
// 将订阅中的策略组添加到模板中
|
||||||
skipGroups := []string{"全球直连", "广告拦截", "手动切换"}
|
|
||||||
for i := range temp.ProxyGroups {
|
for i := range temp.ProxyGroups {
|
||||||
skip := false
|
if temp.ProxyGroups[i].IsCountryGrop {
|
||||||
for _, v := range skipGroups {
|
continue
|
||||||
if strings.Contains(temp.ProxyGroups[i].Name, v) {
|
}
|
||||||
if v == "手动切换" {
|
newProxies := make([]string, 0, len(temp.ProxyGroups[i].Proxies))
|
||||||
proxies := make([]string, 0, len(sub.Proxies))
|
for j := range temp.ProxyGroups[i].Proxies {
|
||||||
for _, p := range sub.Proxies {
|
if temp.ProxyGroups[i].Proxies[j] == "<all>" {
|
||||||
proxies = append(proxies, p.Name)
|
newProxies = append(newProxies, proxyNames...)
|
||||||
}
|
} else if temp.ProxyGroups[i].Proxies[j] == "<countries>" {
|
||||||
temp.ProxyGroups[i].Proxies = proxies
|
newProxies = append(newProxies, countryGroupNames...)
|
||||||
}
|
} else {
|
||||||
skip = true
|
newProxies = append(newProxies, temp.ProxyGroups[i].Proxies[j])
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !skip {
|
temp.ProxyGroups[i].Proxies = newProxies
|
||||||
temp.ProxyGroups[i].Proxies = append(temp.ProxyGroups[i].Proxies, countryGroupNames...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
temp.ProxyGroups = append(temp.ProxyGroups, sub.ProxyGroups...)
|
temp.ProxyGroups = append(temp.ProxyGroups, sub.ProxyGroups...)
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,14 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sub2clash/config"
|
"sub2clash/config"
|
||||||
|
"sub2clash/logger"
|
||||||
"sub2clash/model"
|
"sub2clash/model"
|
||||||
"sub2clash/utils"
|
"sub2clash/utils"
|
||||||
"sub2clash/utils/database"
|
"sub2clash/utils/database"
|
||||||
@ -26,11 +30,16 @@ func ShortLinkGenHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
// 生成hash
|
// 生成hash
|
||||||
hash := utils.RandomString(config.Default.ShortLinkLength)
|
hash := utils.RandomString(config.Default.ShortLinkLength)
|
||||||
// 存入数据库
|
|
||||||
var item model.ShortLink
|
var item model.ShortLink
|
||||||
result := database.FindShortLinkByUrl(params.Url, &item)
|
result := database.FindShortLinkByUrl(params.Url, &item)
|
||||||
if result.Error == nil {
|
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
|
return
|
||||||
} else {
|
} else {
|
||||||
if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||||
@ -50,15 +59,20 @@ func ShortLinkGenHandler(c *gin.Context) {
|
|||||||
Hash: hash,
|
Hash: hash,
|
||||||
Url: params.Url,
|
Url: params.Url,
|
||||||
LastRequestTime: -1,
|
LastRequestTime: -1,
|
||||||
|
Password: params.Password,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
// 返回短链接
|
// 返回短链接
|
||||||
|
if params.Password != "" {
|
||||||
|
hash += "?password=" + params.Password
|
||||||
|
}
|
||||||
c.String(200, hash)
|
c.String(200, hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ShortLinkGetHandler(c *gin.Context) {
|
func ShortLinkGetHandler(c *gin.Context) {
|
||||||
// 获取动态路由
|
// 获取动态路由
|
||||||
hash := c.Param("hash")
|
hash := c.Param("hash")
|
||||||
|
password := c.Query("password")
|
||||||
if strings.TrimSpace(hash) == "" {
|
if strings.TrimSpace(hash) == "" {
|
||||||
c.String(400, "参数错误")
|
c.String(400, "参数错误")
|
||||||
return
|
return
|
||||||
@ -68,12 +82,27 @@ func ShortLinkGetHandler(c *gin.Context) {
|
|||||||
result := database.FindShortLinkByHash(hash, &shortLink)
|
result := database.FindShortLinkByHash(hash, &shortLink)
|
||||||
// 重定向
|
// 重定向
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
c.String(404, "未找到短链接")
|
c.String(404, "未找到短链接或密码错误")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if shortLink.Password != "" && shortLink.Password != password {
|
||||||
|
c.String(404, "未找到短链接或密码错误")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 更新最后访问时间
|
// 更新最后访问时间
|
||||||
shortLink.LastRequestTime = time.Now().Unix()
|
shortLink.LastRequestTime = time.Now().Unix()
|
||||||
database.SaveShortLink(&shortLink)
|
database.SaveShortLink(&shortLink)
|
||||||
uri := config.Default.BasePath + shortLink.Url
|
get, err := utils.Get("http://localhost:" + strconv.Itoa(config.Default.Port) + "/" + shortLink.Url)
|
||||||
c.Redirect(http.StatusTemporaryRedirect, uri)
|
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>
|
<option value="sizedesc">节点数量(降序)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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>
|
</form>
|
||||||
|
|
||||||
<!-- Display the API Link -->
|
<!-- Display the API Link -->
|
||||||
@ -188,6 +212,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input class="form-control" id="apiShortLink" readonly type="text" />
|
<input class="form-control" id="apiShortLink" readonly type="text" />
|
||||||
|
<input
|
||||||
|
class="form-control"
|
||||||
|
id="password"
|
||||||
|
type="text"
|
||||||
|
placeholder="密码"
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
onclick="generateShortLink()"
|
onclick="generateShortLink()"
|
||||||
@ -246,6 +276,17 @@
|
|||||||
return div;
|
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() {
|
function createRule() {
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.classList.add("input-group", "mb-2");
|
div.classList.add("input-group", "mb-2");
|
||||||
@ -268,6 +309,11 @@
|
|||||||
document.getElementById("ruleGroup").appendChild(div);
|
document.getElementById("ruleGroup").appendChild(div);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addReplace() {
|
||||||
|
const div = createReplace();
|
||||||
|
document.getElementById("replaceGroup").appendChild(div);
|
||||||
|
}
|
||||||
|
|
||||||
function removeElement(button) {
|
function removeElement(button) {
|
||||||
button.parentElement.remove();
|
button.parentElement.remove();
|
||||||
}
|
}
|
||||||
@ -382,6 +428,29 @@
|
|||||||
// 获取排序策略
|
// 获取排序策略
|
||||||
const sort = document.getElementById("sort").value;
|
const sort = document.getElementById("sort").value;
|
||||||
queryParams.push(`sort=${sort}`);
|
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("&")}`;
|
return `${endpoint}?${queryParams.join("&")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,6 +465,7 @@
|
|||||||
|
|
||||||
function generateShortLink() {
|
function generateShortLink() {
|
||||||
const apiShortLink = document.getElementById("apiShortLink");
|
const apiShortLink = document.getElementById("apiShortLink");
|
||||||
|
const password = document.getElementById("password");
|
||||||
let uri = generateURI();
|
let uri = generateURI();
|
||||||
if (uri === "") {
|
if (uri === "") {
|
||||||
return;
|
return;
|
||||||
@ -405,6 +475,7 @@
|
|||||||
"./short",
|
"./short",
|
||||||
{
|
{
|
||||||
url: uri,
|
url: uri,
|
||||||
|
password: password.value.trim(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -3,5 +3,6 @@ package model
|
|||||||
type ShortLink struct {
|
type ShortLink struct {
|
||||||
Hash string `gorm:"primary_key"`
|
Hash string `gorm:"primary_key"`
|
||||||
Url string
|
Url string
|
||||||
|
Password string
|
||||||
LastRequestTime int64
|
LastRequestTime int64
|
||||||
}
|
}
|
||||||
|
@ -8,88 +8,104 @@ proxy-groups:
|
|||||||
- name: 节点选择
|
- name: 节点选择
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
|
- <countries>
|
||||||
- 手动切换
|
- 手动切换
|
||||||
- DIRECT
|
- DIRECT
|
||||||
- name: 手动切换
|
- name: 手动切换
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
|
- <all>
|
||||||
- name: 电报消息
|
- name: 电报消息
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
|
- <countries>
|
||||||
- 节点选择
|
- 节点选择
|
||||||
- 手动切换
|
- 手动切换
|
||||||
- DIRECT
|
- DIRECT
|
||||||
- name: OpenAi
|
- name: OpenAi
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
|
- <countries>
|
||||||
- 节点选择
|
- 节点选择
|
||||||
- 手动切换
|
- 手动切换
|
||||||
- DIRECT
|
- DIRECT
|
||||||
- name: 油管视频
|
- name: 油管视频
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
|
- <countries>
|
||||||
- 节点选择
|
- 节点选择
|
||||||
- 手动切换
|
- 手动切换
|
||||||
- DIRECT
|
- DIRECT
|
||||||
- name: 巴哈姆特
|
- name: 巴哈姆特
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
|
- <countries>
|
||||||
- 节点选择
|
- 节点选择
|
||||||
- 手动切换
|
- 手动切换
|
||||||
- DIRECT
|
- DIRECT
|
||||||
- name: 哔哩哔哩
|
- name: 哔哩哔哩
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
|
- <countries>
|
||||||
- 全球直连
|
- 全球直连
|
||||||
- name: 国外媒体
|
- name: 国外媒体
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
|
- <countries>
|
||||||
- 节点选择
|
- 节点选择
|
||||||
- 手动切换
|
- 手动切换
|
||||||
- DIRECT
|
- DIRECT
|
||||||
- name: 国内媒体
|
- name: 国内媒体
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
|
- <countries>
|
||||||
- DIRECT
|
- DIRECT
|
||||||
- 手动切换
|
- 手动切换
|
||||||
- name: 谷歌FCM
|
- name: 谷歌FCM
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
|
- <countries>
|
||||||
- DIRECT
|
- DIRECT
|
||||||
- 节点选择
|
- 节点选择
|
||||||
- 手动切换
|
- 手动切换
|
||||||
- name: 微软云盘
|
- name: 微软云盘
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
|
- <countries>
|
||||||
- DIRECT
|
- DIRECT
|
||||||
- 节点选择
|
- 节点选择
|
||||||
- 手动切换
|
- 手动切换
|
||||||
- name: 微软服务
|
- name: 微软服务
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
|
- <countries>
|
||||||
- DIRECT
|
- DIRECT
|
||||||
- 节点选择
|
- 节点选择
|
||||||
- 手动切换
|
- 手动切换
|
||||||
- name: 苹果服务
|
- name: 苹果服务
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
|
- <countries>
|
||||||
- DIRECT
|
- DIRECT
|
||||||
- 节点选择
|
- 节点选择
|
||||||
- 手动切换
|
- 手动切换
|
||||||
- name: 游戏平台
|
- name: 游戏平台
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
|
- <countries>
|
||||||
- DIRECT
|
- DIRECT
|
||||||
- 节点选择
|
- 节点选择
|
||||||
- 手动切换
|
- 手动切换
|
||||||
- name: 网易音乐
|
- name: 网易音乐
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
|
- <countries>
|
||||||
- DIRECT
|
- DIRECT
|
||||||
- 节点选择
|
- 节点选择
|
||||||
- name: 全球直连
|
- name: 全球直连
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
|
- <countries>
|
||||||
- DIRECT
|
- DIRECT
|
||||||
- 节点选择
|
- 节点选择
|
||||||
- name: 广告拦截
|
- name: 广告拦截
|
||||||
@ -105,6 +121,7 @@ proxy-groups:
|
|||||||
- name: 漏网之鱼
|
- name: 漏网之鱼
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
|
- <countries>
|
||||||
- 节点选择
|
- 节点选择
|
||||||
- DIRECT
|
- DIRECT
|
||||||
- 手动切换
|
- 手动切换
|
||||||
@ -9579,4 +9596,4 @@ rules:
|
|||||||
- PROCESS-NAME,Weiyun.exe,全球直连
|
- PROCESS-NAME,Weiyun.exe,全球直连
|
||||||
- PROCESS-NAME,baidunetdisk.exe,全球直连
|
- PROCESS-NAME,baidunetdisk.exe,全球直连
|
||||||
- GEOIP,CN,全球直连
|
- GEOIP,CN,全球直连
|
||||||
- MATCH,漏网之鱼
|
- MATCH,漏网之鱼
|
||||||
|
@ -8,40 +8,90 @@ proxy-groups:
|
|||||||
- name: 节点选择
|
- name: 节点选择
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
|
- <countries>
|
||||||
- 手动切换
|
- 手动切换
|
||||||
- DIRECT
|
- DIRECT
|
||||||
- name: 手动切换
|
- name: 手动切换
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
- name: 微软服务
|
- <all>
|
||||||
|
- name: 游戏平台(中国)
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
- 节点选择
|
- 节点选择
|
||||||
|
- <countries>
|
||||||
- 手动切换
|
- 手动切换
|
||||||
- DIRECT
|
- DIRECT
|
||||||
- name: 游戏平台
|
- name: 游戏平台(全球)
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
- 节点选择
|
- 节点选择
|
||||||
|
- <countries>
|
||||||
- 手动切换
|
- 手动切换
|
||||||
- DIRECT
|
- DIRECT
|
||||||
- name: 巴哈姆特
|
- name: 巴哈姆特
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
- 节点选择
|
- 节点选择
|
||||||
|
- <countries>
|
||||||
- 手动切换
|
- 手动切换
|
||||||
- DIRECT
|
- DIRECT
|
||||||
- name: 哔哩哔哩
|
- name: 哔哩哔哩
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
- 节点选择
|
- 节点选择
|
||||||
|
- <countries>
|
||||||
- 手动切换
|
- 手动切换
|
||||||
- DIRECT
|
- DIRECT
|
||||||
- name: 全球直连
|
- name: Telegram
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
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: 广告拦截
|
- name: 广告拦截
|
||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
@ -51,17 +101,25 @@ proxy-groups:
|
|||||||
type: select
|
type: select
|
||||||
proxies:
|
proxies:
|
||||||
- 节点选择
|
- 节点选择
|
||||||
|
- <countries>
|
||||||
- 手动切换
|
- 手动切换
|
||||||
- DIRECT
|
- DIRECT
|
||||||
rules:
|
rules:
|
||||||
- GEOSITE,private,全球直连,no-resolve
|
- GEOSITE,private,DIRECT,no-resolve
|
||||||
- GEOIP,private,全球直连
|
- GEOIP,private,DIRECT
|
||||||
- GEOSITE,category-ads-all,广告拦截
|
- 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,bilibili,哔哩哔哩
|
||||||
- GEOSITE,bahamut,巴哈姆特
|
- GEOSITE,bahamut,巴哈姆特
|
||||||
- GEOSITE,category-games,游戏平台
|
- GEOSITE,category-games@cn,游戏平台(中国)
|
||||||
|
- GEOSITE,category-games,游戏平台(全球)
|
||||||
- GEOSITE,geolocation-!cn,节点选择
|
- GEOSITE,geolocation-!cn,节点选择
|
||||||
- GEOSITE,CN,全球直连
|
- GEOSITE,CN,DIRECT
|
||||||
- GEOIP,CN,全球直连
|
- GEOIP,CN,DIRECT
|
||||||
- MATCH,漏网之鱼
|
- MATCH,漏网之鱼
|
||||||
|
10
utils/get.go
10
utils/get.go
@ -12,7 +12,15 @@ func Get(url string) (resp *http.Response, err error) {
|
|||||||
haveTried := 0
|
haveTried := 0
|
||||||
retryDelay := time.Second // 延迟1秒再重试
|
retryDelay := time.Second // 延迟1秒再重试
|
||||||
for haveTried < retryTimes {
|
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 {
|
if err != nil {
|
||||||
haveTried++
|
haveTried++
|
||||||
time.Sleep(retryDelay)
|
time.Sleep(retryDelay)
|
||||||
|
@ -31,7 +31,6 @@ func AddProxy(
|
|||||||
sub *model.Subscription, autotest bool,
|
sub *model.Subscription, autotest bool,
|
||||||
lazy bool, clashType model.ClashType, proxies ...model.Proxy,
|
lazy bool, clashType model.ClashType, proxies ...model.Proxy,
|
||||||
) {
|
) {
|
||||||
newCountryGroupNames := make([]string, 0)
|
|
||||||
proxyTypes := model.GetSupportProxyTypes(clashType)
|
proxyTypes := model.GetSupportProxyTypes(clashType)
|
||||||
// 添加节点
|
// 添加节点
|
||||||
for _, proxy := range proxies {
|
for _, proxy := range proxies {
|
||||||
@ -79,7 +78,6 @@ func AddProxy(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
sub.ProxyGroups = append(sub.ProxyGroups, newGroup)
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
defer func(file *os.File) {
|
defer func(file *os.File) {
|
||||||
_ = file.Close()
|
if file != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
}
|
||||||
}(file)
|
}(file)
|
||||||
fileLock.RLock()
|
fileLock.RLock()
|
||||||
defer fileLock.RUnlock()
|
defer fileLock.RUnlock()
|
||||||
@ -56,7 +58,9 @@ func FetchSubscriptionFromAPI(url string) ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer func(Body io.ReadCloser) {
|
defer func(Body io.ReadCloser) {
|
||||||
_ = Body.Close()
|
if Body != nil {
|
||||||
|
_ = Body.Close()
|
||||||
|
}
|
||||||
}(resp.Body)
|
}(resp.Body)
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -67,7 +71,9 @@ func FetchSubscriptionFromAPI(url string) ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer func(file *os.File) {
|
defer func(file *os.File) {
|
||||||
_ = file.Close()
|
if file != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
}
|
||||||
}(file)
|
}(file)
|
||||||
fileLock.Lock()
|
fileLock.Lock()
|
||||||
defer fileLock.Unlock()
|
defer fileLock.Unlock()
|
||||||
|
@ -17,7 +17,9 @@ func LoadTemplate(template string) ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer func(file *os.File) {
|
defer func(file *os.File) {
|
||||||
_ = file.Close()
|
if file != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
}
|
||||||
}(file)
|
}(file)
|
||||||
result, err := io.ReadAll(file)
|
result, err := io.ReadAll(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -16,7 +16,9 @@ func writeTemplate(path string, template string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer func(file *os.File) {
|
defer func(file *os.File) {
|
||||||
_ = file.Close()
|
if file != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
}
|
||||||
}(file)
|
}(file)
|
||||||
_, err = file.WriteString(template)
|
_, err = file.WriteString(template)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package validator
|
package validator
|
||||||
|
|
||||||
type ShortLinkGenValidator struct {
|
type ShortLinkGenValidator struct {
|
||||||
Url string `form:"url" binding:"required"`
|
Url string `form:"url" binding:"required"`
|
||||||
|
Password string `form:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShortLinkGetValidator struct {
|
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:""`
|
AutoTest bool `form:"autoTest,default=false" binding:""`
|
||||||
Lazy bool `form:"lazy,default=false" binding:""`
|
Lazy bool `form:"lazy,default=false" binding:""`
|
||||||
Sort string `form:"sort" 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 {
|
type RuleProviderStruct struct {
|
||||||
@ -135,5 +139,17 @@ func ParseQuery(c *gin.Context) (SubValidator, error) {
|
|||||||
} else {
|
} else {
|
||||||
query.Rules = nil
|
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
|
return query, nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user