Files
yuzhiran-dev 538de50bb1 注册支持用户名/手机号/邮箱 + 镜像站部署脚本 + AI 助手 Tool Calling 重构
- Prisma User 模型新增 username 字段(唯一索引)
- 注册先查重复再创建,返回友好中文提示(非 500)
- 登录支持用户名/手机号/邮箱三种方式
- 前端注册表单增加用户名输入框,预校验 2-20 位格式
- 新增 scripts/deploy.sh:一键构建并部署主站+镜像站+重启后端+重载 Nginx
- 镜像站 www.yuzhiran.com.cn Nginx 配置与主站同步
- AI 助手架构升级:用户端/管理后台均采用完整 Tool Calling 架构
- 新增 UserAiAssistantService(18 工具)+ AiAssistantController
- admin 助手新增 search + mark-all-notifications-read 工具
- 修复注册 500 错误:catch Prisma P2002 → BadRequestException
- Baidu Analytics Script 注入 root layout
2026-06-01 23:14:42 +08:00

93 lines
2.4 KiB
JavaScript
Executable File

const http = require('http');
const fs = require('fs');
const path = require('path');
const PORT = process.env.PORT || 3000;
const ROOT = path.join(__dirname, 'out');
const MIME = {
'.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',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.webp': 'image/webp',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ttf': 'font/ttf',
'.txt': 'text/plain; charset=utf-8',
'.xml': 'application/xml; charset=utf-8',
'.webmanifest': 'application/manifest+json',
'.map': 'application/octet-stream',
};
function resolvePath(url) {
const decoded = decodeURIComponent(url).split('?')[0];
if (decoded === '/') return path.join(ROOT, 'index.html');
const ext = path.extname(decoded);
if (ext) return path.join(ROOT, decoded);
const asHtml = path.join(ROOT, decoded + '.html');
if (fs.existsSync(asHtml)) return asHtml;
const asIndex = path.join(ROOT, decoded, 'index.html');
if (fs.existsSync(asIndex)) return asIndex;
return path.join(ROOT, decoded + '.html');
}
function sendFile(res, filePath, statusCode) {
const ext = path.extname(filePath);
const ct = MIME[ext] || 'application/octet-stream';
const isHtml = ext === '.html';
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(500);
res.end('Internal Server Error');
return;
}
res.writeHead(statusCode, {
'Content-Type': ct,
'Cache-Control': isHtml ? 'no-cache' : 'public, max-age=31536000, immutable',
});
res.end(data);
});
}
function serve(req, res) {
const filePath = path.normalize(resolvePath(req.url));
if (!filePath.startsWith(ROOT)) {
res.writeHead(403);
res.end('Forbidden');
return;
}
fs.access(filePath, fs.constants.F_OK, (err) => {
if (err) {
const four04 = path.join(ROOT, '404.html');
fs.access(four04, fs.constants.F_OK, (err2) => {
if (err2) {
sendFile(res, path.join(ROOT, 'index.html'), 200);
} else {
sendFile(res, four04, 404);
}
});
return;
}
sendFile(res, filePath, 200);
});
}
const server = http.createServer(serve);
server.listen(PORT, () => {
console.log(`Static server running at http://localhost:${PORT} (serving ${ROOT})`);
});