mirror of
https://github.com/woodchen-ink/proxy-go.git
synced 2025-07-18 00:21:56 +08:00
添加域名过滤功能,支持根据请求域名配置不同的扩展规则,增强代理服务的灵活性和可控性。
This commit is contained in:
parent
aed0f755c8
commit
f126dbb9dc
@ -86,12 +86,21 @@ func (cm *ConfigManager) createDefaultConfig() error {
|
||||
Target: "https://img1.example.com",
|
||||
SizeThreshold: 500 * 1024, // 500KB
|
||||
MaxSize: 2 * 1024 * 1024, // 2MB
|
||||
Domains: "a.com,b.com", // 只对a.com和b.com域名生效
|
||||
},
|
||||
{
|
||||
Extensions: "jpg,png,webp",
|
||||
Target: "https://img2.example.com",
|
||||
SizeThreshold: 2 * 1024 * 1024, // 2MB
|
||||
MaxSize: 5 * 1024 * 1024, // 5MB
|
||||
Domains: "b.com", // 只对b.com域名生效
|
||||
},
|
||||
{
|
||||
Extensions: "mp4,avi",
|
||||
Target: "https://video.example.com",
|
||||
SizeThreshold: 1024 * 1024, // 1MB
|
||||
MaxSize: 50 * 1024 * 1024, // 50MB
|
||||
// 不指定Domains,对所有域名生效
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -23,6 +23,7 @@ type ExtensionRule struct {
|
||||
SizeThreshold int64 // 最小阈值
|
||||
MaxSize int64 // 最大阈值
|
||||
RedirectMode bool // 是否使用302跳转模式
|
||||
Domains []string // 支持的域名列表,为空表示匹配所有域名
|
||||
}
|
||||
|
||||
type CompressionConfig struct {
|
||||
@ -42,6 +43,7 @@ type ExtRuleConfig struct {
|
||||
SizeThreshold int64 `json:"SizeThreshold"` // 最小阈值
|
||||
MaxSize int64 `json:"MaxSize"` // 最大阈值
|
||||
RedirectMode bool `json:"RedirectMode"` // 是否使用302跳转模式
|
||||
Domains string `json:"Domains"` // 逗号分隔的域名列表,为空表示匹配所有域名
|
||||
}
|
||||
|
||||
// 处理扩展名映射的方法
|
||||
@ -69,6 +71,16 @@ func (p *PathConfig) ProcessExtensionMap() {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理域名列表
|
||||
if rule.Domains != "" {
|
||||
for _, domain := range strings.Split(rule.Domains, ",") {
|
||||
domain = strings.TrimSpace(domain)
|
||||
if domain != "" {
|
||||
extRule.Domains = append(extRule.Domains, domain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(extRule.Extensions) > 0 {
|
||||
p.ExtRules = append(p.ExtRules, extRule)
|
||||
}
|
||||
|
@ -38,17 +38,17 @@ func (rh *RedirectHandler) HandleRedirect(w http.ResponseWriter, r *http.Request
|
||||
|
||||
// shouldRedirect 判断是否应该进行302跳转,并返回目标URL(优化版本)
|
||||
func (rh *RedirectHandler) shouldRedirect(r *http.Request, pathConfig config.PathConfig, targetPath string, client *http.Client) (bool, string) {
|
||||
// 使用service包的规则选择函数
|
||||
result := rh.ruleService.SelectRuleForRedirect(client, pathConfig, targetPath)
|
||||
// 使用service包的规则选择函数,传递请求的域名
|
||||
result := rh.ruleService.SelectRuleForRedirect(client, pathConfig, targetPath, r.Host)
|
||||
|
||||
if result.ShouldRedirect {
|
||||
// 构建完整的目标URL
|
||||
targetURL := rh.buildTargetURL(result.TargetURL, targetPath, r.URL.RawQuery)
|
||||
|
||||
if result.Rule != nil {
|
||||
log.Printf("[Redirect] %s -> 使用选中规则进行302跳转: %s", targetPath, targetURL)
|
||||
log.Printf("[Redirect] %s -> 使用选中规则进行302跳转 (域名: %s): %s", targetPath, r.Host, targetURL)
|
||||
} else {
|
||||
log.Printf("[Redirect] %s -> 使用默认目标进行302跳转: %s", targetPath, targetURL)
|
||||
log.Printf("[Redirect] %s -> 使用默认目标进行302跳转 (域名: %s): %s", targetPath, r.Host, targetURL)
|
||||
}
|
||||
|
||||
return true, targetURL
|
||||
|
@ -27,7 +27,7 @@ func NewRuleService(cacheManager CacheManager) *RuleService {
|
||||
}
|
||||
|
||||
// SelectBestRule 选择最合适的规则
|
||||
func (rs *RuleService) SelectBestRule(client *http.Client, pathConfig config.PathConfig, path string) (*config.ExtensionRule, bool, bool) {
|
||||
func (rs *RuleService) SelectBestRule(client *http.Client, pathConfig config.PathConfig, path string, requestHost string) (*config.ExtensionRule, bool, bool) {
|
||||
// 如果没有扩展名规则,返回nil
|
||||
if len(pathConfig.ExtRules) == 0 {
|
||||
return nil, false, false
|
||||
@ -53,6 +53,20 @@ func (rs *RuleService) SelectBestRule(client *http.Client, pathConfig config.Pat
|
||||
return nil, false, false
|
||||
}
|
||||
|
||||
// 过滤符合域名条件的规则
|
||||
var domainMatchingRules []*config.ExtensionRule
|
||||
for _, rule := range matchingRules {
|
||||
if rs.isDomainMatching(rule, requestHost) {
|
||||
domainMatchingRules = append(domainMatchingRules, rule)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有域名匹配的规则,返回nil
|
||||
if len(domainMatchingRules) == 0 {
|
||||
log.Printf("[SelectRule] %s -> 没有找到匹配域名 %s 的扩展名规则", path, requestHost)
|
||||
return nil, false, false
|
||||
}
|
||||
|
||||
// 获取文件大小
|
||||
contentLength, err := utils.GetFileSize(client, pathConfig.DefaultTarget+path)
|
||||
if err != nil {
|
||||
@ -62,12 +76,12 @@ func (rs *RuleService) SelectBestRule(client *http.Client, pathConfig config.Pat
|
||||
}
|
||||
|
||||
// 根据文件大小找出最匹配的规则(规则已经预排序)
|
||||
for _, rule := range matchingRules {
|
||||
for _, rule := range domainMatchingRules {
|
||||
// 检查文件大小是否在阈值范围内
|
||||
if contentLength >= rule.SizeThreshold && contentLength <= rule.MaxSize {
|
||||
// 找到匹配的规则
|
||||
log.Printf("[SelectRule] %s -> 选中规则 (文件大小: %s, 在区间 %s 到 %s 之间)",
|
||||
path, utils.FormatBytes(contentLength),
|
||||
log.Printf("[SelectRule] %s -> 选中规则 (域名: %s, 文件大小: %s, 在区间 %s 到 %s 之间)",
|
||||
path, requestHost, utils.FormatBytes(contentLength),
|
||||
utils.FormatBytes(rule.SizeThreshold), utils.FormatBytes(rule.MaxSize))
|
||||
|
||||
// 检查目标是否可访问
|
||||
@ -85,6 +99,29 @@ func (rs *RuleService) SelectBestRule(client *http.Client, pathConfig config.Pat
|
||||
return nil, false, false
|
||||
}
|
||||
|
||||
// isDomainMatching 检查规则的域名是否匹配请求的域名
|
||||
func (rs *RuleService) isDomainMatching(rule *config.ExtensionRule, requestHost string) bool {
|
||||
// 如果规则没有指定域名,则匹配所有域名
|
||||
if len(rule.Domains) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// 提取请求域名(去除端口号)
|
||||
host := requestHost
|
||||
if colonIndex := strings.Index(host, ":"); colonIndex != -1 {
|
||||
host = host[:colonIndex]
|
||||
}
|
||||
|
||||
// 检查是否匹配任一指定的域名
|
||||
for _, domain := range rule.Domains {
|
||||
if strings.EqualFold(host, domain) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// RuleSelectionResult 规则选择结果
|
||||
type RuleSelectionResult struct {
|
||||
Rule *config.ExtensionRule
|
||||
@ -95,7 +132,7 @@ type RuleSelectionResult struct {
|
||||
}
|
||||
|
||||
// SelectRuleForRedirect 专门为302跳转优化的规则选择函数
|
||||
func (rs *RuleService) SelectRuleForRedirect(client *http.Client, pathConfig config.PathConfig, path string) *RuleSelectionResult {
|
||||
func (rs *RuleService) SelectRuleForRedirect(client *http.Client, pathConfig config.PathConfig, path string, requestHost string) *RuleSelectionResult {
|
||||
result := &RuleSelectionResult{}
|
||||
|
||||
// 快速检查:如果没有任何302跳转配置,直接返回
|
||||
@ -106,7 +143,7 @@ func (rs *RuleService) SelectRuleForRedirect(client *http.Client, pathConfig con
|
||||
// 优先检查扩展名规则,即使根级别配置了302跳转
|
||||
if len(pathConfig.ExtRules) > 0 {
|
||||
// 尝试选择最佳规则(包括文件大小检测)
|
||||
if rule, found, usedAlt := rs.SelectBestRule(client, pathConfig, path); found && rule != nil && rule.RedirectMode {
|
||||
if rule, found, usedAlt := rs.SelectBestRule(client, pathConfig, path, requestHost); found && rule != nil && rule.RedirectMode {
|
||||
result.Rule = rule
|
||||
result.Found = found
|
||||
result.UsedAltTarget = usedAlt
|
||||
@ -120,7 +157,8 @@ func (rs *RuleService) SelectRuleForRedirect(client *http.Client, pathConfig con
|
||||
// 1. 扩展名不匹配,或者
|
||||
// 2. 扩展名匹配但文件大小不在配置范围内,或者
|
||||
// 3. 无法获取文件大小,或者
|
||||
// 4. 目标服务器不可访问
|
||||
// 4. 目标服务器不可访问,或者
|
||||
// 5. 域名不匹配
|
||||
// 在这些情况下,我们不应该强制使用扩展名规则
|
||||
}
|
||||
|
||||
@ -151,7 +189,7 @@ func (rs *RuleService) GetTargetURL(client *http.Client, r *http.Request, pathCo
|
||||
}
|
||||
|
||||
// 使用严格的规则选择逻辑
|
||||
rule, found, usedAlt := rs.SelectBestRule(client, pathConfig, path)
|
||||
rule, found, usedAlt := rs.SelectBestRule(client, pathConfig, path, r.Host)
|
||||
if found && rule != nil {
|
||||
targetBase = rule.Target
|
||||
usedAltTarget = usedAlt
|
||||
|
139
readme.md
139
readme.md
@ -4,16 +4,12 @@ A 'simple' reverse proxy server written in Go.
|
||||
|
||||
使用方法: https://www.q58.club/t/topic/165
|
||||
|
||||
```
|
||||
最新镜像地址: woodchen/proxy-go:latest
|
||||
```
|
||||
|
||||
## 新版统计仪表盘
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## 图片
|
||||
|
||||

|
||||
@ -22,19 +18,14 @@ A 'simple' reverse proxy server written in Go.
|
||||
|
||||

|
||||
|
||||
|
||||
### 配置页
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 缓存页
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## 说明
|
||||
|
||||
1. 支持gzip和brotli压缩
|
||||
@ -45,5 +36,135 @@ A 'simple' reverse proxy server written in Go.
|
||||
6. 适配Cloudflare Images的图片自适应功能, 透传`Accept`头, 支持`format=auto`
|
||||
7. 支持网页端监控和管理
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 🚀 **多路径代理**: 根据不同路径代理到不同的目标服务器
|
||||
- 🔄 **扩展名规则**: 根据文件扩展名和大小智能选择目标服务器
|
||||
- 🌐 **域名过滤**: 支持根据请求域名应用不同的扩展规则
|
||||
- 📦 **压缩支持**: 支持Gzip和Brotli压缩
|
||||
- 🎯 **302跳转**: 支持302跳转模式
|
||||
- 📊 **缓存管理**: 智能缓存机制提升性能
|
||||
- 📈 **监控指标**: 内置监控和指标收集
|
||||
|
||||
## 域名过滤功能
|
||||
|
||||
### 功能介绍
|
||||
|
||||
新增的域名过滤功能允许你为不同的请求域名配置不同的扩展规则。这在以下场景中非常有用:
|
||||
|
||||
1. **多域名服务**: 一个代理服务绑定多个域名(如 a.com 和 b.com)
|
||||
2. **差异化配置**: 不同域名使用不同的CDN或存储服务
|
||||
3. **精细化控制**: 根据域名和文件类型组合进行精确路由
|
||||
|
||||
### 配置示例
|
||||
|
||||
```json
|
||||
{
|
||||
"MAP": {
|
||||
"/images": {
|
||||
"DefaultTarget": "https://default-cdn.com",
|
||||
"ExtensionMap": [
|
||||
{
|
||||
"Extensions": "jpg,png,webp",
|
||||
"Target": "https://a-domain-cdn.com",
|
||||
"SizeThreshold": 1024,
|
||||
"MaxSize": 2097152,
|
||||
"Domains": "a.com",
|
||||
"RedirectMode": false
|
||||
},
|
||||
{
|
||||
"Extensions": "jpg,png,webp",
|
||||
"Target": "https://b-domain-cdn.com",
|
||||
"SizeThreshold": 1024,
|
||||
"MaxSize": 2097152,
|
||||
"Domains": "b.com",
|
||||
"RedirectMode": true
|
||||
},
|
||||
{
|
||||
"Extensions": "mp4,avi",
|
||||
"Target": "https://video-cdn.com",
|
||||
"SizeThreshold": 1048576,
|
||||
"MaxSize": 52428800
|
||||
// 不指定Domains,对所有域名生效
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 使用场景
|
||||
|
||||
#### 场景1: 多域名图片CDN
|
||||
```
|
||||
请求: https://a.com/images/photo.jpg (1MB)
|
||||
结果: 代理到 https://a-domain-cdn.com/photo.jpg
|
||||
|
||||
请求: https://b.com/images/photo.jpg (1MB)
|
||||
结果: 302跳转到 https://b-domain-cdn.com/photo.jpg
|
||||
|
||||
请求: https://c.com/images/photo.jpg (1MB)
|
||||
结果: 代理到 https://default-cdn.com/photo.jpg (使用默认目标)
|
||||
```
|
||||
|
||||
#### 场景2: 域名+扩展名组合规则
|
||||
```
|
||||
请求: https://a.com/files/video.mp4 (10MB)
|
||||
结果: 代理到 https://video-cdn.com/video.mp4 (匹配通用视频规则)
|
||||
|
||||
请求: https://b.com/files/video.mp4 (10MB)
|
||||
结果: 代理到 https://video-cdn.com/video.mp4 (匹配通用视频规则)
|
||||
```
|
||||
|
||||
### 配置字段说明
|
||||
|
||||
- **Domains**: 逗号分隔的域名列表,指定该规则适用的域名
|
||||
- 为空或不设置:匹配所有域名
|
||||
- 单个域名:`"a.com"`
|
||||
- 多个域名:`"a.com,b.com,c.com"`
|
||||
- **Extensions**: 文件扩展名(与之前相同)
|
||||
- **Target**: 目标服务器(与之前相同)
|
||||
- **SizeThreshold/MaxSize**: 文件大小范围(与之前相同)
|
||||
- **RedirectMode**: 是否使用302跳转(与之前相同)
|
||||
|
||||
### 匹配优先级
|
||||
|
||||
1. **域名匹配**: 首先筛选出匹配请求域名的规则
|
||||
2. **扩展名匹配**: 在域名匹配的规则中筛选扩展名匹配的规则
|
||||
3. **文件大小匹配**: 根据文件大小选择最合适的规则
|
||||
4. **目标可用性**: 检查目标服务器是否可访问
|
||||
5. **默认回退**: 如果没有匹配的规则,使用默认目标
|
||||
|
||||
### 日志输出
|
||||
|
||||
启用域名过滤后,日志会包含域名信息:
|
||||
|
||||
```
|
||||
[SelectRule] /image.jpg -> 选中规则 (域名: a.com, 文件大小: 1.2MB, 在区间 1KB 到 2MB 之间)
|
||||
[Redirect] /image.jpg -> 使用选中规则进行302跳转 (域名: b.com): https://b-domain-cdn.com/image.jpg
|
||||
```
|
||||
|
||||
## 原有功能
|
||||
|
||||
### 功能作用
|
||||
|
||||
主要是最好有一台国外服务器, 回国又不慢的, 可以反代国外资源, 然后在proxy-go外面套个cloudfront或者Edgeone, 方便国内访问.
|
||||
|
||||
config里MAP的功能
|
||||
|
||||
目前我的主要使用是反代B2, R2, Oracle存储桶之类的. 也可以反代网站静态资源, 可以一并在CDN环节做缓存.
|
||||
|
||||
根据config示例作示范
|
||||
|
||||
访问https://proxy-go/path1/123.jpg, 实际是访问 https://path1.com/path/path/path/123.jpg
|
||||
访问https://proxy-go/path2/749.movie, 实际是访问https://path2.com/749.movie
|
||||
|
||||
### mirror 固定路由
|
||||
比较适合接口类的CORS问题
|
||||
|
||||
访问https://proxy-go/mirror/https://example.com/path/to/resource
|
||||
|
||||
会实际访问https://example.com/path/to/resource
|
||||
|
||||
|
||||
|
||||
|
@ -35,6 +35,7 @@ interface ExtRuleConfig {
|
||||
SizeThreshold: number; // 最小阈值(字节)
|
||||
MaxSize: number; // 最大阈值(字节)
|
||||
RedirectMode?: boolean; // 是否使用302跳转模式
|
||||
Domains?: string; // 逗号分隔的域名列表,为空表示匹配所有域名
|
||||
}
|
||||
|
||||
interface PathMapping {
|
||||
@ -110,6 +111,7 @@ export default function ConfigPage() {
|
||||
maxSize: number;
|
||||
sizeThresholdUnit: 'B' | 'KB' | 'MB' | 'GB';
|
||||
maxSizeUnit: 'B' | 'KB' | 'MB' | 'GB';
|
||||
domains: string;
|
||||
}>({
|
||||
extensions: "",
|
||||
target: "",
|
||||
@ -118,6 +120,7 @@ export default function ConfigPage() {
|
||||
maxSize: 0,
|
||||
sizeThresholdUnit: 'MB',
|
||||
maxSizeUnit: 'MB',
|
||||
domains: "",
|
||||
});
|
||||
|
||||
const [editingExtensionRule, setEditingExtensionRule] = useState<{
|
||||
@ -128,6 +131,7 @@ export default function ConfigPage() {
|
||||
maxSize: number;
|
||||
sizeThresholdUnit: 'B' | 'KB' | 'MB' | 'GB';
|
||||
maxSizeUnit: 'B' | 'KB' | 'MB' | 'GB';
|
||||
domains: string;
|
||||
} | null>(null);
|
||||
|
||||
// 添加扩展名规则对话框状态
|
||||
@ -527,13 +531,14 @@ export default function ConfigPage() {
|
||||
maxSize: 0,
|
||||
sizeThresholdUnit: 'MB',
|
||||
maxSizeUnit: 'MB',
|
||||
domains: "",
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [handleDialogOpenChange]);
|
||||
|
||||
// 处理扩展名规则的编辑
|
||||
const handleExtensionRuleEdit = (path: string, index?: number, rule?: { Extensions: string; Target: string; SizeThreshold?: number; MaxSize?: number; RedirectMode?: boolean }) => {
|
||||
const handleExtensionRuleEdit = (path: string, index?: number, rule?: { Extensions: string; Target: string; SizeThreshold?: number; MaxSize?: number; RedirectMode?: boolean; Domains?: string }) => {
|
||||
setEditingPath(path);
|
||||
|
||||
if (index !== undefined && rule) {
|
||||
@ -549,6 +554,7 @@ export default function ConfigPage() {
|
||||
maxSize: maxValue,
|
||||
sizeThresholdUnit: thresholdUnit,
|
||||
maxSizeUnit: maxUnit,
|
||||
domains: rule.Domains || "",
|
||||
});
|
||||
|
||||
// 同时更新表单显示数据
|
||||
@ -560,6 +566,7 @@ export default function ConfigPage() {
|
||||
maxSize: maxValue,
|
||||
sizeThresholdUnit: thresholdUnit,
|
||||
maxSizeUnit: maxUnit,
|
||||
domains: rule.Domains || "",
|
||||
});
|
||||
} else {
|
||||
setEditingExtensionRule(null);
|
||||
@ -572,6 +579,7 @@ export default function ConfigPage() {
|
||||
maxSize: 0,
|
||||
sizeThresholdUnit: 'MB',
|
||||
maxSizeUnit: 'MB',
|
||||
domains: "",
|
||||
});
|
||||
}
|
||||
|
||||
@ -582,7 +590,7 @@ export default function ConfigPage() {
|
||||
const addOrUpdateExtensionRule = () => {
|
||||
if (!config || !editingPath) return;
|
||||
|
||||
const { extensions, target, redirectMode, sizeThreshold, maxSize, sizeThresholdUnit, maxSizeUnit } = newExtensionRule;
|
||||
const { extensions, target, redirectMode, sizeThreshold, maxSize, sizeThresholdUnit, maxSizeUnit, domains } = newExtensionRule;
|
||||
|
||||
// 验证输入
|
||||
if (!extensions.trim() || !target.trim()) {
|
||||
@ -617,6 +625,23 @@ export default function ConfigPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证域名格式(如果提供)
|
||||
if (domains.trim()) {
|
||||
const domainList = domains.split(',').map(d => d.trim());
|
||||
const domainRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
|
||||
|
||||
for (const domain of domainList) {
|
||||
if (domain && !domainRegex.test(domain)) {
|
||||
toast({
|
||||
title: "错误",
|
||||
description: `域名格式不正确: ${domain}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 转换大小为字节
|
||||
const sizeThresholdBytes = convertToBytes(sizeThreshold, sizeThresholdUnit);
|
||||
const maxSizeBytes = convertToBytes(maxSize, maxSizeUnit);
|
||||
@ -642,7 +667,9 @@ export default function ConfigPage() {
|
||||
Extensions: extensions,
|
||||
Target: target,
|
||||
SizeThreshold: sizeThresholdBytes,
|
||||
MaxSize: maxSizeBytes
|
||||
MaxSize: maxSizeBytes,
|
||||
RedirectMode: redirectMode,
|
||||
Domains: domains.trim() || undefined
|
||||
}]
|
||||
};
|
||||
} else {
|
||||
@ -659,7 +686,8 @@ export default function ConfigPage() {
|
||||
Target: target,
|
||||
SizeThreshold: sizeThresholdBytes,
|
||||
MaxSize: maxSizeBytes,
|
||||
RedirectMode: redirectMode
|
||||
RedirectMode: redirectMode,
|
||||
Domains: domains.trim() || undefined
|
||||
};
|
||||
} else {
|
||||
// 添加新规则
|
||||
@ -668,7 +696,8 @@ export default function ConfigPage() {
|
||||
Target: target,
|
||||
SizeThreshold: sizeThresholdBytes,
|
||||
MaxSize: maxSizeBytes,
|
||||
RedirectMode: redirectMode
|
||||
RedirectMode: redirectMode,
|
||||
Domains: domains.trim() || undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -684,6 +713,7 @@ export default function ConfigPage() {
|
||||
maxSize: 0,
|
||||
sizeThresholdUnit: 'MB',
|
||||
maxSizeUnit: 'MB',
|
||||
domains: "",
|
||||
});
|
||||
};
|
||||
|
||||
@ -886,6 +916,11 @@ export default function ConfigPage() {
|
||||
302
|
||||
</span>
|
||||
)}
|
||||
{rule.Domains && (
|
||||
<span className="text-xs bg-purple-100 text-purple-800 px-1.5 py-0.5 rounded" title={`限制域名: ${rule.Domains}`}>
|
||||
域名
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex space-x-1">
|
||||
<Button
|
||||
@ -909,6 +944,11 @@ export default function ConfigPage() {
|
||||
<div className="text-muted-foreground truncate" title={rule.Target}>
|
||||
目标: {truncateUrl(rule.Target)}
|
||||
</div>
|
||||
{rule.Domains && (
|
||||
<div className="text-muted-foreground truncate" title={rule.Domains}>
|
||||
域名: {rule.Domains}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between mt-1 text-muted-foreground">
|
||||
<div>阈值: {formatBytes(rule.SizeThreshold || 0)}</div>
|
||||
<div>最大: {formatBytes(rule.MaxSize || 0)}</div>
|
||||
@ -1024,6 +1064,17 @@ export default function ConfigPage() {
|
||||
placeholder="https://example.com"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>限制域名(可选)</Label>
|
||||
<Input
|
||||
value={newExtensionRule.domains}
|
||||
onChange={(e) => setNewExtensionRule({ ...newExtensionRule, domains: e.target.value })}
|
||||
placeholder="a.com,b.com"
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
指定该规则适用的域名,多个域名用逗号分隔。留空表示适用于所有域名。
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<Label>使用302跳转</Label>
|
||||
<Switch
|
||||
|
Loading…
x
Reference in New Issue
Block a user