server.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. // PNG 序列动画预览工具 - 服务器主文件
  2. // 负责启动 HTTP 服务器和静态文件服务
  3. const http = require('http');
  4. const fs = require('fs');
  5. const path = require('path');
  6. const url = require('url');
  7. const TextureReader = require('./texture-reader');
  8. const ZipHandler = require('./zip');
  9. const DiskManager = require('./disk');
  10. const ReplaceCharacterHandler = require('./replace-character');
  11. const RemoveBackgroundBase64 = require('./remove-background-base64');
  12. const { handleLoginRequest, handleCheckPhoneRequest } = require('./login');
  13. const { handleRegisterRequest } = require('./register');
  14. const PORT = 3000;
  15. const SERVER_DIR = __dirname; // Server 目录
  16. const CLIENT_DIR = path.join(__dirname, '..', 'Client'); // Client 目录
  17. // MIME 类型映射
  18. const mimeTypes = {
  19. '.html': 'text/html',
  20. '.js': 'text/javascript',
  21. '.css': 'text/css',
  22. '.png': 'image/png',
  23. '.jpg': 'image/jpeg',
  24. '.gif': 'image/gif',
  25. '.json': 'application/json',
  26. };
  27. // 初始化 TextureReader
  28. const textureReader = new TextureReader(SERVER_DIR);
  29. // 初始化 DiskManager
  30. const diskManager = new DiskManager();
  31. // 创建 HTTP 服务器
  32. const server = http.createServer((req, res) => {
  33. const parsedUrl = url.parse(req.url, true);
  34. let pathname = parsedUrl.pathname;
  35. // API: 获取可用的文件夹列表
  36. if (pathname === '/api/folders') {
  37. textureReader.handleGetFolders(res);
  38. return;
  39. }
  40. // API: 获取指定文件夹中的帧文件列表
  41. if (pathname.startsWith('/api/frames/')) {
  42. const encodedPath = pathname.replace('/api/frames/', '');
  43. // 对路径的每一段进行解码
  44. const folderName = encodedPath.split('/').map(seg => decodeURIComponent(seg)).join('/');
  45. console.log('[Server] API请求帧列表, 原始:', encodedPath, '解码后:', folderName);
  46. textureReader.handleGetFrames(folderName, res);
  47. return;
  48. }
  49. // API: 打包 sprite sheet 为 ZIP
  50. if (pathname === '/api/pack') {
  51. ZipHandler.handlePackRequest(req, res);
  52. return;
  53. }
  54. // API: 角色替换(图生图)
  55. if (pathname === '/api/replace-character') {
  56. ReplaceCharacterHandler.handleReplaceRequest(req, res);
  57. return;
  58. }
  59. // API: Base64 图片抠图
  60. if (pathname === '/api/remove-background-base64') {
  61. RemoveBackgroundBase64.handleRequest(req, res);
  62. return;
  63. }
  64. // API: 网盘 - 获取文件列表
  65. if (pathname === '/api/disk/list') {
  66. diskManager.handleListRequest(req, res);
  67. return;
  68. }
  69. // API: 网盘 - 上传文件
  70. if (pathname === '/api/disk/upload') {
  71. diskManager.handleUploadRequest(req, res);
  72. return;
  73. }
  74. // API: 网盘 - 创建文件夹
  75. if (pathname === '/api/disk/create-folder') {
  76. diskManager.handleCreateFolderRequest(req, res);
  77. return;
  78. }
  79. // API: 网盘 - 下载文件
  80. if (pathname === '/api/disk/download') {
  81. diskManager.handleDownloadRequest(req, res);
  82. return;
  83. }
  84. // API: 网盘 - 重命名
  85. if (pathname === '/api/disk/rename') {
  86. diskManager.handleRenameRequest(req, res);
  87. return;
  88. }
  89. // API: 网盘 - 图片预览
  90. if (pathname === '/api/disk/preview') {
  91. diskManager.handlePreviewRequest(req, res);
  92. return;
  93. }
  94. // API: 网盘 - 移动文件/文件夹
  95. if (pathname === '/api/disk/move') {
  96. diskManager.handleMoveRequest(req, res);
  97. return;
  98. }
  99. // API: 网盘 - 复制文件/文件夹
  100. if (pathname === '/api/disk/copy') {
  101. diskManager.handleCopyRequest(req, res);
  102. return;
  103. }
  104. // API: 网盘 - 删除文件/文件夹
  105. if (pathname === '/api/disk/delete') {
  106. diskManager.handleDeleteRequest(req, res);
  107. return;
  108. }
  109. // API: 网盘 - 一键抠背景
  110. if (pathname === '/api/disk/remove-background') {
  111. diskManager.handleRemoveBackgroundRequest(req, res);
  112. return;
  113. }
  114. // API: 网盘 - 剪裁最小区域
  115. if (pathname === '/api/disk/crop-mini') {
  116. diskManager.handleCropMiniRequest(req, res);
  117. return;
  118. }
  119. // API: 用户注册
  120. if (pathname === '/api/register') {
  121. handleRegisterRequest(req, res);
  122. return;
  123. }
  124. // API: 用户登录
  125. if (pathname === '/api/login') {
  126. handleLoginRequest(req, res);
  127. return;
  128. }
  129. // API: 检查手机号是否存在
  130. if (pathname === '/api/check-phone') {
  131. handleCheckPhoneRequest(req, res);
  132. return;
  133. }
  134. // 默认首页
  135. if (pathname === '/') {
  136. pathname = '/index.html';
  137. }
  138. let filePath;
  139. // 如果是 texture、disk_data 或 avatar 相关的请求,从 server 目录提供
  140. if (pathname.startsWith('/texture/') || pathname.startsWith('/disk_data/') || pathname.startsWith('/avatar/')) {
  141. // 移除开头的 /,然后拼接路径
  142. const relativePath = pathname.substring(1); // 移除开头的 /
  143. // 解码 URL 编码的路径(处理中文、空格等特殊字符)
  144. const decodedPath = decodeURIComponent(relativePath);
  145. filePath = path.join(SERVER_DIR, decodedPath);
  146. } else {
  147. // 其他请求从 Client 目录提供
  148. const relativePath = pathname.startsWith('/') ? pathname.substring(1) : pathname;
  149. // 解码 URL 编码的路径
  150. const decodedPath = decodeURIComponent(relativePath);
  151. filePath = path.join(CLIENT_DIR, decodedPath);
  152. }
  153. // 检查文件是否存在并获取文件信息
  154. fs.stat(filePath, (statErr, stats) => {
  155. if (statErr) {
  156. // 文件不存在,返回 404(静默处理)
  157. res.writeHead(404, { 'Content-Type': 'text/plain' });
  158. res.end('');
  159. return;
  160. }
  161. // 获取文件扩展名
  162. const ext = path.extname(filePath).toLowerCase();
  163. const contentType = mimeTypes[ext] || 'application/octet-stream';
  164. // 构建响应头
  165. const headers = {
  166. 'Content-Type': contentType
  167. };
  168. // 如果是图片文件,添加缓存头
  169. if (ext === '.png' || ext === '.jpg' || ext === '.jpeg' || ext === '.gif') {
  170. // 设置长期缓存(1年)
  171. headers['Cache-Control'] = 'public, max-age=31536000, immutable';
  172. // 添加 ETag 用于缓存验证
  173. const etag = `"${stats.mtime.getTime()}-${stats.size}"`;
  174. headers['ETag'] = etag;
  175. // 检查客户端是否发送了 If-None-Match 头(缓存验证)
  176. const ifNoneMatch = req.headers['if-none-match'];
  177. if (ifNoneMatch === etag) {
  178. // 文件未修改,返回 304 Not Modified
  179. res.writeHead(304, headers);
  180. res.end();
  181. return;
  182. }
  183. }
  184. // 读取文件
  185. fs.readFile(filePath, (readErr, data) => {
  186. if (readErr) {
  187. res.writeHead(500, { 'Content-Type': 'text/plain' });
  188. res.end('Internal Server Error');
  189. return;
  190. }
  191. // 返回文件
  192. res.writeHead(200, headers);
  193. res.end(data);
  194. });
  195. });
  196. });
  197. // 启动服务器
  198. server.listen(PORT, () => {
  199. // 立即输出服务器启动信息(在批处理脚本清屏之前)
  200. console.log('');
  201. console.log('========================================');
  202. console.log(' PNG 序列动画预览工具 - 服务器');
  203. console.log('========================================');
  204. console.log(`服务器运行在: http://localhost:${PORT}`);
  205. console.log(`图片资源路径: ${path.join(SERVER_DIR, 'disk_data')}`);
  206. console.log('========================================');
  207. console.log('按 Ctrl+C 或关闭此窗口停止服务器');
  208. console.log('');
  209. });
  210. // 优雅关闭处理
  211. function gracefulShutdown() {
  212. console.log('\n正在关闭服务器...');
  213. server.close(() => {
  214. console.log('服务器已关闭');
  215. process.exit(0);
  216. });
  217. }
  218. process.on('SIGINT', gracefulShutdown);
  219. process.on('SIGTERM', gracefulShutdown);
  220. // Windows 下处理窗口关闭事件
  221. if (process.platform === 'win32') {
  222. const readline = require('readline');
  223. const rl = readline.createInterface({
  224. input: process.stdin,
  225. output: process.stdout
  226. });
  227. rl.on('SIGINT', () => {
  228. process.emit('SIGINT');
  229. });
  230. }