99i commited on
Commit
8c62aab
·
verified ·
1 Parent(s): 9805e10

Upload main.py

Browse files
Files changed (1) hide show
  1. main.py +330 -205
main.py CHANGED
@@ -1,206 +1,331 @@
1
- from fastapi import FastAPI, HTTPException
2
- from fastapi.staticfiles import StaticFiles
3
- from fastapi.responses import HTMLResponse
4
- from pydantic import BaseModel
5
- import markdown
6
- import aiofiles
7
- import os
8
- import uuid
9
- from datetime import datetime
10
- import json
11
-
12
- app = FastAPI(title="HTML/Markdown Preview API", version="1.0.0")
13
-
14
- # 挂载静态文件目录
15
- app.mount("/static", StaticFiles(directory="static"), name="static")
16
-
17
- class HTMLRequest(BaseModel):
18
- html_content: str
19
-
20
- class MarkdownRequest(BaseModel):
21
- markdown_content: str
22
-
23
- class PreviewResponse(BaseModel):
24
- url: str
25
- message: str
26
-
27
- def generate_filename(extension: str = ".html") -> str:
28
- """生成唯一的文件名"""
29
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
30
- unique_id = str(uuid.uuid4())[:8]
31
- return f"{timestamp}_{unique_id}{extension}"
32
-
33
- async def save_html_file(content: str, filename: str) -> str:
34
- """保存HTML内容到文件"""
35
- file_path = os.path.join("static", filename)
36
- async with aiofiles.open(file_path, 'w', encoding='utf-8') as f:
37
- await f.write(content)
38
- return filename
39
-
40
- def markdown_to_html(markdown_content: str) -> str:
41
- """将Markdown转换为HTML"""
42
- # 配置markdown扩展
43
- extensions = [
44
- 'markdown.extensions.extra',
45
- 'markdown.extensions.codehilite',
46
- 'markdown.extensions.toc',
47
- 'markdown.extensions.tables',
48
- 'markdown.extensions.fenced_code'
49
- ]
50
-
51
- # 转换markdown为HTML
52
- html_content = markdown.markdown(markdown_content, extensions=extensions)
53
-
54
- # 包装在完整的HTML文档中
55
- full_html = f"""
56
- <!DOCTYPE html>
57
- <html lang="zh-CN">
58
- <head>
59
- <meta charset="UTF-8">
60
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
61
- <title>Markdown Preview</title>
62
- <style>
63
- body {{
64
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
65
- line-height: 1.6;
66
- max-width: 800px;
67
- margin: 0 auto;
68
- padding: 20px;
69
- color: #333;
70
- }}
71
- h1, h2, h3, h4, h5, h6 {{
72
- margin-top: 1.5em;
73
- margin-bottom: 0.5em;
74
- }}
75
- code {{
76
- background-color: #f4f4f4;
77
- padding: 2px 4px;
78
- border-radius: 3px;
79
- font-family: 'Consolas', 'Monaco', monospace;
80
- }}
81
- pre {{
82
- background-color: #f4f4f4;
83
- padding: 10px;
84
- border-radius: 5px;
85
- overflow-x: auto;
86
- }}
87
- pre code {{
88
- background-color: transparent;
89
- padding: 0;
90
- }}
91
- blockquote {{
92
- border-left: 4px solid #ddd;
93
- margin: 0;
94
- padding-left: 20px;
95
- color: #666;
96
- }}
97
- table {{
98
- border-collapse: collapse;
99
- width: 100%;
100
- margin: 1em 0;
101
- }}
102
- th, td {{
103
- border: 1px solid #ddd;
104
- padding: 8px;
105
- text-align: left;
106
- }}
107
- th {{
108
- background-color: #f2f2f2;
109
- }}
110
- img {{
111
- max-width: 100%;
112
- height: auto;
113
- }}
114
- </style>
115
- </head>
116
- <body>
117
- {html_content}
118
- </body>
119
- </html>
120
- """
121
- return full_html
122
-
123
- @app.post("/api/html/preview", response_model=PreviewResponse)
124
- async def preview_html(request: HTMLRequest):
125
- """
126
- 接收HTML代码,返回在线访问链接
127
- """
128
- try:
129
- # 生成唯一文件名
130
- filename = generate_filename(".html")
131
-
132
- # 保存HTML文件
133
- await save_html_file(request.html_content, filename)
134
-
135
- # 构建访问URL
136
- url = f"/static/{filename}"
137
-
138
- return PreviewResponse(
139
- url=url,
140
- message=f"HTML预览已创建,可通过链接访问"
141
- )
142
-
143
- except Exception as e:
144
- raise HTTPException(status_code=500, detail=f"创建HTML预览失败: {str(e)}")
145
-
146
- @app.post("/api/markdown/preview", response_model=PreviewResponse)
147
- async def preview_markdown(request: MarkdownRequest):
148
- """
149
- 接收Markdown代码,返回渲染后的HTML在线访问链接
150
- """
151
- try:
152
- # 将Markdown转换为HTML
153
- html_content = markdown_to_html(request.markdown_content)
154
-
155
- # 生成唯一文件名
156
- filename = generate_filename(".html")
157
-
158
- # 保存HTML文件
159
- await save_html_file(html_content, filename)
160
-
161
- # 构建访问URL
162
- url = f"/static/{filename}"
163
-
164
- return PreviewResponse(
165
- url=url,
166
- message=f"Markdown预览已创建,可通过链接访问"
167
- )
168
-
169
- except Exception as e:
170
- raise HTTPException(status_code=500, detail=f"创建Markdown预览失败: {str(e)}")
171
-
172
- @app.get("/")
173
- async def root():
174
- """
175
- API根路径,返回使用说明
176
- """
177
- return {
178
- "message": "HTML/Markdown Preview API",
179
- "version": "1.0.0",
180
- "endpoints": {
181
- "html_preview": {
182
- "url": "/api/html/preview",
183
- "method": "POST",
184
- "description": "接收HTML代码,返回在线访问链接",
185
- "request_body": {
186
- "html_content": "string - HTML代码内容"
187
- }
188
- },
189
- "markdown_preview": {
190
- "url": "/api/markdown/preview",
191
- "method": "POST",
192
- "description": "接收Markdown代码,返回渲染后的HTML在线访问链接",
193
- "request_body": {
194
- "markdown_content": "string - Markdown代码内容"
195
- }
196
- }
197
- },
198
- "example_usage": {
199
- "html": "curl -X POST http://localhost:8000/api/html/preview -H 'Content-Type: application/json' -d '{\"html_content\": \"<h1>Hello World</h1>\"}'",
200
- "markdown": "curl -X POST http://localhost:8000/api/markdown/preview -H 'Content-Type: application/json' -d '{\"markdown_content\": \"# Hello World\"}'"
201
- }
202
- }
203
-
204
- if __name__ == "__main__":
205
- import uvicorn
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from fastapi.staticfiles import StaticFiles
3
+ from fastapi.responses import HTMLResponse
4
+ from pydantic import BaseModel
5
+ import markdown
6
+ import aiofiles
7
+ import os
8
+ import uuid
9
+ from datetime import datetime
10
+ import json
11
+ import re
12
+ from typing import List, Optional
13
+
14
+ app = FastAPI(title="HTML/Markdown Preview API", version="1.0.0")
15
+
16
+ # 挂载静态文件目录
17
+ app.mount("/static", StaticFiles(directory="static"), name="static")
18
+
19
+ class HTMLRequest(BaseModel):
20
+ html_content: str
21
+
22
+ class MarkdownRequest(BaseModel):
23
+ markdown_content: str
24
+
25
+ class PreviewResponse(BaseModel):
26
+ url: str
27
+ message: str
28
+
29
+ class FileInfo(BaseModel):
30
+ filename: str
31
+ title: Optional[str] = None
32
+ url: str
33
+ created_time: str
34
+
35
+ class FileListResponse(BaseModel):
36
+ files: List[FileInfo]
37
+ total: int
38
+
39
+ def generate_filename(extension: str = ".html") -> str:
40
+ """生成唯一的文件名"""
41
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
42
+ unique_id = str(uuid.uuid4())[:8]
43
+ return f"{timestamp}_{unique_id}{extension}"
44
+
45
+ async def save_html_file(content: str, filename: str) -> str:
46
+ """保存HTML内容到文件"""
47
+ file_path = os.path.join("static", filename)
48
+ async with aiofiles.open(file_path, 'w', encoding='utf-8') as f:
49
+ await f.write(content)
50
+ return filename
51
+
52
+ def extract_title_from_html(html_content: str) -> Optional[str]:
53
+ """从HTML内容中提取标题"""
54
+ # 尝试从title标签提取
55
+ title_match = re.search(r'<title[^>]*>(.*?)</title>', html_content, re.IGNORECASE | re.DOTALL)
56
+ if title_match:
57
+ title = title_match.group(1).strip()
58
+ if title:
59
+ return title
60
+
61
+ # 尝试从h1标签提取
62
+ h1_match = re.search(r'<h1[^>]*>(.*?)</h1>', html_content, re.IGNORECASE | re.DOTALL)
63
+ if h1_match:
64
+ title = h1_match.group(1).strip()
65
+ if title:
66
+ return title
67
+
68
+ return None
69
+
70
+ async def get_html_files_info() -> List[FileInfo]:
71
+ """获取static目录中所有HTML文件的信息"""
72
+ files_info = []
73
+ static_dir = "static"
74
+
75
+ if not os.path.exists(static_dir):
76
+ return files_info
77
+
78
+ for filename in os.listdir(static_dir):
79
+ if filename.endswith('.html'):
80
+ file_path = os.path.join(static_dir, filename)
81
+
82
+ # 获取文件创建时间
83
+ stat = os.stat(file_path)
84
+ created_time = datetime.fromtimestamp(stat.st_ctime).strftime("%Y-%m-%d %H:%M:%S")
85
+
86
+ # 尝试提取标题
87
+ try:
88
+ async with aiofiles.open(file_path, 'r', encoding='utf-8') as f:
89
+ content = await f.read()
90
+ title = extract_title_from_html(content)
91
+ except:
92
+ title = None
93
+
94
+ files_info.append(FileInfo(
95
+ filename=filename,
96
+ title=title,
97
+ url=f"/static/{filename}",
98
+ created_time=created_time
99
+ ))
100
+
101
+ # 按创建时间倒序排列
102
+ files_info.sort(key=lambda x: x.created_time, reverse=True)
103
+ return files_info
104
+
105
+ def markdown_to_html(markdown_content: str) -> str:
106
+ """将Markdown转换为HTML"""
107
+ # 配置markdown扩展
108
+ extensions = [
109
+ 'markdown.extensions.extra',
110
+ 'markdown.extensions.codehilite',
111
+ 'markdown.extensions.toc',
112
+ 'markdown.extensions.tables',
113
+ 'markdown.extensions.fenced_code'
114
+ ]
115
+
116
+ # 转换markdown为HTML
117
+ html_content = markdown.markdown(markdown_content, extensions=extensions)
118
+
119
+ # 包装在完整的HTML文档中
120
+ full_html = f"""
121
+ <!DOCTYPE html>
122
+ <html lang="zh-CN">
123
+ <head>
124
+ <meta charset="UTF-8">
125
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
126
+ <title>Markdown Preview</title>
127
+ <style>
128
+ body {{
129
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
130
+ line-height: 1.6;
131
+ max-width: 800px;
132
+ margin: 0 auto;
133
+ padding: 20px;
134
+ color: #333;
135
+ }}
136
+ h1, h2, h3, h4, h5, h6 {{
137
+ margin-top: 1.5em;
138
+ margin-bottom: 0.5em;
139
+ }}
140
+ code {{
141
+ background-color: #f4f4f4;
142
+ padding: 2px 4px;
143
+ border-radius: 3px;
144
+ font-family: 'Consolas', 'Monaco', monospace;
145
+ }}
146
+ pre {{
147
+ background-color: #f4f4f4;
148
+ padding: 10px;
149
+ border-radius: 5px;
150
+ overflow-x: auto;
151
+ }}
152
+ pre code {{
153
+ background-color: transparent;
154
+ padding: 0;
155
+ }}
156
+ blockquote {{
157
+ border-left: 4px solid #ddd;
158
+ margin: 0;
159
+ padding-left: 20px;
160
+ color: #666;
161
+ }}
162
+ table {{
163
+ border-collapse: collapse;
164
+ width: 100%;
165
+ margin: 1em 0;
166
+ }}
167
+ th, td {{
168
+ border: 1px solid #ddd;
169
+ padding: 8px;
170
+ text-align: left;
171
+ }}
172
+ th {{
173
+ background-color: #f2f2f2;
174
+ }}
175
+ img {{
176
+ max-width: 100%;
177
+ height: auto;
178
+ }}
179
+ </style>
180
+ </head>
181
+ <body>
182
+ {html_content}
183
+ </body>
184
+ </html>
185
+ """
186
+ return full_html
187
+
188
+ @app.post("/api/html/preview", response_model=PreviewResponse)
189
+ async def preview_html(request: HTMLRequest):
190
+ """
191
+ 接收HTML代码,返回在线访问链接
192
+ """
193
+ try:
194
+ # 生成唯一文件名
195
+ filename = generate_filename(".html")
196
+
197
+ # 保存HTML文件
198
+ await save_html_file(request.html_content, filename)
199
+
200
+ # 构建访问URL
201
+ url = f"/static/{filename}"
202
+
203
+ return PreviewResponse(
204
+ url=url,
205
+ message=f"HTML预览已创建,可通过链接访问"
206
+ )
207
+
208
+ except Exception as e:
209
+ raise HTTPException(status_code=500, detail=f"创建HTML预览失败: {str(e)}")
210
+
211
+ @app.post("/api/markdown/preview", response_model=PreviewResponse)
212
+ async def preview_markdown(request: MarkdownRequest):
213
+ """
214
+ 接收Markdown代码,返回渲染后的HTML在线访问链接
215
+ """
216
+ try:
217
+ # 将Markdown转换为HTML
218
+ html_content = markdown_to_html(request.markdown_content)
219
+
220
+ # 生成唯一文件名
221
+ filename = generate_filename(".html")
222
+
223
+ # 保存HTML文件
224
+ await save_html_file(html_content, filename)
225
+
226
+ # 构建访问URL
227
+ url = f"/static/{filename}"
228
+
229
+ return PreviewResponse(
230
+ url=url,
231
+ message=f"Markdown预览已创建,可通过链接访问"
232
+ )
233
+
234
+ except Exception as e:
235
+ raise HTTPException(status_code=500, detail=f"创建Markdown预览失败: {str(e)}")
236
+
237
+ @app.get("/api/files", response_model=FileListResponse)
238
+ async def get_files():
239
+ """
240
+ 获取所有HTML文件列表
241
+ """
242
+ try:
243
+ files = await get_html_files_info()
244
+ return FileListResponse(
245
+ files=files,
246
+ total=len(files)
247
+ )
248
+ except Exception as e:
249
+ raise HTTPException(status_code=500, detail=f"获取文件列表失败: {str(e)}")
250
+
251
+ @app.delete("/api/files/{filename}")
252
+ async def delete_file(filename: str):
253
+ """
254
+ 删除指定的HTML文件
255
+ """
256
+ try:
257
+ # 安全检查:确保文件名不包含路径遍历字符
258
+ if '..' in filename or '/' in filename or '\\' in filename:
259
+ raise HTTPException(status_code=400, detail="无效的文件名")
260
+
261
+ file_path = os.path.join("static", filename)
262
+
263
+ if not os.path.exists(file_path):
264
+ raise HTTPException(status_code=404, detail="文件不存在")
265
+
266
+ os.remove(file_path)
267
+
268
+ return {"message": f"文件 {filename} 已删除"}
269
+
270
+ except HTTPException:
271
+ raise
272
+ except Exception as e:
273
+ raise HTTPException(status_code=500, detail=f"删除文件失败: {str(e)}")
274
+
275
+ @app.get("/", response_class=HTMLResponse)
276
+ async def root():
277
+ """
278
+ 返回前端管理页面
279
+ """
280
+ try:
281
+ async with aiofiles.open("templates/index.html", 'r', encoding='utf-8') as f:
282
+ content = await f.read()
283
+ return HTMLResponse(content=content)
284
+ except Exception as e:
285
+ return HTMLResponse(content=f"<h1>错误</h1><p>无法加载页面: {str(e)}</p>")
286
+
287
+ @app.get("/api/docs")
288
+ async def api_docs():
289
+ """
290
+ API文档,返回使用说明
291
+ """
292
+ return {
293
+ "message": "HTML/Markdown Preview API",
294
+ "version": "1.0.0",
295
+ "endpoints": {
296
+ "html_preview": {
297
+ "url": "/api/html/preview",
298
+ "method": "POST",
299
+ "description": "接收HTML代码,返回在线访问链接",
300
+ "request_body": {
301
+ "html_content": "string - HTML代码内容"
302
+ }
303
+ },
304
+ "markdown_preview": {
305
+ "url": "/api/markdown/preview",
306
+ "method": "POST",
307
+ "description": "接收Markdown代码,返回渲染后的HTML在线访问链接",
308
+ "request_body": {
309
+ "markdown_content": "string - Markdown代码内容"
310
+ }
311
+ },
312
+ "files_list": {
313
+ "url": "/api/files",
314
+ "method": "GET",
315
+ "description": "获取所有HTML文件列表"
316
+ },
317
+ "delete_file": {
318
+ "url": "/api/files/{filename}",
319
+ "method": "DELETE",
320
+ "description": "删除指定的HTML文件"
321
+ }
322
+ },
323
+ "example_usage": {
324
+ "html": "curl -X POST http://localhost:8000/api/html/preview -H 'Content-Type: application/json' -d '{\"html_content\": \"<h1>Hello World</h1>\"}'",
325
+ "markdown": "curl -X POST http://localhost:8000/api/markdown/preview -H 'Content-Type: application/json' -d '{\"markdown_content\": \"# Hello World\"}'"
326
+ }
327
+ }
328
+
329
+ if __name__ == "__main__":
330
+ import uvicorn
331
  uvicorn.run(app, host="0.0.0.0", port=7860)