diff --git a/src/Config/AppConfig.cs b/src/Config/AppConfig.cs
index 14f6d92..8727135 100644
--- a/src/Config/AppConfig.cs
+++ b/src/Config/AppConfig.cs
@@ -23,6 +23,9 @@ namespace iFileProxy.Config
[JsonPropertyName("Database")]
public Database Database { get; set; }
+ [JsonPropertyName("GithubProxy")]
+ public GithubProxyOptions GithubProxyOptions { get; set; } = new();
+
public static AppConfig GetCurrConfig(string configPath = "iFileProxy.json")
{
if (File.Exists(configPath))
@@ -140,4 +143,22 @@ namespace iFileProxy.Config
[JsonPropertyName("Description")]
public string Description { get; set; }
}
+
+ public class GithubProxyOptions
+ {
+ ///
+ /// 文件大小限制(字节)
+ ///
+ public long SizeLimit { get; set; } = 1024L * 1024L * 1024L; // 默认1GB
+
+ ///
+ /// 黑名单列表
+ ///
+ public List Blacklist { get; set; } = [];
+
+ ///
+ /// 是否启用 jsDelivr 加速
+ ///
+ public bool EnableJsDelivr { get; set; } = false;
+ }
}
diff --git a/src/Controllers/GithubProxyController.cs b/src/Controllers/GithubProxyController.cs
new file mode 100644
index 0000000..f0562a8
--- /dev/null
+++ b/src/Controllers/GithubProxyController.cs
@@ -0,0 +1,87 @@
+using Microsoft.AspNetCore.Mvc;
+using iFileProxy.Services;
+using iFileProxy.Models;
+
+namespace iFileProxy.Controllers
+{
+ [Route("[controller]")]
+ [ApiController]
+ public class GithubProxyController : ControllerBase
+ {
+ private readonly ILogger _logger;
+ private readonly GithubProxyService _proxyService;
+
+ public GithubProxyController(
+ ILogger logger,
+ GithubProxyService proxyService)
+ {
+ _logger = logger;
+ _proxyService = proxyService;
+ }
+
+ [HttpGet("{**url}")]
+ public async Task ProxyDownload(string url)
+ {
+ try
+ {
+ // 1. URL 格式化
+ url = url.StartsWith("http") ? url : $"https://{url}";
+
+ // 2. 验证 URL
+ var (isValid, author, repo) = _proxyService.ValidateUrl(url);
+ if (!isValid || author == null || repo == null)
+ {
+ return BadRequest(new CommonRsp
+ {
+ Retcode = 1,
+ Message = "Invalid GitHub URL"
+ });
+ }
+
+ // 3. 检查黑名单
+ if (_proxyService.IsBlocked(author, repo))
+ {
+ return Forbid();
+ }
+
+ // 4. 处理URL(CDN或Raw)
+ url = _proxyService.ProcessUrl(url);
+
+ // 5. 代理请求
+ var response = await _proxyService.ProxyRequestAsync(url, Request.Headers);
+ if (!response.Success)
+ {
+ return StatusCode(response.StatusCode, new CommonRsp
+ {
+ Retcode = 1,
+ Message = response.Message
+ });
+ }
+
+ // 6. 设置响应头
+ if (response.Headers != null)
+ {
+ foreach (var header in response.Headers)
+ {
+ Response.Headers[header.Key] = header.Value;
+ }
+ }
+
+ // 7. 返回流式响应
+ return new FileStreamResult(
+ response.Stream!,
+ response.ContentType ?? "application/octet-stream"
+ );
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Proxy download failed");
+ return StatusCode(500, new CommonRsp
+ {
+ Retcode = 1,
+ Message = "Internal server error"
+ });
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Controllers/UserController.cs b/src/Controllers/UserController.cs
index bbbc212..59915d4 100644
--- a/src/Controllers/UserController.cs
+++ b/src/Controllers/UserController.cs
@@ -50,7 +50,7 @@ namespace iFileProxy.Controllers
{
try
{
- var ip = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
+ var ip = MasterHelper.GetClientIPAddr(HttpContext);
var userAgent = HttpContext.Request.Headers["User-Agent"].FirstOrDefault() ?? "unknown";
var fingerprint = HttpContext.Request.Headers["X-Device-Fingerprint"].FirstOrDefault() ?? "unknown";
diff --git a/src/Middleware/JwtMiddleware.cs b/src/Middleware/JwtMiddleware.cs
index 3aca506..1a3e75a 100644
--- a/src/Middleware/JwtMiddleware.cs
+++ b/src/Middleware/JwtMiddleware.cs
@@ -1,3 +1,4 @@
+using iFileProxy.Helpers;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
@@ -21,7 +22,7 @@ namespace iFileProxy.Middleware
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
var fingerprint = context.Request.Headers["X-Device-Fingerprint"].FirstOrDefault();
var userAgent = context.Request.Headers["User-Agent"].FirstOrDefault();
- var ip = context.Connection.RemoteIpAddress?.ToString();
+ var ip = MasterHelper.GetClientIPAddr(context);
if (token != null)
{
diff --git a/src/Program.cs b/src/Program.cs
index 6af721c..d75abf7 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -19,7 +19,6 @@ namespace iFileProxy
Console.Write(" "); // 补全日志第一行开头的空白
var builder = WebApplication.CreateBuilder(args);
-
// CORS配置
builder.Services.AddCors(options =>
{
@@ -27,7 +26,7 @@ namespace iFileProxy
builder =>
{
builder
- .WithOrigins("http://localhost:3000", "http://admin.gitdl.cn", "https://admin.gitdl.cn","http://47.243.56.137:50050", "http://localhost:4173")
+ .WithOrigins("http://localhost:3000", "http://admin.gitdl.cn", "https://admin.gitdl.cn", "https://github.linxi.info", "http://github.linxi.info/", "http://localhost:4173" )
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
diff --git a/src/Services/DatabaseGateService.cs b/src/Services/DatabaseGateService.cs
index 33ba47b..28923ae 100644
--- a/src/Services/DatabaseGateService.cs
+++ b/src/Services/DatabaseGateService.cs
@@ -995,7 +995,7 @@ namespace iFileProxy.Services
{ "@level", log.Level },
{ "@message", log.Message },
{ "@exception", log.Exception },
- { "@properties", JsonConvert.SerializeObject(log.Properties) },
+ { "@properties", log.Properties },
{ "@timestamp", log.Timestamp }
};
diff --git a/src/Services/GithubProxyService.cs b/src/Services/GithubProxyService.cs
new file mode 100644
index 0000000..7b1aa7f
--- /dev/null
+++ b/src/Services/GithubProxyService.cs
@@ -0,0 +1,130 @@
+using System.Text.RegularExpressions;
+using iFileProxy.Config;
+using iFileProxy.Models;
+
+namespace iFileProxy.Services
+{
+ public class GithubProxyService
+ {
+ private readonly ILogger _logger;
+ private readonly AppConfig _appConfig;
+ private readonly HttpClient _httpClient;
+
+ private static readonly Regex[] URL_PATTERNS =
+ {
+ new(@"^(?:https?://)?github\.com/(?.+?)/(?.+?)/(?:releases|archive)/.*$"),
+ new(@"^(?:https?://)?github\.com/(?.+?)/(?.+?)/(?:blob|raw)/.*$"),
+ new(@"^(?:https?://)?raw\.(?:githubusercontent|github)\.com/(?.+?)/(?.+?)/.+?/.+$"),
+ new(@"^(?:https?://)?gist\.(?:githubusercontent|github)\.com/(?.+?)/.+?/.+$")
+ };
+
+ public GithubProxyService(
+ ILogger logger,
+ AppConfig appConfig,
+ HttpClient httpClient)
+ {
+ _logger = logger;
+ _appConfig = appConfig;
+ _httpClient = httpClient;
+ }
+
+ public (bool isValid, string? author, string? repo) ValidateUrl(string url)
+ {
+ foreach (var pattern in URL_PATTERNS)
+ {
+ var match = pattern.Match(url);
+ if (match.Success)
+ {
+ return (true, match.Groups["author"].Value, match.Groups["repo"].Value);
+ }
+ }
+ return (false, null, null);
+ }
+
+ public bool IsBlocked(string author, string repo)
+ {
+ return _appConfig.GithubProxyOptions.Blacklist.Contains($"{author}/{repo}") ||
+ _appConfig.GithubProxyOptions.Blacklist.Contains(author);
+ }
+
+ public string ProcessUrl(string url)
+ {
+ if (_appConfig.GithubProxyOptions.EnableJsDelivr && url.Contains("/blob/"))
+ {
+ return url.Replace("/blob/", "@")
+ .Replace("github.com", "cdn.jsdelivr.net/gh");
+ }
+ else if (url.Contains("/blob/"))
+ {
+ return url.Replace("/blob/", "/raw/");
+ }
+ return url;
+ }
+
+ public async Task ProxyRequestAsync(
+ string url,
+ IHeaderDictionary headers,
+ CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ var request = new HttpRequestMessage(HttpMethod.Get, url);
+
+ // 复制请求头
+ foreach (var header in headers)
+ {
+ if (!header.Key.Equals("Host", StringComparison.OrdinalIgnoreCase))
+ {
+ request.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
+ }
+ }
+
+ var response = await _httpClient.SendAsync(
+ request,
+ HttpCompletionOption.ResponseHeadersRead,
+ cancellationToken);
+
+ // 检查文件大小
+ var contentLength = response.Content.Headers.ContentLength;
+ if (contentLength.HasValue && contentLength.Value > _appConfig.GithubProxyOptions.SizeLimit)
+ {
+ return new ProxyResponse
+ {
+ Success = false,
+ StatusCode = StatusCodes.Status413PayloadTooLarge,
+ Message = "File too large"
+ };
+ }
+
+ return new ProxyResponse
+ {
+ Success = true,
+ StatusCode = (int)response.StatusCode,
+ Headers = response.Headers.ToDictionary(h => h.Key, h => h.Value.ToArray()),
+ Stream = await response.Content.ReadAsStreamAsync(cancellationToken),
+ ContentType = response.Content.Headers.ContentType?.ToString()
+ };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Proxy request failed");
+ return new ProxyResponse
+ {
+ Success = false,
+ StatusCode = StatusCodes.Status500InternalServerError,
+ Message = "Internal server error"
+ };
+ }
+ }
+ }
+
+ public class ProxyResponse
+ {
+ public bool Success { get; set; }
+ public int StatusCode { get; set; }
+ public string? Message { get; set; }
+ public Dictionary? Headers { get; set; }
+ public Stream? Stream { get; set; }
+ public string? ContentType { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/appsettings.json b/src/appsettings.json
index 42f9c78..90a065a 100644
--- a/src/appsettings.json
+++ b/src/appsettings.json
@@ -10,5 +10,13 @@
"Key": "iFileProxy-JWT-Secret-Key-2024-Very-Long-Secret-Key-For-Security",
"Issuer": "iFileProxy",
"Audience": "iFileProxy.Client"
+ },
+ "GithubProxy": {
+ "SizeLimit": 1073741824, // 1GB in bytes
+ "Blacklist": [
+ "blockedUser1",
+ "blockedUser2/blockedRepo",
+ "blockedOrg/*"
+ ]
}
}
diff --git a/src/document/iFileProxy.sql b/src/document/iFileProxy.sql
index 9117ea6..f928e15 100644
--- a/src/document/iFileProxy.sql
+++ b/src/document/iFileProxy.sql
@@ -11,12 +11,28 @@
Target Server Version : 50743
File Encoding : 65001
- Date: 01/12/2024 01:28:13
+ Date: 04/12/2024 00:13:59
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
+-- ----------------------------
+-- Table structure for t_system_logs
+-- ----------------------------
+DROP TABLE IF EXISTS `t_system_logs`;
+CREATE TABLE `t_system_logs` (
+ `log_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
+ `level` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
+ `message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
+ `exception` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
+ `properties` json NULL,
+ `timestamp` datetime NOT NULL,
+ PRIMARY KEY (`log_id`) USING BTREE,
+ INDEX `idx_timestamp`(`timestamp`) USING BTREE,
+ INDEX `idx_level`(`level`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
+
-- ----------------------------
-- Table structure for t_tasks_info
-- ----------------------------
@@ -35,7 +51,7 @@ CREATE TABLE `t_tasks_info` (
`tag` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标记',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `tid`(`tid`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 9130 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
+) ENGINE = InnoDB AUTO_INCREMENT = 111135 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for t_user_events
@@ -60,6 +76,7 @@ DROP TABLE IF EXISTS `t_users`;
CREATE TABLE `t_users` (
`user_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '昵称',
+ `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '电子邮箱',
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名',
`password_hash` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码哈希值',
`mask` int(11) NOT NULL DEFAULT 0 COMMENT '权限掩码',
@@ -68,7 +85,8 @@ CREATE TABLE `t_users` (
`last_login_time` datetime NULL DEFAULT NULL COMMENT '上次登录时间',
`last_login_ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '上次登录IP',
PRIMARY KEY (`user_id`) USING BTREE,
- UNIQUE INDEX `username`(`username`) USING BTREE
+ UNIQUE INDEX `username`(`username`) USING BTREE,
+ UNIQUE INDEX `email`(`email`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
diff --git a/src/iFileProxy.json b/src/iFileProxy.json
index 36ee834..2b41bf4 100644
--- a/src/iFileProxy.json
+++ b/src/iFileProxy.json
@@ -39,5 +39,14 @@
"/AddOfflineTask"
],
"AllowDifferentIPsForDownload": true // 下载者与任务数据提交者IP不同是否允许下载文件
+ },
+ "GithubProxy": {
+ "SizeLimit": 1073741824,
+ "Blacklist": [
+ "blockedUser1",
+ "blockedUser2/blockedRepo",
+ "blockedOrg/*"
+ ],
+ "EnableJsDelivr": false
}
}
\ No newline at end of file