const http = require('http');
const fs = require('fs');
const path = require('path');
const PORT = 8081;
const DIRECTORY = '/data/ai-output';
const server = http.createServer((req, res) => {
let pathname = req.url.split('?')[0];
// URL解码
try {
pathname = decodeURIComponent(pathname);
} catch (e) {
console.error('URL解码失败:', e.message);
}
const filePath = path.join(DIRECTORY, pathname);
console.log(`[${new Date().toLocaleString('zh-CN')}] ${req.method} ${pathname}`);
// 检查文件是否存在
if (!fs.existsSync(filePath)) {
res.writeHead(404, {'Content-Type': 'text/html; charset=utf-8'});
res.end('<h1>404 - 文件未找到</h1><p><a href="/">返回首页</a></p>');
return;
}
const stat = fs.statSync(filePath);
// 如果是目录,显示文件列表
if (stat.isDirectory()) {
const files = fs.readdirSync(filePath).map(f => {
const fstat = fs.statSync(path.join(filePath, f));
const icon = fstat.isDirectory() ? '📁' : '📄';
const size = fstat.isFile() ? `${(fstat.size / 1024).toFixed(1)} KB` : '';
const encodedName = encodeURIComponent(f);
const link = path.join(pathname, encodedName);
return `
<div style="padding: 15px; margin: 10px 0; background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); display: flex; justify-content: space-between; align-items: center;">
<div>
<span style="font-size: 24px;">${icon}</span>
<a href="${link}" style="color: #667eea; text-decoration: none; font-size: 16px; margin-left: 10px; font-weight: 500;">${f}</a>
</div>
<span style="color: #999; font-size: 14px;">${size}</span>
</div>
`;
}).join('');
const html = getHtmlTemplate('文件列表', `
<div style="background: white; padding: 20px; border-radius: 12px; margin-top: 20px;">
<h2 style="color: #2c3e50; margin-bottom: 20px;">📂 当前目录</h2>
${files}
</div>
`, pathname);
res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
res.end(html);
return;
}
// 读取文件内容
const content = fs.readFileSync(filePath, 'utf-8');
const ext = path.extname(filePath).toLowerCase();
// Markdown文件:渲染为HTML
if (ext === '.md') {
const html = getHtmlTemplate(path.basename(filePath), `
<div style="background: white; padding: 40px; border-radius: 12px; margin-top: 20px;">
<div class="markdown-body" id="content"></div>
</div>
`, pathname, content, path.basename(filePath));
res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
res.end(html);
} else {
// 其他文件:直接返回
const mimeTypes = {
'.txt': 'text/plain; charset=utf-8',
'.html': 'text/html; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.js': 'application/javascript; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.pdf': 'application/pdf'
};
const mimeType = mimeTypes[ext] || 'application/octet-stream';
res.writeHead(200, {
'Content-Type': mimeType,
'Content-Length': stat.size
});
res.end(content);
}
});
function getHtmlTemplate(title, bodyContent, currentPath, markdownContent = null, filename = null) {
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title}</title>
<!-- 引入 marked.js -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- 引入 highlight.js -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/styles/github-dark.min.css">
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/core.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/javascript.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/python.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/bash.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/languages/json.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", "Microsoft YaHei", sans-serif;
line-height: 1.8;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
background: white;
padding: 30px;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.header h1 {
color: #2c3e50;
font-size: 28px;
margin-bottom: 10px;
}
.breadcrumb {
color: #7f8c8d;
font-size: 14px;
margin-top: 10px;
}
.breadcrumb a {
color: #667eea;
text-decoration: none;
}
.breadcrumb a:hover {
text-decoration: underline;
}
/* Markdown 样式 */
.markdown-body h1 {
color: #2c3e50;
border-bottom: 3px solid #667eea;
padding-bottom: 15px;
margin: 30px 0 20px 0;
font-size: 32px;
}
.markdown-body h2 {
color: #34495e;
border-bottom: 2px solid #95a5a6;
padding-bottom: 10px;
margin: 25px 0 15px 0;
font-size: 26px;
}
.markdown-body h3 {
color: #7f8c8d;
margin: 20px 0 10px 0;
font-size: 22px;
}
.markdown-body h4 {
color: #95a5a6;
margin: 15px 0 10px 0;
font-size: 18px;
}
.markdown-body p {
margin: 15px 0;
line-height: 1.8;
}
.markdown-body table {
border-collapse: collapse;
width: 100%;
margin: 20px 0;
font-size: 14px;
}
.markdown-body th, .markdown-body td {
border: 1px solid #e0e0e0;
padding: 12px 15px;
text-align: left;
}
.markdown-body th {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
font-weight: 600;
}
.markdown-body tr:nth-child(even) { background-color: #f9f9f9; }
.markdown-body tr:hover { background-color: #f0f0ff; }
.markdown-body code {
background: #f4f4f4;
padding: 3px 8px;
border-radius: 4px;
font-size: 0.9em;
font-family: 'Courier New', Consolas, monospace;
color: #e74c3c;
}
.markdown-body pre {
background: #2c3e50;
color: #ecf0f1;
padding: 20px;
border-radius: 8px;
overflow-x: auto;
margin: 20px 0;
}
.markdown-body pre code {
background: none;
padding: 0;
color: #ecf0f1;
font-size: 14px;
}
.markdown-body blockquote {
border-left: 5px solid #667eea;
padding: 15px 20px;
margin: 20px 0;
color: #7f8c8d;
background: #f9f9f9;
border-radius: 0 8px 8px 0;
}
.markdown-body a {
color: #667eea;
text-decoration: none;
border-bottom: 1px dashed #667eea;
}
.markdown-body a:hover {
color: #764ba2;
border-bottom-style: solid;
}
.markdown-body hr {
border: none;
height: 2px;
background: linear-gradient(to right, transparent, #667eea, transparent);
margin: 30px 0;
}
.markdown-body ul, .markdown-body ol {
padding-left: 30px;
margin: 15px 0;
}
.markdown-body li {
margin: 8px 0;
line-height: 1.6;
}
.markdown-body img {
max-width: 100%;
border-radius: 8px;
margin: 15px 0;
}
.footer {
text-align: center;
padding: 20px;
color: white;
font-size: 14px;
margin-top: 20px;
}
@media (max-width: 768px) {
.header h1 { font-size: 24px; }
.markdown-body h1 { font-size: 24px; }
.markdown-body h2 { font-size: 20px; }
.markdown-body h3 { font-size: 18px; }
body { padding: 10px; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📄 ${title}</h1>
<div class="breadcrumb">
<a href="/">🏠 首页</a> / ${currentPath.split('/').filter(p => p).map(p => `<a href="${p}">${p}</a>`).join(' / ')}
</div>
</div>
${bodyContent}
<div class="footer">
由增强版文件服务器生成 • ${new Date().toLocaleString('zh-CN')} • 支持 Markdown 渲染
</div>
</div>
${markdownContent ? `
<script>
// 配置 marked
marked.setOptions({
breaks: true,
gfm: true,
highlight: function(code, lang) {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(code, { language: lang }).value;
}
return code;
}
});
// 渲染 Markdown
const content = ${JSON.stringify(markdownContent)};
document.getElementById('content').innerHTML = marked.parse(content);
</script>
` : ''}
</body>
</html>`;
}
server.listen(PORT, '0.0.0.0', () => {
console.log('✅ 增强版 Markdown 文件服务器运行中');
console.log(`📁 服务目录: ${DIRECTORY}`);
console.log(`🌐 访问地址: http://proxy.mossai.icu:${PORT}`);
console.log('🎨 功能: Markdown渲染、代码高亮、UTF-8编码、美观界面');
});