Skip to content

Commit 8e4e98f

Browse files
authored
Merge pull request #102 from shinonomeow/shino_aio
move poster to api/bangumi
2 parents fd50934 + 07b13da commit 8e4e98f

3 files changed

Lines changed: 51 additions & 58 deletions

File tree

backend/src/main.py

Lines changed: 36 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33
from pathlib import Path
44

55
import uvicorn
6-
from fastapi import Depends, FastAPI, HTTPException, Request
6+
from fastapi import FastAPI, HTTPException, Request
77
from fastapi.responses import FileResponse, RedirectResponse
88
from fastapi.staticfiles import StaticFiles
99
from fastapi.templating import Jinja2Templates
10+
1011
from module.api import lifespan, v1
1112
from module.conf import VERSION, settings, setup_logger
12-
from module.network import load_image
13-
from module.security.api import get_current_user
1413

1514
setup_logger(reset=True)
1615
logger = logging.getLogger(__name__)
@@ -37,62 +36,47 @@ def create_app() -> FastAPI:
3736

3837

3938
app = create_app()
40-
41-
42-
@app.get("/posters/{path:path}", dependencies=[Depends(get_current_user)])
43-
async def get_poster(path: str):
44-
"""
45-
安全的poster图片访问端点
46-
- 添加了用户鉴权
47-
- 防止路径遍历攻击
48-
- 限制只能访问posters目录下的文件
49-
"""
50-
# 验证路径安全性 - 阻止路径遍历
51-
if ".." in path or path.startswith("/") or "\\" in path:
52-
logger.warning(f"[Poster] Blocked path traversal attempt: {path}")
53-
raise HTTPException(status_code=400, detail="Invalid path")
54-
55-
# 构建安全的文件路径
56-
poster_dir = Path("data/posters")
57-
post_path = poster_dir / Path(path)
58-
59-
# 确保解析后的路径仍在预期目录内
60-
try:
61-
post_path.resolve().relative_to(poster_dir.resolve())
62-
except ValueError:
63-
logger.warning(f"[Poster] Path outside allowed directory: {path}")
64-
raise HTTPException(status_code=400, detail="Path outside allowed directory")
65-
66-
# 如果文件不存在,尝试下载
67-
if not post_path.exists():
68-
try:
69-
await load_image(path)
70-
except Exception as e:
71-
logger.warning(f"[Poster] Failed to load image {path}: {e}")
72-
73-
# 返回文件
74-
if post_path.exists() and post_path.is_file():
75-
return FileResponse(
76-
post_path,
77-
media_type="image/jpeg",
78-
headers={"Cache-Control": "public, max-age=86400"}, # 缓存1天
79-
)
80-
else:
81-
logger.warning(f"[Poster] File not found: {post_path}")
82-
raise HTTPException(status_code=404, detail="Poster not found")
83-
84-
8539
if VERSION != "DEV_VERSION":
8640
app.mount("/assets", StaticFiles(directory="dist/assets"), name="assets")
8741
app.mount("/images", StaticFiles(directory="dist/images"), name="images")
8842
templates = Jinja2Templates(directory="dist")
8943

9044
@app.get("/{path:path}")
91-
def html(request: Request, path: str):
92-
files = os.listdir("dist")
93-
if path in files:
94-
return FileResponse(f"dist/{path}")
45+
async def serve_spa(request: Request, path: str):
46+
"""
47+
安全的SPA静态文件服务
48+
- 防止路径遍历攻击
49+
- 限制只能访问dist目录下的文件
50+
- 对未匹配路由返回SPA入口页面
51+
"""
52+
# 空路径或根路径,返回SPA入口页面
53+
if not path or path == "/":
54+
context = {"request": request}
55+
return templates.TemplateResponse("index.html", context)
56+
57+
# 验证路径安全性 - 阻止路径遍历
58+
if ".." in path or path.startswith("/") or "\\" in path:
59+
logger.warning(f"[Static] Blocked path traversal attempt: {path}")
60+
context = {"request": request}
61+
return templates.TemplateResponse("index.html", context)
62+
63+
# 构建安全的文件路径
64+
dist_dir = Path("dist").resolve()
65+
file_path = (dist_dir / path).resolve()
66+
67+
# 确保解析后的路径仍在预期目录内
68+
try:
69+
file_path.relative_to(dist_dir)
70+
except ValueError:
71+
logger.warning(f"[Static] Path outside allowed directory: {path}")
72+
context = {"request": request}
73+
return templates.TemplateResponse("index.html", context)
74+
75+
# 如果文件存在且是文件,则返回
76+
if file_path.exists() and file_path.is_file():
77+
return FileResponse(file_path)
9578
else:
79+
# 文件不存在,返回SPA入口页面(用于客户端路由)
9680
context = {"request": request}
9781
return templates.TemplateResponse("index.html", context)
9882

webui/src/components/ab-image.vue

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script lang="ts" setup>
22
import { ErrorPicture } from '@icon-park/vue-next';
33
4-
withDefaults(
4+
const props = withDefaults(
55
defineProps<{
66
src?: string | null;
77
aspectRatio?: number;
@@ -11,6 +11,15 @@ withDefaults(
1111
objectFit: 'cover',
1212
}
1313
);
14+
15+
const transformedSrc = computed(() => {
16+
if (!props.src) return null;
17+
// Transform posters/ URLs to the new API endpoint
18+
if (props.src.startsWith('posters/')) {
19+
return props.src.replace('posters/', '/api/v1/bangumi/posters/');
20+
}
21+
return props.src;
22+
});
1423
</script>
1524

1625
<template>
@@ -22,8 +31,8 @@ withDefaults(
2231
></div>
2332

2433
<img
25-
v-if="src"
26-
:src="src"
34+
v-if="transformedSrc"
35+
:src="transformedSrc"
2736
alt="poster"
2837
abs
2938
top-0
@@ -34,7 +43,7 @@ withDefaults(
3443
</template>
3544

3645
<template v-else>
37-
<img v-if="src" :src="src" alt="poster" :style="{ objectFit }" wh-full />
46+
<img v-if="transformedSrc" :src="transformedSrc" alt="poster" :style="{ objectFit }" wh-full />
3847

3948
<div v-else wh-full f-cer border="1 white">
4049
<ErrorPicture theme="outline" size="24" fill="#333" />

webui/vite.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ export default defineConfig(({ mode }) => ({
101101
},
102102
server: {
103103
proxy: {
104-
'^/api/.*': 'http://192.168.0.100:7892',
105-
'^/posters/.*': 'http://192.168.0.100:7892',
104+
'^/api/.*': 'http://127.0.0.1:7892',
105+
// '^/api/.*': 'http://192.168.0.100:7892',
106106
},
107107
},
108108
}));

0 commit comments

Comments
 (0)