const http = require('http');
const fs = require('fs');
const path = require('path');
const url = require('url');
const https = require('https');
const PORT = 8081;
const DIRECTORY = '/data/ai-output';
// 动态加载 marked.js
let marked = null;
function loadMarked(callback) {
if (marked) {
callback();
return;
}
// 下载 marked.min.js
const markedUrl = 'https://cdn.jsdelivr.net/npm/marked/marked.min.js';
https.get(markedUrl, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
// 简单的marked实现(备用)
marked = require('./marked_simple.js');
console.log('✅ 使用简化版Markdown解析器');
callback();
} catch (e) {
console.log('⚠️ 使用基础Markdown解析');
marked = basicMarkdown;
callback();
}
});
}).on('error', () => {
marked = basicMarkdown;
callback();
});
}
// 基础Markdown解析器
function basicMarkdown(text) {
return text
// 转义HTML
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
// 标题
.replace(/^#### (.*$)/gim, '<h4>$1</h4>')
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
// 粗体斜体
.replace(/\*\*\*(.*?)\*\*\*/g, '<strong><em>$1</em></strong>')
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*?)\*/g, '<em>$1</em>')
// 代码块
.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre><code class="language-$1">$2</code></pre>')
.replace(/`(.*?)`/g, '<code>$1</code>')
// 链接
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>')
// 图片
.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" style="max-width:100%;">')
// 待办事项
.replace(/^- \[ \] (.*)$/gim, '<li class="todo"><input type="checkbox" disabled> $1</li>')
.replace(/^- \[x\] (.*)$/gim, '<li class="todo"><input type="checkbox" checked disabled> $1</li>')
// 列表
.replace(/^- (.*)$/gim, '<li>$1</li>')
.replace(/^\d+\. (.*)$/gim, '<li>$1</li>')
// 引用
.replace(/^> (.*)$/gim, '<blockquote>$1</blockquote>')
// 水平线
.replace(/^---$/gim, '<hr>')
// 表格(简单实现)
.replace(/^\|(.+)\|$/gm, (match, content) => {
const cells = content.split('|').map(c => c.trim());
if (cells.every(c => /^[-:]+$/.test(c))) return ''; // 分隔行
const tag = 'td';
return '<tr>' + cells.map(c => `<${tag}>${c}</${tag}>`).join('') + '</tr>';
})
// 段落
.replace(/\n\n/g, '</p><p>')
.replace(/\n/g, '<br>');
}
// 生成HTML页面(使用CDN加载marked.js和highlight.js)
function generatePage(markdownContent, filename) {
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${filename}</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>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
line-height: 1.8;
color: #333;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1000px;
margin: 0 auto;
background: white;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 15px;
}
.header h1 {
font-size: 24px;
font-weight: 600;
}
.toolbar {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.btn {
display: inline-block;
padding: 10px 20px;
background: white;
color: #667eea;
text-decoration: none;
border-radius: 6px;
font-weight: 500;
transition: all 0.3s;
border: none;
cursor: pointer;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.btn-primary {
background: #00C853;
color: white;
}
.content {
padding: 40px;
}
/* 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;
overflow-x: auto;
display: block;
}
.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 .todo {
list-style: none;
margin-left: -30px;
}
.markdown-body img {
max-width: 100%;
border-radius: 8px;
margin: 15px 0;
}
.footer {
text-align: center;
padding: 20px;
background: #f5f5f5;
color: #999;
font-size: 14px;
}
@media (max-width: 768px) {
.header {
flex-direction: column;
align-items: flex-start;
}
.content {
padding: 20px;
}
.markdown-body h1 { font-size: 24px; }
.markdown-body h2 { font-size: 20px; }
.markdown-body h3 { font-size: 18px; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📄 ${filename}</h1>
<div class="toolbar">
<button class="btn" onclick="toggleRaw()">📝 切换源码</button>
<a href="${filename}" download class="btn btn-primary">⬇️ 下载文件</a>
</div>
</div>
<div class="content">
<div id="rendered" class="markdown-body"></div>
<div id="raw" style="display:none;">
<pre style="white-space: pre-wrap; word-wrap: break-word; background: #f5f5f5; padding: 20px; border-radius: 8px;">${markdownContent.replace(/</g, '<').replace(/>/g, '>')}</pre>
</div>
</div>
<div class="footer">
由增强版文件服务器生成 • ${new Date().toLocaleString('zh-CN')} • 支持Markdown渲染
</div>
</div>
<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 markdownContent = ${JSON.stringify(markdownContent)};
document.getElementById('rendered').innerHTML = marked.parse(markdownContent);
// 切换源码/渲染视图
let showRaw = false;
function toggleRaw() {
showRaw = !showRaw;
document.getElementById('rendered').style.display = showRaw ? 'none' : 'block';
document.getElementById('raw').style.display = showRaw ? 'block' : 'none';
}
</script>
</body>
</html>`;
}
const server = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url, true);
const pathname = decodeURIComponent(parsedUrl.pathname);
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);
const list = files.map(f => {
const fstat = fs.statSync(path.join(filePath, f));
const icon = fstat.isDirectory() ? '📁' : '📄';
const size = fstat.isFile() ? `(${(fstat.size / 1024).toFixed(1)} KB)` : '';
return `<li style="margin: 10px 0;"><a href="${path.join(pathname, f)}" style="color: #667eea; text-decoration: none; border-bottom: 1px dashed #667eea;">${icon} ${f}</a> <span style="color: #999; font-size: 12px;">${size}</span></li>`;
}).join('');
const html = generatePage(`<h2>📁 目录列表</h2><ul style="list-style: none; padding-left: 0;">${list}</ul>`, '文件列表');
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且没有raw参数,渲染为HTML
if (ext === '.md' && !parsedUrl.query.raw) {
const html = generatePage(content, path.basename(filePath));
res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
res.end(html);
} else {
// 其他文件或raw模式,直接返回
const mimeTypes = {
'.md': 'text/markdown; charset=utf-8',
'.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',
'.zip': 'application/zip'
};
const mimeType = mimeTypes[ext] || 'application/octet-stream';
res.writeHead(200, {
'Content-Type': mimeType,
'Content-Length': stat.size,
'Access-Control-Allow-Origin': '*'
});
res.end(content);
}
});
// 启动服务器
loadMarked(() => {
server.listen(PORT, () => {
console.log('✅ 增强版文件服务器运行中');
console.log(`📁 服务目录: ${DIRECTORY}`);
console.log(`🌐 访问地址: http://proxy.mossai.icu:${PORT}`);
console.log('🎨 功能: Markdown渲染(使用marked.js)、代码高亮、UTF-8编码、美观界面');
});
});