完善页面内容,支持路径匹配计数
This commit is contained in:
parent
be8303cd63
commit
3323eef81e
9 changed files with 326 additions and 130 deletions
13
README.md
13
README.md
|
@ -1 +1,12 @@
|
|||
### 文件代理下载工具
|
||||
### 文件代理下载工具
|
||||
|
||||
#### 功能列表
|
||||
- [x] 代理下载文件
|
||||
- [x] 浏览器下载文件
|
||||
- [x] 文件名黑名单
|
||||
- [x] 目标Host黑名单
|
||||
- [x] 文件大小限制
|
||||
- [x] 基于IP查询提交的任务状态
|
||||
- [x] 基于IP和路由的请求次数限制
|
||||
- [ ] 已经存在对应文件时候直接跳过代理下载 直接下载已经缓存的内容
|
||||
- [ ] 捐赠
|
|
@ -55,6 +55,7 @@ namespace iFileProxy.Config
|
|||
public List<string> BlockedHost { get; set; } = [];
|
||||
public List<string> BlockedFileName { get; set; } = [];
|
||||
public List<string> BlockedClientIP { get; set; } = [];
|
||||
public List<string> RoutesToTrack { get; set; } = [];
|
||||
public int DailyRequestLimitPerIP { get; set; } = -1;
|
||||
}
|
||||
|
||||
|
|
|
@ -176,7 +176,7 @@ namespace iFileProxy.Helpers
|
|||
|
||||
public bool UpdateTaskStatus(TaskInfo taskInfo)
|
||||
{
|
||||
string sql = @"UPDATE t_tasks_info set `status` = @status WHERE `tid` = @tid";
|
||||
string sql = @"UPDATE t_tasks_info set `status` = @status , update_time = Now() WHERE `tid` = @tid";
|
||||
MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
|
||||
try
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
namespace iFileProxy.Middleware
|
||||
{
|
||||
using iFileProxy.Config;
|
||||
using iFileProxy.Helpers;
|
||||
using iFileProxy.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
@ -13,6 +14,7 @@
|
|||
private readonly RequestDelegate _next;
|
||||
private readonly Dictionary<string, Dictionary<string, uint>> _IPAccessCountDict;
|
||||
private readonly int _dailyRequestLimitPerIP;
|
||||
private readonly AppConfig _appConfig = AppConfig.GetCurrConfig();
|
||||
|
||||
public IPAccessLimitMiddleware(RequestDelegate next, Dictionary<string, Dictionary<string, uint>> IPAccessCountDict, int dailyRequestLimitPerIP)
|
||||
{
|
||||
|
@ -23,6 +25,26 @@
|
|||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
// 获取需要跟踪的路由列表
|
||||
var routesToTrack = _appConfig.SecurityOptions.RoutesToTrack;
|
||||
|
||||
// 检查当前请求的路径是否在需要跟踪的路由列表中
|
||||
foreach (var p in routesToTrack)
|
||||
{
|
||||
if (context.Request.Path.ToString().StartsWith(p, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// 如果匹配到需要跟踪的路由,继续处理请求
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有匹配到需要跟踪的路由,直接调用下一个中间件
|
||||
if (!routesToTrack.Any(p => context.Request.Path.ToString().StartsWith(p, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
await _next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
string dateStr = DateTime.Now.ToString("yyyy-MM-dd");
|
||||
string ipStr = MasterHelper.GetClientIPAddr(context);
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
|||
<_TargetId>Folder</_TargetId>
|
||||
<SiteUrlToLaunchAfterPublish />
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
||||
<ProjectGuid>e343bd8a-27ed-47e2-b50d-e3000730e65e</ProjectGuid>
|
||||
<SelfContained>false</SelfContained>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace iFileProxy.Services
|
|||
public TaskAddState AddTask(HttpContext c)
|
||||
{
|
||||
string? clientIp = MasterHelper.GetClientIPAddr(c);
|
||||
string? t_url = c.Request.Query["url"].FirstOrDefault();
|
||||
string? t_url = c.Request.Query["url"].FirstOrDefault() ?? c.Request.Form["url"].FirstOrDefault();
|
||||
|
||||
if (_appConfig.DownloadOptions.MaxParallelTasks != 0 && runningTasks.Count >= _appConfig.DownloadOptions.MaxParallelTasks)
|
||||
return TaskAddState.ErrMaxParallelTasksLimit;
|
||||
|
|
|
@ -30,6 +30,10 @@
|
|||
"BlockedClientIP": [ // 禁止使用服务的客户端IP
|
||||
"127.0.0.1"
|
||||
],
|
||||
"DailyRequestLimitPerIP": 200 // 单个IP每日最大请求次数限制
|
||||
"DailyRequestLimitPerIP": 200, // 单个IP每日最大请求次数限制
|
||||
"RoutesToTrack": [ // 参加IP请求数统计的路径名单
|
||||
"/Download",
|
||||
"/AddOfflineTask"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,70 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<html lang="cn">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>离线下载任务管理</title>
|
||||
<title>Github文件下载加速</title>
|
||||
<!-- 引入 Bootstrap 5 CSS -->
|
||||
<link href="static/css/bootstarp/5/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
||||
|
||||
<style>
|
||||
body {
|
||||
padding: 20px;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
h1 {
|
||||
.more-content {
|
||||
text-align: center;
|
||||
color: #343a40;
|
||||
}
|
||||
.table-responsive {
|
||||
overflow-x: auto;
|
||||
}
|
||||
.table {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
background-color: transparent;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.table th, .table td {
|
||||
padding: 15px;
|
||||
vertical-align: middle;
|
||||
border: 1px solid #dee2e6;
|
||||
text-align: center;
|
||||
max-width: 90px; /* 设置最大宽度 */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.table thead th {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
.table tbody tr:nth-child(even) {
|
||||
background-color: #f1f3f5;
|
||||
}
|
||||
.table tbody tr:hover {
|
||||
background-color: #e2e6ea;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.table-responsive {
|
||||
overflow-x: auto;
|
||||
}
|
||||
.table th, .table td {
|
||||
padding: 10px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.hidden-on-small {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
@ -72,24 +21,36 @@
|
|||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-center">Github文件下载加速</h5>
|
||||
|
||||
<div class="container">
|
||||
<h1 class="text-center">离线下载任务管理</h1>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>文件名</th>
|
||||
<th class="hidden-on-small">大小</th>
|
||||
<th>提交时间</th>
|
||||
<th>状态</th>
|
||||
<th class="hidden-on-small">哈希</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="taskTableBody">
|
||||
<!-- 数据将在这里动态填充 -->
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- URL 输入框 -->
|
||||
<div class="mb-3">
|
||||
<label for="url_ipt" class="form-label">目标文件URL</label>
|
||||
<input type="text" required="required" class="form-control" id="url_ipt"
|
||||
placeholder="请输入要下载的文件链接">
|
||||
</div>
|
||||
|
||||
<!-- 验证码 输入框 -->
|
||||
<!-- <div class="mb-3 input-group">
|
||||
<input type="text" class="form-control" id="vcode" placeholder="交易验证码">
|
||||
<button type="button" id="send_vcode_btn" class="btn btn-primary">发送验证码</button>
|
||||
</div> -->
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<div class="d-grid gap-2">
|
||||
<button type="button" id="sub_btn" class="btn btn-primary">提交</button>
|
||||
</div>
|
||||
<br />
|
||||
<hr />
|
||||
<p class="more-content"><a href="/query_download_task.html">查询文件下载任务状态</a> | 捐赠</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -97,57 +58,25 @@
|
|||
<script src="static/js/bootstarp/5/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
|
||||
<script src="static/js/jquery/2.1.4/jquery.min.js"></script>
|
||||
<script>
|
||||
let statusStrDict = new Map([
|
||||
[0,"排队中"],
|
||||
[1,"进行中"],
|
||||
[2,"错误"],
|
||||
[3,"已完成"]
|
||||
])
|
||||
data = [];
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/GetMyTasks",
|
||||
dataType: "json",
|
||||
success: function (response) {
|
||||
if (response.retcode == 0) {
|
||||
data = response.data;
|
||||
populateTable(data);
|
||||
}
|
||||
|
||||
else
|
||||
alert(response.message);
|
||||
},
|
||||
error(xhr, status, error) {
|
||||
alert(error);
|
||||
}
|
||||
|
||||
});
|
||||
function formatBytes(bytes, decimals = 2) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const dm = decimals || 2;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
function populateTable(data) {
|
||||
const tableBody = document.getElementById('taskTableBody');
|
||||
tableBody.innerHTML = ''; // 清空现有内容
|
||||
|
||||
data.forEach(item => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td><a href="/Download/${item.tid}"> ${item.file_name}</td>
|
||||
<td class="hidden-on-small">${formatBytes(item.size)}</td>
|
||||
<td>${item.add_time}</td>
|
||||
<td>${statusStrDict.get(item.status)}</td>
|
||||
<td class="hidden-on-small">${item.hash || 'N/A'}</td>
|
||||
`;
|
||||
tableBody.appendChild(row);
|
||||
$(document).ready(function () {
|
||||
console.log("document ready")
|
||||
sub_btn.addEventListener('click', function (param) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/AddOfflineTask",
|
||||
data: {
|
||||
url: url_ipt.value
|
||||
},
|
||||
dataType: "json",
|
||||
success: function (response) {
|
||||
if (response.retcode == 0)
|
||||
alert("任务提交成功! 请稍后点击页面下方的 \"查询文件下载任务状态\" 超链接查询任务状态!");
|
||||
else
|
||||
alert(response.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
|
229
src/wwwroot/query_download_task.html
Normal file
229
src/wwwroot/query_download_task.html
Normal file
|
@ -0,0 +1,229 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>离线下载任务管理</title>
|
||||
<link href="static/css/bootstarp/5/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
||||
<style>
|
||||
body {
|
||||
padding: 20px;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: #343a40;
|
||||
}
|
||||
|
||||
.table-responsive {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
background-color: transparent;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.table th,
|
||||
.table td {
|
||||
padding: 15px;
|
||||
vertical-align: middle;
|
||||
border: 1px solid #dee2e6;
|
||||
text-align: center;
|
||||
max-width: 256px;
|
||||
/* 设置最大宽度 */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.table thead th {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.table tbody tr:nth-child(even) {
|
||||
background-color: #f1f3f5;
|
||||
}
|
||||
|
||||
.table tbody tr:hover {
|
||||
background-color: #e2e6ea;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.table-responsive {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.table th,
|
||||
.table td {
|
||||
padding: 10px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.hidden-on-small {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.more-content {
|
||||
text-align: center;
|
||||
|
||||
}
|
||||
|
||||
|
||||
#loading-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
border: 16px solid #f3f3f3;
|
||||
/* Light grey */
|
||||
border-top: 16px solid #3498db;
|
||||
/* Blue */
|
||||
border-radius: 50%;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<h1 class="text-center">离线下载任务管理</h1>
|
||||
<div class="table-responsive">
|
||||
<div id="loading-mask" style="display: none;">
|
||||
<div class="loading-spinner"></div>
|
||||
</div>
|
||||
<table id="data-table" class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>文件名</th>
|
||||
<th class="hidden-on-small">大小</th>
|
||||
<th>提交时间</th>
|
||||
<th>状态</th>
|
||||
<th class="hidden-on-small">哈希</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="taskTableBody">
|
||||
<!-- 数据将在这里动态填充 -->
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="more-content"><a href="/index.html">返回主页</a> | 捐赠</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 优先加载jq一类的三方库 -->
|
||||
<script src="static/js/bootstarp/5/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
|
||||
<script src="static/js/jquery/2.1.4/jquery.min.js"></script>
|
||||
<script>
|
||||
const $loadingMask = $('#loading-mask');
|
||||
const $dataTable = $('#data-table');
|
||||
|
||||
// 显示遮罩层
|
||||
function showLoadingMask() {
|
||||
$loadingMask.show();
|
||||
}
|
||||
|
||||
// 隐藏遮罩层
|
||||
function hideLoadingMask() {
|
||||
$loadingMask.hide();
|
||||
}
|
||||
|
||||
showLoadingMask();
|
||||
let statusStrDict = new Map([
|
||||
[0, "排队中"],
|
||||
[1, "进行中"],
|
||||
[2, "错误"],
|
||||
[3, "已完成"]
|
||||
])
|
||||
data = [];
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/GetMyTasks",
|
||||
dataType: "json",
|
||||
success: function (response) {
|
||||
hideLoadingMask();
|
||||
if (response.retcode == 0) {
|
||||
data = response.data;
|
||||
populateTable(data);
|
||||
}
|
||||
else
|
||||
alert(response.message);
|
||||
},
|
||||
error(xhr, status, error) {
|
||||
alert(error);
|
||||
}
|
||||
|
||||
});
|
||||
function formatBytes(bytes, decimals = 2) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const dm = decimals || 2;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
function populateTable(data) {
|
||||
const tableBody = document.getElementById('taskTableBody');
|
||||
tableBody.innerHTML = ''; // 清空现有内容
|
||||
|
||||
data.forEach(item => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>${item.status == 3 ? `<a href="/Download/${item.tid}">${item.file_name}</a>` : item.file_name}</td>
|
||||
<td class="hidden-on-small">${formatBytes(item.size)}</td>
|
||||
<td>${item.add_time}</td>
|
||||
<td>${statusStrDict.get(item.status)}</td>
|
||||
<td class="hidden-on-small">${item.hash || 'N/A'}</td>
|
||||
`;
|
||||
tableBody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
Reference in a new issue