| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714 |
- // PNG 序列动画预览工具 - 服务器主文件
- // 负责启动 HTTP 服务器和静态文件服务
- const http = require('http');
- const fs = require('fs');
- const path = require('path');
- const url = require('url');
- const TextureReader = require('./texture-reader');
- const ZipHandler = require('./zip');
- const DiskManager = require('./disk');
- const ReplaceCharacterHandler = require('./replace-character');
- const RemoveBackgroundBase64 = require('./remove-background-base64');
- const { handleLoginRequest, handleCheckPhoneRequest } = require('./login');
- const { handleRegisterRequest } = require('./register');
- const PORT = 3000;
- const SERVER_DIR = __dirname; // Server 目录
- const CLIENT_DIR = path.join(__dirname, '..', 'Client'); // Client 目录
- const ADMIN_DIR = path.join(__dirname, '..', 'admin'); // Admin 目录
- // MIME 类型映射
- const mimeTypes = {
- '.html': 'text/html',
- '.js': 'text/javascript',
- '.css': 'text/css',
- '.png': 'image/png',
- '.jpg': 'image/jpeg',
- '.gif': 'image/gif',
- '.json': 'application/json',
- };
- // 初始化 TextureReader
- const textureReader = new TextureReader(SERVER_DIR);
- // 初始化 DiskManager
- const diskManager = new DiskManager();
- // 初始化 StoreManager
- const StoreManager = require('./store/store');
- const storeManager = new StoreManager();
- // 初始化 Pay 模块
- const { handlePurchaseRequest, handleGetPurchaseHistory } = require('./pay');
- // 初始化 WebSocket 服务器(用于 matting-server 连接)
- require('./socket-connecting');
- // 创建 HTTP 服务器
- const server = http.createServer((req, res) => {
- const parsedUrl = url.parse(req.url, true);
- let pathname = parsedUrl.pathname;
- // 添加 CORS 头(允许 file:// 协议访问)
- res.setHeader('Access-Control-Allow-Origin', '*');
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
-
- // 处理 OPTIONS 预检请求
- if (req.method === 'OPTIONS') {
- res.writeHead(200);
- res.end();
- return;
- }
- // 调试日志:记录所有API请求
- if (pathname.startsWith('/api/')) {
- console.log(`[Server] ${req.method} ${pathname}`);
- }
- // API: 获取可用的文件夹列表
- if (pathname === '/api/folders') {
- textureReader.handleGetFolders(res);
- return;
- }
- // API: 获取指定文件夹中的帧文件列表
- if (pathname.startsWith('/api/frames/')) {
- const parsedUrl = url.parse(req.url, true);
- const encodedPath = pathname.replace('/api/frames/', '');
- // 对路径的每一段进行解码
- const folderName = encodedPath.split('/').map(seg => decodeURIComponent(seg)).join('/');
- const username = parsedUrl.query.username; // 从查询参数获取用户名
-
- console.log('[Server] API请求帧列表, 原始:', encodedPath, '解码后:', folderName, '用户名:', username);
-
- // 如果有用户名,尝试从用户网盘查找
- if (username) {
- const { handleGetUserFrames } = require('./disk');
- diskManager.handleGetUserFrames(username, folderName, res);
- } else {
- // 回退到旧的 TextureReader(兼容旧代码)
- textureReader.handleGetFrames(folderName, res);
- }
- return;
- }
- // API: 打包 sprite sheet 为 ZIP
- if (pathname === '/api/pack') {
- ZipHandler.handlePackRequest(req, res);
- return;
- }
- // API: 角色替换(图生图)- 旧版本,保留兼容
- if (pathname === '/api/replace-character') {
- ReplaceCharacterHandler.handleReplaceRequest(req, res);
- return;
- }
- // API: AI生图(队列版本)
- if (pathname === '/api/ai/generate') {
- const { handleAIRequest } = require('./ai-queue');
- handleAIRequest(req, res);
- return;
- }
- // API: AI生图重试(免费)
- if (pathname === '/api/ai/retry' && req.method === 'POST') {
- const { handleRetryRequest } = require('./ai-queue');
- handleRetryRequest(req, res);
- return;
- }
- // API: 获取AI生成的图片
- if (pathname === '/api/ai/image') {
- const parsedUrl = require('url').parse(req.url, true);
- const { username, id } = parsedUrl.query;
-
- if (!username || !id) {
- res.writeHead(400, { 'Content-Type': 'text/plain' });
- res.end('Missing parameters');
- return;
- }
-
- try {
- const fs = require('fs');
- const path = require('path');
- const usersDir = path.join(__dirname, 'users');
- const userDir = path.join(usersDir, username.toLowerCase());
- const aiDir = path.join(userDir, 'ai-images');
- const imagePath = path.join(aiDir, `${id}.png`);
-
- // 安全检查
- const normalizedPath = path.normalize(imagePath);
- const normalizedAiDir = path.normalize(aiDir);
- if (!normalizedPath.startsWith(normalizedAiDir)) {
- res.writeHead(403, { 'Content-Type': 'text/plain' });
- res.end('Access denied');
- return;
- }
-
- if (fs.existsSync(imagePath)) {
- const imageData = fs.readFileSync(imagePath);
- res.writeHead(200, {
- 'Content-Type': 'image/png',
- 'Access-Control-Allow-Origin': '*',
- 'Cache-Control': 'public, max-age=3600'
- });
- res.end(imageData);
- } else {
- res.writeHead(404, { 'Content-Type': 'text/plain' });
- res.end('Image not found');
- }
- } catch (error) {
- console.error('[Server] 获取AI图片失败:', error);
- res.writeHead(500, { 'Content-Type': 'text/plain' });
- res.end('Internal server error');
- }
- return;
- }
- // API: 获取AI生成预览图(原始texture)
- if (pathname === '/api/ai/preview') {
- const parsedUrl = require('url').parse(req.url, true);
- const { username, id } = parsedUrl.query;
-
- if (!username || !id) {
- res.writeHead(400, { 'Content-Type': 'text/plain' });
- res.end('Missing parameters');
- return;
- }
-
- try {
- const fs = require('fs');
- const path = require('path');
- const usersDir = path.join(__dirname, 'users');
- const userDir = path.join(usersDir, username.toLowerCase());
- const aiDir = path.join(userDir, 'ai-images');
- const imagePath = path.join(aiDir, `${id}_preview.png`);
-
- // 安全检查
- const normalizedPath = path.normalize(imagePath);
- const normalizedAiDir = path.normalize(aiDir);
- if (!normalizedPath.startsWith(normalizedAiDir)) {
- res.writeHead(403, { 'Content-Type': 'text/plain' });
- res.end('Access denied');
- return;
- }
-
- if (fs.existsSync(imagePath)) {
- const imageData = fs.readFileSync(imagePath);
- res.writeHead(200, {
- 'Content-Type': 'image/png',
- 'Access-Control-Allow-Origin': '*',
- 'Cache-Control': 'public, max-age=3600'
- });
- res.end(imageData);
- } else {
- res.writeHead(404, { 'Content-Type': 'text/plain' });
- res.end('Preview not found');
- }
- } catch (error) {
- console.error('[Server] 获取AI预览图失败:', error);
- res.writeHead(500, { 'Content-Type': 'text/plain' });
- res.end('Internal server error');
- }
- return;
- }
- // API: Base64 图片抠图
- if (pathname === '/api/remove-background-base64') {
- RemoveBackgroundBase64.handleRequest(req, res);
- return;
- }
- // API: 普通抠图(使用 image-matting.py)
- if (pathname === '/api/matting-normal') {
- const MattingNormal = require('./matting-normal');
- MattingNormal.handleRequest(req, res);
- return;
- }
- // API: VIP抠图(使用 BiRefNet)
- if (pathname === '/api/matting-vip') {
- const MattingVIP = require('./matting-vip');
- MattingVIP.handleRequest(req, res);
- return;
- }
- // API: VIP抠图队列(异步处理)
- if (pathname === '/api/vip-matting/queue') {
- const VIPMatting = require('./vip-matting');
- VIPMatting.handleQueueRequest(req, res);
- return;
- }
- // API: 网盘 - 获取文件列表
- if (pathname === '/api/disk/list') {
- diskManager.handleListRequest(req, res);
- return;
- }
- // API: 网盘 - 上传文件
- if (pathname === '/api/disk/upload') {
- diskManager.handleUploadRequest(req, res);
- return;
- }
- // API: 网盘 - 创建文件夹
- if (pathname === '/api/disk/create-folder') {
- diskManager.handleCreateFolderRequest(req, res);
- return;
- }
- // API: 网盘 - 下载文件
- if (pathname === '/api/disk/download') {
- diskManager.handleDownloadRequest(req, res);
- return;
- }
- // API: 网盘 - 重命名
- if (pathname === '/api/disk/rename') {
- diskManager.handleRenameRequest(req, res);
- return;
- }
- // API: 网盘 - 图片预览
- if (pathname === '/api/disk/preview') {
- diskManager.handlePreviewRequest(req, res);
- return;
- }
- // API: 网盘 - 移动文件/文件夹
- if (pathname === '/api/disk/move') {
- diskManager.handleMoveRequest(req, res);
- return;
- }
- // API: 网盘 - 复制文件/文件夹
- if (pathname === '/api/disk/copy') {
- diskManager.handleCopyRequest(req, res);
- return;
- }
- // API: 网盘 - 删除文件/文件夹
- if (pathname === '/api/disk/delete') {
- diskManager.handleDeleteRequest(req, res);
- return;
- }
- // API: 网盘 - 一键抠背景
- if (pathname === '/api/disk/remove-background') {
- diskManager.handleRemoveBackgroundRequest(req, res);
- return;
- }
- // API: 网盘 - 剪裁最小区域
- if (pathname === '/api/disk/crop-mini') {
- diskManager.handleCropMiniRequest(req, res);
- return;
- }
- // API: 用户注册
- if (pathname === '/api/register') {
- handleRegisterRequest(req, res);
- return;
- }
- // API: 用户登录
- if (pathname === '/api/login') {
- handleLoginRequest(req, res);
- return;
- }
- // API: 检查手机号是否存在
- if (pathname === '/api/check-phone') {
- handleCheckPhoneRequest(req, res);
- return;
- }
- // API: 获取用户点数
- if (pathname === '/api/user/points') {
- const { handleGetUserPoints } = require('./user');
- handleGetUserPoints(req, res);
- return;
- }
- // API: 扣除用户点数
- if (pathname === '/api/user/deduct-points' && req.method === 'POST') {
- const { handleDeductUserPoints } = require('./user');
- handleDeductUserPoints(req, res);
- return;
- }
- // API: 获取用户信息
- if (pathname === '/api/user/info') {
- const { handleGetUserInfo } = require('./user');
- handleGetUserInfo(req, res);
- return;
- }
- // API: 更新用户信息
- if (pathname === '/api/user/update') {
- const { handleUpdateUser } = require('./user');
- handleUpdateUser(req, res);
- return;
- }
- // API: 上传头像
- if (pathname === '/api/user/avatar') {
- const { handleUploadAvatar } = require('./user');
- handleUploadAvatar(req, res);
- return;
- }
- // API: 获取AI生图历史
- if (pathname === '/api/ai/history') {
- const { handleGetAIHistory } = require('./user');
- handleGetAIHistory(req, res);
- return;
- }
- // API: 充值
- if (pathname === '/api/recharge') {
- const { handleRecharge } = require('./user');
- handleRecharge(req, res);
- return;
- }
- // API: 管理后台 - 获取所有用户
- if (pathname === '/api/admin/users') {
- const { handleGetAllUsers } = require('./admin');
- handleGetAllUsers(req, res);
- return;
- }
- // API: 管理后台 - 更新用户
- if (pathname === '/api/admin/users/update') {
- const { handleAdminUpdateUser } = require('./admin');
- handleAdminUpdateUser(req, res);
- return;
- }
- // API: 管理后台 - 上传商店素材
- if (pathname === '/api/admin/store/upload') {
- const { handleAdminUploadStore } = require('./admin');
- handleAdminUploadStore(req, res);
- return;
- }
- // API: 管理后台 - 删除商店素材
- if (pathname === '/api/admin/store/delete') {
- console.log('[Server] 处理删除请求');
- const { handleAdminDeleteStore } = require('./admin');
- handleAdminDeleteStore(req, res);
- // 清除分类缓存(删除可能影响分类列表)
- storeManager.clearCategoriesCache();
- return;
- }
- // API: 管理后台 - 创建分类文件夹
- if (pathname === '/api/admin/store/create-folder') {
- const { handleAdminCreateFolder } = require('./admin');
- handleAdminCreateFolder(req, res);
- // 清除分类缓存
- storeManager.clearCategoriesCache();
- return;
- }
- // API: 管理后台 - 重命名商店素材
- if (pathname === '/api/admin/store/rename') {
- const { handleAdminRenameStore } = require('./admin');
- handleAdminRenameStore(req, res);
- // 清除分类缓存(重命名可能影响分类列表)
- storeManager.clearCategoriesCache();
- return;
- }
- // API: 管理后台 - 更新资源价格
- if (pathname === '/api/admin/store/update-price') {
- const { handleAdminUpdatePrice } = require('./admin');
- handleAdminUpdatePrice(req, res);
- // 清除价格缓存
- storeManager.clearPricesCache();
- return;
- }
- // API: 管理后台 - 获取货币设置
- if (pathname === '/api/admin/currency/settings' && req.method === 'GET') {
- const { handleGetCurrencySettings } = require('./admin');
- handleGetCurrencySettings(req, res);
- return;
- }
- // API: 管理后台 - 保存货币设置
- if (pathname === '/api/admin/currency/settings' && req.method === 'POST') {
- const { handleSaveCurrencySettings } = require('./admin');
- handleSaveCurrencySettings(req, res);
- return;
- }
- // API: 管理后台 - 获取商品定价设置
- if (pathname === '/api/admin/product-pricing/settings' && req.method === 'GET') {
- const { handleGetProductPricingSettings } = require('./admin');
- handleGetProductPricingSettings(req, res);
- return;
- }
- // API: 管理后台 - 保存商品定价设置
- if (pathname === '/api/admin/product-pricing/settings' && req.method === 'POST') {
- const { handleSaveProductPricingSettings } = require('./admin');
- handleSaveProductPricingSettings(req, res);
- return;
- }
- // API: 客户端 - 获取商品定价(用于显示价格)
- if (pathname === '/api/product-pricing' && req.method === 'GET') {
- const { handleGetProductPricingSettings } = require('./admin');
- handleGetProductPricingSettings(req, res);
- return;
- }
- // API: 管理后台 - 更新分类排序
- if (pathname === '/api/admin/store/update-order') {
- let body = '';
- req.on('data', chunk => { body += chunk.toString(); });
- req.on('end', async () => {
- try {
- const data = JSON.parse(body);
- const { order } = data;
-
- if (!order || !Array.isArray(order)) {
- res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
- res.end(JSON.stringify({ success: false, message: '缺少排序数据' }));
- return;
- }
-
- const success = await storeManager.saveCategoryOrder(order);
- if (success) {
- res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
- res.end(JSON.stringify({ success: true, message: '排序已保存' }));
- } else {
- res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
- res.end(JSON.stringify({ success: false, message: '保存排序失败' }));
- }
- } catch (error) {
- console.error('[Server] 更新分类排序失败:', error);
- res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
- res.end(JSON.stringify({ success: false, message: error.message }));
- }
- });
- return;
- }
- // API: 商店 - 获取分类列表
- if (pathname === '/api/store/categories') {
- storeManager.handleGetCategories(req, res);
- return;
- }
- // API: 商店 - 获取资源列表
- if (pathname === '/api/store/resources') {
- storeManager.handleGetResources(req, res);
- return;
- }
- // API: 商店 - 预览图
- if (pathname === '/api/store/preview') {
- storeManager.handlePreview(req, res);
- return;
- }
- // API: 商店 - 获取帧列表
- if (pathname === '/api/store/frames') {
- storeManager.handleGetFrames(req, res);
- return;
- }
- // API: 商店 - 获取帧图片
- if (pathname === '/api/store/frame') {
- storeManager.handleGetFrame(req, res);
- return;
- }
- // API: 支付 - 购买资源
- if (pathname === '/api/pay/purchase') {
- handlePurchaseRequest(req, res);
- return;
- }
- // API: 支付 - 检查资源是否存在
- if (pathname === '/api/pay/check-resource') {
- const { handleCheckResourceRequest } = require('./pay');
- handleCheckResourceRequest(req, res);
- return;
- }
- // API: 获取用户购买记录
- if (pathname === '/api/pay/purchase-history' && req.method === 'GET') {
- handleGetPurchaseHistory(req, res);
- return;
- }
- // API: 获取默认头像列表
- if (pathname === '/api/avatars/default') {
- const fs = require('fs');
- const avatarDir = path.join(__dirname, 'avatar');
-
- fs.readdir(avatarDir, (err, files) => {
- if (err) {
- res.writeHead(500, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
- res.end(JSON.stringify({ success: false, error: '读取头像目录失败' }));
- return;
- }
-
- // 过滤出图片文件
- const imageFiles = files.filter(file => {
- const ext = path.extname(file).toLowerCase();
- return ['.png', '.jpg', '.jpeg', '.gif'].includes(ext);
- });
-
- // 返回头像文件名列表
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
- res.end(JSON.stringify({ success: true, avatars: imageFiles }));
- });
- return;
- }
- // 处理 favicon.ico 请求,重定向到 static/favicon.png
- if (pathname === '/favicon.ico') {
- pathname = '/static/favicon.png';
- }
- // 默认首页 - 跳转到管理后台
- if (pathname === '/') {
- res.writeHead(302, { 'Location': '/admin/index.html' });
- res.end();
- return;
- }
-
- // /admin 路径也跳转到管理后台
- if (pathname === '/admin' || pathname === '/admin/') {
- res.writeHead(302, { 'Location': '/admin/index.html' });
- res.end();
- return;
- }
- let filePath;
- // 如果是 admin 相关的请求,从 admin 目录提供
- if (pathname.startsWith('/admin/')) {
- // 移除开头的 /,然后拼接路径
- const relativePath = pathname.substring(1); // 移除开头的 /
- // 解码 URL 编码的路径(处理中文、空格等特殊字符)
- const decodedPath = decodeURIComponent(relativePath);
- filePath = path.join(ADMIN_DIR, decodedPath.replace(/^admin\//, ''));
- } else if (pathname.startsWith('/texture/') || pathname.startsWith('/avatar/') || pathname.startsWith('/users/')) {
- // 如果是 texture、avatar 或 users 相关的请求,从 server 目录提供
- // 移除开头的 /,然后拼接路径
- const relativePath = pathname.substring(1); // 移除开头的 /
- // 解码 URL 编码的路径(处理中文、空格等特殊字符)
- const decodedPath = decodeURIComponent(relativePath);
- filePath = path.join(SERVER_DIR, decodedPath);
- } else {
- // 其他请求从 Client 目录提供
- const relativePath = pathname.startsWith('/') ? pathname.substring(1) : pathname;
- // 解码 URL 编码的路径
- const decodedPath = decodeURIComponent(relativePath);
- filePath = path.join(CLIENT_DIR, decodedPath);
- }
- // 检查文件是否存在并获取文件信息
- fs.stat(filePath, (statErr, stats) => {
- if (statErr) {
- // 文件不存在,返回 404(静默处理)
- res.writeHead(404, { 'Content-Type': 'text/plain' });
- res.end('');
- return;
- }
- // 获取文件扩展名
- const ext = path.extname(filePath).toLowerCase();
- const contentType = mimeTypes[ext] || 'application/octet-stream';
- // 构建响应头
- const headers = {
- 'Content-Type': contentType
- };
- // 如果是图片文件,添加缓存头
- if (ext === '.png' || ext === '.jpg' || ext === '.jpeg' || ext === '.gif') {
- // 设置长期缓存(1年)
- headers['Cache-Control'] = 'public, max-age=31536000, immutable';
- // 添加 ETag 用于缓存验证
- const etag = `"${stats.mtime.getTime()}-${stats.size}"`;
- headers['ETag'] = etag;
-
- // 检查客户端是否发送了 If-None-Match 头(缓存验证)
- const ifNoneMatch = req.headers['if-none-match'];
- if (ifNoneMatch === etag) {
- // 文件未修改,返回 304 Not Modified
- res.writeHead(304, headers);
- res.end();
- return;
- }
- }
- // 读取文件
- fs.readFile(filePath, (readErr, data) => {
- if (readErr) {
- res.writeHead(500, { 'Content-Type': 'text/plain' });
- res.end('Internal Server Error');
- return;
- }
- // 返回文件
- res.writeHead(200, headers);
- res.end(data);
- });
- });
- });
- // 启动服务器
- server.listen(PORT, () => {
- // 立即清屏(使用 ANSI 转义序列)
- process.stdout.write('\x1B[2J\x1B[0f');
-
- // 使用 setTimeout 延迟输出,确保批处理脚本的输出先完成
- setTimeout(() => {
- console.log('========================================');
- console.log(' PNG 序列动画预览工具 - 服务器');
- console.log('========================================');
- console.log(`服务器运行在: http://localhost:${PORT}`);
- console.log(`用户数据路径: ${path.join(SERVER_DIR, 'users')}`);
- console.log('========================================');
- console.log('按 Ctrl+C 或关闭此窗口停止服务器');
- console.log('');
- }, 1000); // 延迟 1000ms 输出,避免与批处理脚本输出重叠
- });
- // 优雅关闭处理
- function gracefulShutdown() {
- console.log('\n正在关闭服务器...');
- server.close(() => {
- console.log('服务器已关闭');
- process.exit(0);
- });
- }
- process.on('SIGINT', gracefulShutdown);
- process.on('SIGTERM', gracefulShutdown);
- // Windows 下处理窗口关闭事件
- if (process.platform === 'win32') {
- const readline = require('readline');
- const rl = readline.createInterface({
- input: process.stdin,
- output: process.stdout
- });
- rl.on('SIGINT', () => {
- process.emit('SIGINT');
- });
- }
|