admin.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854
  1. // 管理后台API处理
  2. const { getDatabase } = require('./sql');
  3. const fs = require('fs');
  4. const path = require('path');
  5. const { promisify } = require('util');
  6. const { formidable } = require('formidable');
  7. const RECHARGE_FILE = path.join(__dirname, 'recharge.json');
  8. const PRODUCT_PRICING_FILE = path.join(__dirname, 'product-pricing.json');
  9. const mkdir = promisify(fs.mkdir);
  10. const access = promisify(fs.access);
  11. const readdir = promisify(fs.readdir);
  12. const stat = promisify(fs.stat);
  13. const unlink = promisify(fs.unlink);
  14. const rmdir = promisify(fs.rmdir);
  15. // 商店资源根目录(使用 market_data 目录)
  16. const STORE_DIR = path.join(__dirname, 'market_data');
  17. // 确保目录存在
  18. async function ensureDir(dirPath) {
  19. try {
  20. await access(dirPath);
  21. } catch (error) {
  22. await mkdir(dirPath, { recursive: true });
  23. }
  24. }
  25. // 递归删除目录
  26. async function deleteDirectory(dirPath) {
  27. try {
  28. const files = await readdir(dirPath);
  29. for (const file of files) {
  30. const filePath = path.join(dirPath, file);
  31. const stats = await stat(filePath);
  32. if (stats.isDirectory()) {
  33. await deleteDirectory(filePath);
  34. } else {
  35. await unlink(filePath);
  36. }
  37. }
  38. await rmdir(dirPath);
  39. } catch (error) {
  40. console.error('[Admin] 删除目录失败:', error);
  41. throw error;
  42. }
  43. }
  44. // 获取所有用户列表
  45. async function handleGetAllUsers(req, res) {
  46. try {
  47. console.log('[Admin] 收到获取用户列表请求');
  48. const db = await getDatabase();
  49. const users = db.getAllUsers();
  50. console.log('[Admin] 从数据库获取到用户数量:', users ? users.length : 0);
  51. console.log('[Admin] 用户数据:', users);
  52. res.writeHead(200, {
  53. 'Content-Type': 'application/json; charset=utf-8',
  54. 'Access-Control-Allow-Origin': '*' // 允许跨域访问
  55. });
  56. res.end(JSON.stringify({
  57. success: true,
  58. users: users || []
  59. }));
  60. } catch (error) {
  61. console.error('[Admin] 获取用户列表失败:', error);
  62. res.writeHead(500, {
  63. 'Content-Type': 'application/json; charset=utf-8',
  64. 'Access-Control-Allow-Origin': '*'
  65. });
  66. res.end(JSON.stringify({
  67. success: false,
  68. message: '获取用户列表失败: ' + error.message
  69. }));
  70. }
  71. }
  72. // 更新用户信息(管理员)
  73. async function handleAdminUpdateUser(req, res) {
  74. let body = '';
  75. req.on('data', chunk => {
  76. body += chunk.toString();
  77. });
  78. req.on('end', async () => {
  79. try {
  80. const data = JSON.parse(body);
  81. const { id, username, phone, points } = data;
  82. if (!id) {
  83. res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
  84. res.end(JSON.stringify({
  85. success: false,
  86. message: '缺少用户ID参数'
  87. }));
  88. return;
  89. }
  90. const db = await getDatabase();
  91. const user = db.findUserById(id);
  92. if (!user) {
  93. res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
  94. res.end(JSON.stringify({
  95. success: false,
  96. message: '用户不存在'
  97. }));
  98. return;
  99. }
  100. // 构建更新对象
  101. const updates = {};
  102. if (username !== undefined) {
  103. updates.username = username;
  104. }
  105. if (phone !== undefined) {
  106. updates.phone = phone;
  107. }
  108. // 更新用户信息
  109. if (Object.keys(updates).length > 0) {
  110. db.updateUser(id, updates);
  111. }
  112. // 更新点数(如果提供)
  113. if (points !== undefined && points !== null) {
  114. const currentPoints = db.getUserPoints(user.username);
  115. const diff = points - currentPoints;
  116. if (diff > 0) {
  117. db.addPoints(user.username, diff);
  118. } else if (diff < 0) {
  119. db.deductPoints(user.username, Math.abs(diff));
  120. }
  121. }
  122. res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
  123. res.end(JSON.stringify({
  124. success: true,
  125. message: '更新成功'
  126. }));
  127. } catch (error) {
  128. console.error('[Admin] 更新用户失败:', error);
  129. res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
  130. res.end(JSON.stringify({
  131. success: false,
  132. message: '更新失败: ' + error.message
  133. }));
  134. }
  135. });
  136. }
  137. // 上传商店素材
  138. async function handleAdminUploadStore(req, res) {
  139. console.log('[Admin] ========== 收到上传请求 ==========');
  140. console.log('[Admin] 请求方法:', req.method);
  141. console.log('[Admin] 请求URL:', req.url);
  142. console.log('[Admin] Content-Type:', req.headers['content-type']);
  143. const form = formidable({
  144. uploadDir: path.join(__dirname, 'temp'),
  145. keepExtensions: true,
  146. multiples: true
  147. });
  148. form.parse(req, async (err, fields, files) => {
  149. if (err) {
  150. console.error('[Admin] ❌ 解析上传文件失败:', err);
  151. res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
  152. res.end(JSON.stringify({
  153. success: false,
  154. message: '上传失败: ' + err.message
  155. }));
  156. return;
  157. }
  158. console.log('[Admin] ✅ 文件解析成功');
  159. console.log('[Admin] fields:', JSON.stringify(fields, null, 2));
  160. console.log('[Admin] files keys:', Object.keys(files));
  161. try {
  162. const category = Array.isArray(fields.category) ? fields.category[0] : fields.category;
  163. const name = Array.isArray(fields.name) ? fields.name[0] : fields.name;
  164. const price = parseInt(Array.isArray(fields.price) ? fields.price[0] : fields.price) || 0;
  165. console.log('[Admin] 📦 上传参数:');
  166. console.log('[Admin] - 分类(category):', category);
  167. console.log('[Admin] - 文件夹名(name):', name);
  168. console.log('[Admin] - 价格(price):', price);
  169. if (!category || !name) {
  170. res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
  171. res.end(JSON.stringify({
  172. success: false,
  173. message: '缺少分类或名称参数'
  174. }));
  175. return;
  176. }
  177. // 获取上传的文件
  178. let fileList = [];
  179. if (files.files) {
  180. fileList = Array.isArray(files.files) ? files.files : [files.files];
  181. }
  182. console.log('[Admin] 📁 收到文件数量:', fileList.length);
  183. if (fileList.length === 0) {
  184. console.log('[Admin] ❌ 文件列表为空');
  185. res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
  186. res.end(JSON.stringify({
  187. success: false,
  188. message: '请选择要上传的文件'
  189. }));
  190. return;
  191. }
  192. // 创建目标目录
  193. const targetDir = path.join(STORE_DIR, category, name);
  194. console.log('[Admin] 📂 目标目录:', targetDir);
  195. console.log('[Admin] 📂 STORE_DIR:', STORE_DIR);
  196. await ensureDir(targetDir);
  197. console.log('[Admin] ✅ 目标目录已创建/确认存在');
  198. // 移动文件到目标目录,保持目录结构
  199. console.log('[Admin] 🔄 开始处理', fileList.length, '个文件...');
  200. let processedCount = 0;
  201. let skippedCount = 0;
  202. for (let i = 0; i < fileList.length; i++) {
  203. const file = fileList[i];
  204. const fileObj = Array.isArray(file) ? file[0] : file;
  205. if (!fileObj || !fileObj.filepath) {
  206. console.log(`[Admin] ⚠️ [${i+1}/${fileList.length}] 跳过无效文件对象`);
  207. skippedCount++;
  208. continue;
  209. }
  210. // 获取原始文件名(包含相对路径)
  211. const originalName = fileObj.originalFilename || path.basename(fileObj.filepath);
  212. console.log(`[Admin] 📄 [${i+1}/${fileList.length}] 处理文件:`, originalName);
  213. // 处理路径分隔符(统一使用 /)
  214. const normalizedName = originalName.replace(/\\/g, '/');
  215. // 如果 originalName 包含文件夹名(如 player_0001/image.png),需要去掉文件夹名前缀
  216. // 因为 targetDir 已经是 market_data/category/name 了
  217. let relativePath = normalizedName;
  218. // 如果路径以文件夹名开头(如 player_0001/image.png),去掉文件夹名
  219. if (normalizedName.startsWith(name + '/')) {
  220. relativePath = normalizedName.substring(name.length + 1);
  221. console.log(`[Admin] └─ 去掉文件夹前缀 "${name}/",相对路径:`, relativePath);
  222. }
  223. const targetPath = path.join(targetDir, relativePath);
  224. console.log(`[Admin] └─ 目标路径:`, targetPath);
  225. // 确保目标目录存在
  226. await ensureDir(path.dirname(targetPath));
  227. // 复制文件
  228. await fs.promises.copyFile(fileObj.filepath, targetPath);
  229. console.log(`[Admin] └─ ✅ 文件已复制成功`);
  230. processedCount++;
  231. // 删除临时文件
  232. try {
  233. await fs.promises.unlink(fileObj.filepath);
  234. } catch (err) {
  235. console.warn(`[Admin] └─ ⚠️ 删除临时文件失败:`, err.message);
  236. }
  237. }
  238. console.log('[Admin] ========== 上传处理完成 ==========');
  239. console.log('[Admin] ✅ 成功处理:', processedCount, '个文件');
  240. if (skippedCount > 0) {
  241. console.log('[Admin] ⚠️ 跳过:', skippedCount, '个无效文件');
  242. }
  243. console.log('[Admin] 📂 最终保存位置:', targetDir);
  244. // 更新商店资源配置(如果需要)
  245. // 这里可以添加更新商店配置文件的逻辑
  246. res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
  247. res.end(JSON.stringify({
  248. success: true,
  249. message: '上传成功',
  250. path: `${category}/${name}`,
  251. fileCount: processedCount
  252. }));
  253. } catch (error) {
  254. console.error('[Admin] ❌ ========== 上传失败 ==========');
  255. console.error('[Admin] 错误信息:', error.message);
  256. console.error('[Admin] 错误堆栈:', error.stack);
  257. res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
  258. res.end(JSON.stringify({
  259. success: false,
  260. message: '上传失败: ' + error.message
  261. }));
  262. }
  263. });
  264. }
  265. // 删除商店素材
  266. async function handleAdminDeleteStore(req, res) {
  267. let body = '';
  268. req.on('data', chunk => {
  269. body += chunk.toString();
  270. });
  271. req.on('end', async () => {
  272. try {
  273. const data = JSON.parse(body);
  274. const { resourcePath } = data;
  275. console.log('[Admin] 删除请求 - resourcePath:', resourcePath);
  276. console.log('[Admin] STORE_DIR:', STORE_DIR);
  277. if (!resourcePath) {
  278. res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
  279. res.end(JSON.stringify({
  280. success: false,
  281. message: '缺少资源路径参数'
  282. }));
  283. return;
  284. }
  285. // 构建完整路径
  286. const fullPath = path.join(STORE_DIR, resourcePath);
  287. console.log('[Admin] 完整路径:', fullPath);
  288. // 安全检查
  289. const normalizedPath = path.normalize(fullPath);
  290. const normalizedRoot = path.normalize(STORE_DIR);
  291. console.log('[Admin] normalizedPath:', normalizedPath);
  292. console.log('[Admin] normalizedRoot:', normalizedRoot);
  293. if (!normalizedPath.startsWith(normalizedRoot)) {
  294. console.log('[Admin] 安全检查失败');
  295. res.writeHead(403, { 'Content-Type': 'application/json; charset=utf-8' });
  296. res.end(JSON.stringify({
  297. success: false,
  298. message: '访问被拒绝'
  299. }));
  300. return;
  301. }
  302. // 检查路径是否存在
  303. try {
  304. const stats = await stat(fullPath);
  305. console.log('[Admin] 路径存在,是目录:', stats.isDirectory());
  306. if (stats.isDirectory()) {
  307. await deleteDirectory(fullPath);
  308. } else {
  309. await unlink(fullPath);
  310. }
  311. console.log('[Admin] 删除成功');
  312. res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
  313. res.end(JSON.stringify({
  314. success: true,
  315. message: '删除成功'
  316. }));
  317. } catch (error) {
  318. console.log('[Admin] stat 错误:', error.code, error.message);
  319. if (error.code === 'ENOENT') {
  320. res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
  321. res.end(JSON.stringify({
  322. success: false,
  323. message: '资源不存在'
  324. }));
  325. } else {
  326. throw error;
  327. }
  328. }
  329. } catch (error) {
  330. console.error('[Admin] 删除素材失败:', error);
  331. res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
  332. res.end(JSON.stringify({
  333. success: false,
  334. message: '删除失败: ' + error.message
  335. }));
  336. }
  337. });
  338. }
  339. // 创建分类文件夹
  340. async function handleAdminCreateFolder(req, res) {
  341. let body = '';
  342. req.on('data', chunk => {
  343. body += chunk.toString();
  344. });
  345. req.on('end', async () => {
  346. try {
  347. const data = JSON.parse(body);
  348. const { name } = data;
  349. if (!name || !name.trim()) {
  350. res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
  351. res.end(JSON.stringify({
  352. success: false,
  353. message: '缺少文件夹名称参数'
  354. }));
  355. return;
  356. }
  357. const folderName = name.trim();
  358. // 验证文件夹名称
  359. if (/[\\/:*?"<>|]/.test(folderName)) {
  360. res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
  361. res.end(JSON.stringify({
  362. success: false,
  363. message: '文件夹名称包含非法字符'
  364. }));
  365. return;
  366. }
  367. // 创建分类文件夹(在 STORE_DIR 根目录下)
  368. const targetDir = path.join(STORE_DIR, folderName);
  369. // 检查文件夹是否已存在
  370. try {
  371. await access(targetDir);
  372. res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
  373. res.end(JSON.stringify({
  374. success: false,
  375. message: '文件夹已存在'
  376. }));
  377. return;
  378. } catch (error) {
  379. if (error.code !== 'ENOENT') {
  380. throw error;
  381. }
  382. // 文件夹不存在,创建它
  383. await mkdir(targetDir, { recursive: true });
  384. }
  385. res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
  386. res.end(JSON.stringify({
  387. success: true,
  388. message: '创建文件夹成功',
  389. path: folderName
  390. }));
  391. } catch (error) {
  392. console.error('[Admin] 创建文件夹失败:', error);
  393. res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
  394. res.end(JSON.stringify({
  395. success: false,
  396. message: '创建文件夹失败: ' + error.message
  397. }));
  398. }
  399. });
  400. }
  401. // 重命名商店素材
  402. async function handleAdminRenameStore(req, res) {
  403. let body = '';
  404. req.on('data', chunk => {
  405. body += chunk.toString();
  406. });
  407. req.on('end', async () => {
  408. try {
  409. const data = JSON.parse(body);
  410. const { resourcePath, newName } = data;
  411. if (!resourcePath || !newName) {
  412. res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
  413. res.end(JSON.stringify({
  414. success: false,
  415. message: '缺少资源路径或新名称参数'
  416. }));
  417. return;
  418. }
  419. // 验证新名称
  420. if (/[\\/:*?"<>|]/.test(newName)) {
  421. res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
  422. res.end(JSON.stringify({
  423. success: false,
  424. message: '名称包含非法字符'
  425. }));
  426. return;
  427. }
  428. // 构建完整路径
  429. const fullPath = path.join(STORE_DIR, resourcePath);
  430. // 安全检查
  431. const normalizedPath = path.normalize(fullPath);
  432. const normalizedRoot = path.normalize(STORE_DIR);
  433. if (!normalizedPath.startsWith(normalizedRoot)) {
  434. res.writeHead(403, { 'Content-Type': 'application/json; charset=utf-8' });
  435. res.end(JSON.stringify({
  436. success: false,
  437. message: '访问被拒绝'
  438. }));
  439. return;
  440. }
  441. // 检查路径是否存在
  442. try {
  443. await access(fullPath);
  444. } catch (error) {
  445. if (error.code === 'ENOENT') {
  446. res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
  447. res.end(JSON.stringify({
  448. success: false,
  449. message: '资源不存在'
  450. }));
  451. return;
  452. } else {
  453. throw error;
  454. }
  455. }
  456. // 构建新路径
  457. const parentDir = path.dirname(fullPath);
  458. const newFullPath = path.join(parentDir, newName);
  459. // 检查新名称是否已存在
  460. try {
  461. await access(newFullPath);
  462. res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
  463. res.end(JSON.stringify({
  464. success: false,
  465. message: '新名称已存在'
  466. }));
  467. return;
  468. } catch (error) {
  469. if (error.code !== 'ENOENT') {
  470. throw error;
  471. }
  472. }
  473. // 重命名文件或文件夹
  474. await fs.promises.rename(fullPath, newFullPath);
  475. const newPathKey = path.relative(STORE_DIR, newFullPath).replace(/\\/g, '/');
  476. const oldPathKey = resourcePath;
  477. // 检查是否是分类文件夹(在根目录下)
  478. const stats = await stat(newFullPath);
  479. const isCategoryFolder = stats.isDirectory() && !resourcePath.includes('/');
  480. // 更新价格文件中的路径(如果存在)
  481. try {
  482. const pricesFilePath = path.join(STORE_DIR, 'prices.json');
  483. const pricesData = await fs.promises.readFile(pricesFilePath, 'utf-8');
  484. const prices = JSON.parse(pricesData);
  485. // 更新所有相关的价格路径
  486. // 如果是分类文件夹,需要更新所有子资源的价格路径
  487. if (isCategoryFolder) {
  488. const updatedPrices = {};
  489. for (const [key, value] of Object.entries(prices)) {
  490. if (key.startsWith(oldPathKey + '/')) {
  491. // 更新子资源的路径
  492. const newKey = key.replace(oldPathKey + '/', newPathKey + '/');
  493. updatedPrices[newKey] = value;
  494. } else if (key === oldPathKey) {
  495. // 更新分类文件夹本身的价格(如果有)
  496. updatedPrices[newPathKey] = value;
  497. } else {
  498. // 保留其他路径
  499. updatedPrices[key] = value;
  500. }
  501. }
  502. await fs.promises.writeFile(pricesFilePath, JSON.stringify(updatedPrices, null, 2), 'utf-8');
  503. } else {
  504. // 只更新当前资源的价格路径
  505. if (prices[oldPathKey] !== undefined) {
  506. prices[newPathKey] = prices[oldPathKey];
  507. delete prices[oldPathKey];
  508. await fs.promises.writeFile(pricesFilePath, JSON.stringify(prices, null, 2), 'utf-8');
  509. }
  510. }
  511. } catch (error) {
  512. // 价格文件不存在或更新失败,不影响重命名操作
  513. console.warn('[Admin] 更新价格文件失败:', error);
  514. }
  515. res.writeHead(200, {
  516. 'Content-Type': 'application/json; charset=utf-8',
  517. 'Access-Control-Allow-Origin': '*'
  518. });
  519. res.end(JSON.stringify({
  520. success: true,
  521. message: '重命名成功',
  522. newPath: newPathKey,
  523. isCategoryFolder: isCategoryFolder
  524. }));
  525. } catch (error) {
  526. console.error('[Admin] 重命名素材失败:', error);
  527. res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
  528. res.end(JSON.stringify({
  529. success: false,
  530. message: '重命名失败: ' + error.message
  531. }));
  532. }
  533. });
  534. }
  535. // 更新资源价格
  536. async function handleAdminUpdatePrice(req, res) {
  537. let body = '';
  538. req.on('data', chunk => {
  539. body += chunk.toString();
  540. });
  541. req.on('end', async () => {
  542. try {
  543. const data = JSON.parse(body);
  544. const { resourcePath, price } = data;
  545. if (!resourcePath || price === undefined) {
  546. res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
  547. res.end(JSON.stringify({
  548. success: false,
  549. message: '缺少资源路径或价格参数'
  550. }));
  551. return;
  552. }
  553. // 价格数据存储在JSON文件中
  554. const pricesFilePath = path.join(STORE_DIR, 'prices.json');
  555. let prices = {};
  556. try {
  557. const pricesData = await fs.promises.readFile(pricesFilePath, 'utf-8');
  558. prices = JSON.parse(pricesData);
  559. } catch (error) {
  560. // 文件不存在,创建新的
  561. prices = {};
  562. }
  563. // 更新价格
  564. prices[resourcePath] = price;
  565. // 保存到文件
  566. await fs.promises.writeFile(pricesFilePath, JSON.stringify(prices, null, 2), 'utf-8');
  567. // 通知 StoreManager 清除价格缓存(通过事件或直接调用)
  568. // 注意:这里 StoreManager 的缓存会在下次请求时自动清除
  569. res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
  570. res.end(JSON.stringify({
  571. success: true,
  572. message: '价格更新成功'
  573. }));
  574. } catch (error) {
  575. console.error('[Admin] 更新价格失败:', error);
  576. res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
  577. res.end(JSON.stringify({
  578. success: false,
  579. message: '更新失败: ' + error.message
  580. }));
  581. }
  582. });
  583. }
  584. // 货币设置文件路径
  585. const CURRENCY_SETTINGS_FILE = path.join(__dirname, 'currency-settings.json');
  586. // 获取货币/充值套餐设置
  587. async function handleGetCurrencySettings(req, res) {
  588. try {
  589. let packages = [
  590. { points: 100, bonus: 20, price: 5 },
  591. { points: 1000, bonus: 200, price: 50 },
  592. { points: 10000, bonus: 800, price: 500 }
  593. ];
  594. try {
  595. const data = await fs.promises.readFile(CURRENCY_SETTINGS_FILE, 'utf-8');
  596. const parsed = JSON.parse(data);
  597. packages = parsed.packages || packages;
  598. } catch (err) {
  599. // 文件不存在,使用默认值
  600. }
  601. res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
  602. res.end(JSON.stringify({
  603. success: true,
  604. packages
  605. }));
  606. } catch (error) {
  607. console.error('[Admin] 获取充值套餐失败:', error);
  608. res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
  609. res.end(JSON.stringify({
  610. success: false,
  611. message: '获取设置失败: ' + error.message
  612. }));
  613. }
  614. }
  615. // 保存货币/充值套餐设置
  616. async function handleSaveCurrencySettings(req, res) {
  617. let body = '';
  618. req.on('data', chunk => { body += chunk.toString(); });
  619. req.on('end', async () => {
  620. try {
  621. const data = JSON.parse(body);
  622. const { packages } = data;
  623. await fs.promises.writeFile(CURRENCY_SETTINGS_FILE, JSON.stringify({ packages }, null, 2), 'utf-8');
  624. res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
  625. res.end(JSON.stringify({
  626. success: true,
  627. message: '充值套餐保存成功'
  628. }));
  629. } catch (error) {
  630. console.error('[Admin] 保存充值套餐失败:', error);
  631. res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
  632. res.end(JSON.stringify({
  633. success: false,
  634. message: '保存失败: ' + error.message
  635. }));
  636. }
  637. });
  638. }
  639. // 获取商品定价设置
  640. async function handleGetProductPricingSettings(req, res) {
  641. try {
  642. let products = [
  643. { id: 'vip-matting', name: 'VIP抠图', desc: '使用VIP服务进行图片抠图', price: 0 },
  644. { id: 'ai-generate', name: 'AI生图', desc: '使用AI生成图片', price: 0 }
  645. ];
  646. try {
  647. const data = await fs.promises.readFile(PRODUCT_PRICING_FILE, 'utf-8');
  648. const parsed = JSON.parse(data);
  649. if (parsed.products && Array.isArray(parsed.products)) {
  650. // 合并服务器保存的价格
  651. parsed.products.forEach(serverProduct => {
  652. const localProduct = products.find(p => p.id === serverProduct.id);
  653. if (localProduct) {
  654. localProduct.price = serverProduct.price || 0;
  655. }
  656. });
  657. }
  658. } catch (err) {
  659. // 文件不存在,使用默认值
  660. }
  661. res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
  662. res.end(JSON.stringify({
  663. success: true,
  664. products
  665. }));
  666. } catch (error) {
  667. console.error('[Admin] 获取商品定价失败:', error);
  668. res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
  669. res.end(JSON.stringify({
  670. success: false,
  671. message: '获取设置失败: ' + error.message
  672. }));
  673. }
  674. }
  675. // 保存商品定价设置
  676. async function handleSaveProductPricingSettings(req, res) {
  677. let body = '';
  678. req.on('data', chunk => { body += chunk.toString(); });
  679. req.on('end', async () => {
  680. try {
  681. const data = JSON.parse(body);
  682. const { productId, price } = data;
  683. if (!productId || price === undefined) {
  684. res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
  685. res.end(JSON.stringify({
  686. success: false,
  687. message: '缺少必要参数'
  688. }));
  689. return;
  690. }
  691. // 读取现有设置
  692. let products = [
  693. { id: 'vip-matting', name: 'VIP抠图', desc: '使用VIP服务进行图片抠图', price: 0 },
  694. { id: 'ai-generate', name: 'AI生图', desc: '使用AI生成图片', price: 0 }
  695. ];
  696. try {
  697. const fileData = await fs.promises.readFile(PRODUCT_PRICING_FILE, 'utf-8');
  698. const parsed = JSON.parse(fileData);
  699. if (parsed.products && Array.isArray(parsed.products)) {
  700. products = parsed.products;
  701. }
  702. } catch (err) {
  703. // 文件不存在,使用默认值
  704. }
  705. // 更新指定商品的价格
  706. const product = products.find(p => p.id === productId);
  707. if (product) {
  708. product.price = parseFloat(price) || 0;
  709. } else {
  710. res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
  711. res.end(JSON.stringify({
  712. success: false,
  713. message: '商品不存在'
  714. }));
  715. return;
  716. }
  717. await fs.promises.writeFile(PRODUCT_PRICING_FILE, JSON.stringify({ products }, null, 2), 'utf-8');
  718. res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
  719. res.end(JSON.stringify({
  720. success: true,
  721. message: '价格保存成功'
  722. }));
  723. } catch (error) {
  724. console.error('[Admin] 保存商品定价失败:', error);
  725. res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
  726. res.end(JSON.stringify({
  727. success: false,
  728. message: '保存失败: ' + error.message
  729. }));
  730. }
  731. });
  732. }
  733. module.exports = {
  734. handleGetAllUsers,
  735. handleAdminUpdateUser,
  736. handleAdminUploadStore,
  737. handleAdminDeleteStore,
  738. handleAdminCreateFolder,
  739. handleAdminRenameStore,
  740. handleAdminUpdatePrice,
  741. handleGetCurrencySettings,
  742. handleSaveCurrencySettings,
  743. handleGetProductPricingSettings,
  744. handleSaveProductPricingSettings
  745. };