Skip to content

Commit bf43727

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents d49a61d + 3074d9b commit bf43727

49 files changed

Lines changed: 1102 additions & 321 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/admin/services.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from apps.base.utils import get_expire_info, get_file_path_name
99
from fastapi import HTTPException
1010
from core.settings import data_root
11+
from core.utils import hash_password, is_password_hashed
1112

1213

1314
class FileService:
@@ -76,6 +77,9 @@ async def update_config(self, data: dict):
7677
if admin_token is None or admin_token == "":
7778
raise HTTPException(status_code=400, detail="管理员密码不能为空")
7879

80+
if not is_password_hashed(admin_token):
81+
data["admin_token"] = hash_password(admin_token)
82+
7983
for key, value in data.items():
8084
if key not in settings.default_config:
8185
continue

apps/admin/views.py

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,16 @@
1717
from apps.base.models import FileCodes, KeyValue
1818
from apps.admin.dependencies import create_token
1919
from core.settings import settings
20+
from core.utils import get_now, verify_password
2021

2122
admin_api = APIRouter(prefix="/admin", tags=["管理"])
2223

2324

2425
@admin_api.post("/login")
2526
async def login(data: LoginData):
26-
# 验证管理员密码
27-
if data.password != settings.admin_token:
27+
if not verify_password(data.password, settings.admin_token):
2828
raise HTTPException(status_code=401, detail="密码错误")
2929

30-
# 生成包含管理员身份的token
3130
token = create_token({"is_admin": True})
3231
return APIResponse(detail={"token": token, "token_type": "Bearer"})
3332

@@ -37,16 +36,13 @@ async def dashboard(admin: bool = Depends(admin_required)):
3736
all_codes = await FileCodes.all()
3837
all_size = str(sum([code.size for code in all_codes]))
3938
sys_start = await KeyValue.filter(key="sys_start").first()
40-
# 获取当前日期时间
41-
now = datetime.datetime.now()
39+
now = await get_now()
4240
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
4341
yesterday_start = today_start - datetime.timedelta(days=1)
4442
yesterday_end = today_start - datetime.timedelta(microseconds=1)
45-
# 统计昨天一整天的记录数(从昨天0点到23:59:59)
4643
yesterday_codes = FileCodes.filter(
4744
created_at__gte=yesterday_start, created_at__lte=yesterday_end
4845
)
49-
# 统计今天到现在的记录数(从今天0点到现在)
5046
today_codes = FileCodes.filter(created_at__gte=today_start)
5147
return APIResponse(
5248
detail={
@@ -63,21 +59,21 @@ async def dashboard(admin: bool = Depends(admin_required)):
6359

6460
@admin_api.delete("/file/delete")
6561
async def file_delete(
66-
data: IDData,
67-
file_service: FileService = Depends(get_file_service),
68-
admin: bool = Depends(admin_required),
62+
data: IDData,
63+
file_service: FileService = Depends(get_file_service),
64+
admin: bool = Depends(admin_required),
6965
):
7066
await file_service.delete_file(data.id)
7167
return APIResponse()
7268

7369

7470
@admin_api.get("/file/list")
7571
async def file_list(
76-
page: int = 1,
77-
size: int = 10,
78-
keyword: str = "",
79-
file_service: FileService = Depends(get_file_service),
80-
admin: bool = Depends(admin_required),
72+
page: int = 1,
73+
size: int = 10,
74+
keyword: str = "",
75+
file_service: FileService = Depends(get_file_service),
76+
admin: bool = Depends(admin_required),
8177
):
8278
files, total = await file_service.list_files(page, size, keyword)
8379
return APIResponse(
@@ -92,17 +88,17 @@ async def file_list(
9288

9389
@admin_api.get("/config/get")
9490
async def get_config(
95-
config_service: ConfigService = Depends(get_config_service),
96-
admin: bool = Depends(admin_required),
91+
config_service: ConfigService = Depends(get_config_service),
92+
admin: bool = Depends(admin_required),
9793
):
9894
return APIResponse(detail=config_service.get_config())
9995

10096

10197
@admin_api.patch("/config/update")
10298
async def update_config(
103-
data: dict,
104-
config_service: ConfigService = Depends(get_config_service),
105-
admin: bool = Depends(admin_required),
99+
data: dict,
100+
config_service: ConfigService = Depends(get_config_service),
101+
admin: bool = Depends(admin_required),
106102
):
107103
data.pop("themesChoices")
108104
await config_service.update_config(data)
@@ -111,47 +107,47 @@ async def update_config(
111107

112108
@admin_api.get("/file/download")
113109
async def file_download(
114-
id: int,
115-
file_service: FileService = Depends(get_file_service),
116-
admin: bool = Depends(admin_required),
110+
id: int,
111+
file_service: FileService = Depends(get_file_service),
112+
admin: bool = Depends(admin_required),
117113
):
118114
file_content = await file_service.download_file(id)
119115
return file_content
120116

121117

122118
@admin_api.get("/local/lists")
123119
async def get_local_lists(
124-
local_file_service: LocalFileService = Depends(get_local_file_service),
125-
admin: bool = Depends(admin_required),
120+
local_file_service: LocalFileService = Depends(get_local_file_service),
121+
admin: bool = Depends(admin_required),
126122
):
127123
files = await local_file_service.list_files()
128124
return APIResponse(detail=files)
129125

130126

131127
@admin_api.delete("/local/delete")
132128
async def delete_local_file(
133-
item: DeleteItem,
134-
local_file_service: LocalFileService = Depends(get_local_file_service),
135-
admin: bool = Depends(admin_required),
129+
item: DeleteItem,
130+
local_file_service: LocalFileService = Depends(get_local_file_service),
131+
admin: bool = Depends(admin_required),
136132
):
137133
result = await local_file_service.delete_file(item.filename)
138134
return APIResponse(detail=result)
139135

140136

141137
@admin_api.post("/local/share")
142138
async def share_local_file(
143-
item: ShareItem,
144-
file_service: FileService = Depends(get_file_service),
145-
admin: bool = Depends(admin_required),
139+
item: ShareItem,
140+
file_service: FileService = Depends(get_file_service),
141+
admin: bool = Depends(admin_required),
146142
):
147143
share_info = await file_service.share_local_file(item)
148144
return APIResponse(detail=share_info)
149145

150146

151147
@admin_api.patch("/file/update")
152148
async def update_file(
153-
data: UpdateFileData,
154-
admin: bool = Depends(admin_required),
149+
data: UpdateFileData,
150+
admin: bool = Depends(admin_required),
155151
):
156152
file_code = await FileCodes.filter(id=data.id).first()
157153
if not file_code:
@@ -167,7 +163,11 @@ async def update_file(
167163
update_data["prefix"] = data.prefix
168164
if data.suffix is not None and data.suffix != file_code.suffix:
169165
update_data["suffix"] = data.suffix
170-
if data.expired_at is not None and data.expired_at != "" and data.expired_at != file_code.expired_at:
166+
if (
167+
data.expired_at is not None
168+
and data.expired_at != ""
169+
and data.expired_at != file_code.expired_at
170+
):
171171
update_data["expired_at"] = data.expired_at
172172
if data.expired_count is not None and data.expired_count != file_code.expired_count:
173173
update_data["expired_count"] = data.expired_count
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from tortoise import connections
2+
3+
4+
async def add_save_path_to_uploadchunk():
5+
conn = connections.get("default")
6+
await conn.execute_script(
7+
"""
8+
ALTER TABLE uploadchunk ADD COLUMN save_path VARCHAR(512) NULL;
9+
"""
10+
)
11+
12+
13+
async def migrate():
14+
await add_save_path_to_uploadchunk()

apps/base/models.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class UploadChunk(models.Model):
4949
file_size = fields.BigIntField()
5050
chunk_size = fields.IntField()
5151
file_name = fields.CharField(max_length=255)
52+
save_path = fields.CharField(max_length=512, null=True)
5253
created_at = fields.DatetimeField(auto_now_add=True)
5354
completed = fields.BooleanField(default=False)
5455

@@ -66,6 +67,7 @@ class KeyValue(Model):
6667

6768
class PresignUploadSession(models.Model):
6869
"""预签名上传会话模型"""
70+
6971
id = fields.IntField(pk=True)
7072
upload_id = fields.CharField(max_length=36, unique=True, index=True)
7173
file_name = fields.CharField(max_length=255)
@@ -85,4 +87,6 @@ async def is_expired(self):
8587
file_codes_pydantic = pydantic_model_creator(FileCodes, name="FileCodes")
8688
upload_chunk_pydantic = pydantic_model_creator(UploadChunk, name="UploadChunk")
8789
key_value_pydantic = pydantic_model_creator(KeyValue, name="KeyValue")
88-
presign_upload_session_pydantic = pydantic_model_creator(PresignUploadSession, name="PresignUploadSession")
90+
presign_upload_session_pydantic = pydantic_model_creator(
91+
PresignUploadSession, name="PresignUploadSession"
92+
)

apps/base/utils.py

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,41 +10,44 @@
1010
from apps.base.dependencies import IPRateLimit
1111
from apps.base.models import FileCodes
1212
from core.settings import settings
13-
from core.utils import get_random_num, get_random_string, max_save_times_desc, sanitize_filename
13+
from core.utils import (
14+
get_random_num,
15+
get_random_string,
16+
max_save_times_desc,
17+
sanitize_filename,
18+
get_now,
19+
)
1420

1521

1622
async def get_file_path_name(file: UploadFile) -> Tuple[str, str, str, str, str]:
17-
"""获取文件路径和文件名"""
18-
today = datetime.datetime.now()
19-
storage_path = settings.storage_path.strip("/") # 移除开头和结尾的斜杠
23+
today = await get_now()
24+
storage_path = settings.storage_path.strip("/")
2025
file_uuid = uuid.uuid4().hex
21-
# 一些客户端对非ASCII字符会进行编码,需要解码
22-
filename = await sanitize_filename(unquote(file.filename))
23-
# 使用 UUID 作为子目录名
26+
filename = await sanitize_filename(unquote(file.filename or ""))
2427
base_path = f"share/data/{today.strftime('%Y/%m/%d')}/{file_uuid}"
25-
# 如果设置了存储路径,将其添加到基础路径中
2628
path = f"{storage_path}/{base_path}" if storage_path else base_path
2729
prefix, suffix = os.path.splitext(filename)
28-
# 保持原始文件名
2930
save_path = f"{path}/{filename}"
3031
return path, suffix, prefix, filename, save_path
3132

3233

33-
async def get_chunk_file_path_name(file_name: str, upload_id: str) -> Tuple[str, str, str, str, str]:
34-
"""获取切片文件的路径和文件名"""
35-
today = datetime.datetime.now()
36-
storage_path = settings.storage_path.strip("/") # 移除开头和结尾的斜杠
34+
async def get_chunk_file_path_name(
35+
file_name: str, upload_id: str
36+
) -> Tuple[str, str, str, str, str]:
37+
today = await get_now()
38+
storage_path = settings.storage_path.strip("/")
3739
base_path = f"share/data/{today.strftime('%Y/%m/%d')}/{upload_id}"
3840
path = f"{storage_path}/{base_path}" if storage_path else base_path
3941
prefix, suffix = os.path.splitext(file_name)
4042
save_path = f"{path}/{prefix}{suffix}"
4143
return path, suffix, prefix, file_name, save_path
4244

4345

44-
async def get_expire_info(expire_value: int, expire_style: str) -> Tuple[Optional[datetime.datetime], int, int, str]:
45-
"""获取过期信息"""
46+
async def get_expire_info(
47+
expire_value: int, expire_style: str
48+
) -> Tuple[Optional[datetime.datetime], int, int, str]:
4649
expired_count, used_count = -1, 0
47-
now = datetime.datetime.now()
50+
now = await get_now()
4851
code = None
4952

5053
max_timedelta = (
@@ -64,7 +67,7 @@ async def get_expire_info(expire_value: int, expire_style: str) -> Tuple[Optiona
6467
"hour": lambda: now + datetime.timedelta(hours=expire_value),
6568
"minute": lambda: now + datetime.timedelta(minutes=expire_value),
6669
"count": lambda: (now + datetime.timedelta(days=1), expire_value),
67-
"forever": lambda: (None, None), # 修改这里
70+
"forever": lambda: (None, None),
6871
}
6972

7073
if expire_style in expire_styles:
@@ -74,7 +77,7 @@ async def get_expire_info(expire_value: int, expire_style: str) -> Tuple[Optiona
7477
if expire_style == "count":
7578
expired_count = extra
7679
elif expire_style == "forever":
77-
code = await get_random_code(style="string") # 移动到这里
80+
code = await get_random_code(style="string")
7881
else:
7982
expired_at = result
8083
if expired_at and expired_at - now > max_timedelta:
@@ -88,12 +91,11 @@ async def get_expire_info(expire_value: int, expire_style: str) -> Tuple[Optiona
8891
return expired_at, expired_count, used_count, code
8992

9093

91-
async def get_random_code(style="num") -> str:
92-
"""获取随机字符串"""
94+
async def get_random_code(style: str = "num") -> str:
9395
while True:
9496
code = await get_random_num() if style == "num" else await get_random_string()
9597
if not await FileCodes.filter(code=code).exists():
96-
return code
98+
return str(code)
9799

98100

99101
async def calculate_file_hash(file: UploadFile, chunk_size=1024 * 1024) -> str:

0 commit comments

Comments
 (0)