From cf1cbbd5975c546b4e15bf722d54d4fe97c7cd1d Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 10 Feb 2026 10:09:24 +0800 Subject: [PATCH] save code --- backend/__pycache__/app.cpython-310.pyc | Bin 0 -> 7492 bytes backend/__pycache__/config.cpython-310.pyc | Bin 0 -> 593 bytes backend/app.log | 288 +++++++++++ backend/nohup.out | 132 +++++ docs/PROJECT_ANALYSIS.md | 556 +++++++++++++++++++++ frontend/frontend.log | 70 +++ frontend/index.html | 5 +- frontend/js/api.js | 2 +- frontend/js/axios.min.js | 5 + frontend/js/main.js | 9 +- 10 files changed, 1060 insertions(+), 7 deletions(-) create mode 100644 backend/__pycache__/app.cpython-310.pyc create mode 100644 backend/__pycache__/config.cpython-310.pyc create mode 100644 backend/app.log create mode 100644 backend/nohup.out create mode 100644 docs/PROJECT_ANALYSIS.md create mode 100644 frontend/frontend.log create mode 100644 frontend/js/axios.min.js diff --git a/backend/__pycache__/app.cpython-310.pyc b/backend/__pycache__/app.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be2f76347c115ddb2de9f9d64283c58226413ed4 GIT binary patch literal 7492 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 U;}OGeJZBt4$&XuambDxI3n(3=MgRZ+ literal 0 HcmV?d00001 diff --git a/backend/__pycache__/config.cpython-310.pyc b/backend/__pycache__/config.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c18e5ac12ed78f0dfffd53c6b57c2e907dcd35cc GIT binary patch literal 593 zcmaJ@0<2xt9u)ai z8gKSTLAp8^BtafW2ZJIjR-40Qy)I8Fy-Oijr5UKBuvnhPAeGW?9M{oO^ z-hZnTsA^E@?c4i1IA#ir&rFbt#OakH*JZ3ona9e!E~D6YdmUv7kq6622-Oi{S;#um z_Ddl?)j{^0vFzzoxb}ppS2H&8d?xJaSS)6adh_g$?D0$tJja{;PQB1I{&>Q?g|E63 z`vUT=&s<+P%pLk8)wLbR`^e4(yMXX~?74H*I%gji!yjn14~K5V(=knoWkl=CI$zgR z-%*6k1VyMPNH2r%CdwrZn;9RKUsf1;^>bCPL0HH%OtM`Twn&q8d$`emw%Jâ\x05¤Zõ\x00?@Üå\x1b8') +3.140.198.248 - - [08/Feb/2026 05:43:41] "\x16\x03\x01\x00{\x01\x00\x00w\x03\x03\x0f½L>â\x05¤Zõ\x00?@Üå\x1b8" HTTPStatus.BAD_REQUEST - +3.140.198.248 - - [08/Feb/2026 05:44:45] code 400, message Bad request syntax ('SSH-2.0-Go') +3.140.198.248 - - [08/Feb/2026 05:44:45] "SSH-2.0-Go" HTTPStatus.BAD_REQUEST - +3.140.198.248 - - [08/Feb/2026 05:46:02] code 400, message Bad request version ('À\x14À') +3.140.198.248 - - [08/Feb/2026 05:46:02] "\x16\x03\x01\x00{\x01\x00\x00w\x03\x03\x86cÛèꬩ/Þ{j\x04½¼v¾\x1c\x126î§;KÞi\x1e@\x14-w\x11{\x00\x00\x1aÀ/À+À\x11À\x07À\x13À\x09À\x14À" HTTPStatus.BAD_REQUEST - +3.135.20.78 - - [08/Feb/2026 05:46:10] "GET / HTTP/1.1" 200 - +3.135.20.78 - - [08/Feb/2026 05:49:33] code 400, message Bad request version ('À\x14À') +3.135.20.78 - - [08/Feb/2026 05:49:33] "\x16\x03\x01\x00{\x01\x00\x00w\x03\x030}\x0eSĺ-e~\x1b\x1eÔX¹ºoñ\x00³Þ\x07ø¶ëz¡=G2\x17¹æ\x00\x00\x1aÀ/À+À\x11À\x07À\x13À\x09À\x14À" HTTPStatus.BAD_REQUEST - +3.135.20.78 - - [08/Feb/2026 05:51:04] code 400, message Bad request syntax ('SSH-2.0-Go') +3.135.20.78 - - [08/Feb/2026 05:51:04] "SSH-2.0-Go" HTTPStatus.BAD_REQUEST - +3.135.20.78 - - [08/Feb/2026 05:52:41] code 400, message Bad request version ('À\x14À') +3.135.20.78 - - [08/Feb/2026 05:52:41] "\x16\x03\x01\x00{\x01\x00\x00w\x03\x03x\x97\x18dSÎ\x0fOäúÈ\x97=>\x90Âäü\x02,«b\x93yO\x9d»\x85±È`´\x00\x00\x1aÀ/À+À\x11À\x07À\x13À\x09À\x14À" HTTPStatus.BAD_REQUEST - +18.97.5.10 - - [08/Feb/2026 07:16:52] code 400, message Bad request version ("z\x91H²Ëwc[\x7fLÕs\x01Üzá_þïÞ\x7fZk3LçÈ\x00V\x13\x02\x13\x03\x13\x01À,À0À+À/̨̩\x00\x9f\x00\x9e̪À¯À\xadÀ®À¬À$À(À#À'À") +18.97.5.10 - - [08/Feb/2026 07:16:52] "\x16\x03\x01\x02\x00\x01\x00\x01ü\x03\x03\x8bI\x0d÷^gÞv\x84×CEW.\x169T¿\x14SãÍyè\x88d)²\x03ÁÂn \x11Fý"\x1ez\x91H²Ëwc[\x7fLÕs\x01Üzá_þïÞ\x7fZk3LçÈ\x00V\x13\x02\x13\x03\x13\x01À,À0À+À/̨̩\x00\x9f\x00\x9e̪À¯À­À®À¬À$À(À#À'À" HTTPStatus.BAD_REQUEST - +18.97.5.10 - - [08/Feb/2026 07:16:52] "GET / HTTP/1.1" 200 - +198.235.24.90 - - [08/Feb/2026 08:01:41] "GET / HTTP/1.0" 200 - +18.97.5.65 - - [08/Feb/2026 08:07:07] code 400, message Bad request syntax ('\x16\x03\x01\x02\x00\x01\x00\x01ü\x03\x03Ô¹Céjì') +18.97.5.65 - - [08/Feb/2026 08:07:07] "\x16\x03\x01\x02\x00\x01\x00\x01ü\x03\x03Ô¹Céjì" HTTPStatus.BAD_REQUEST - +18.97.5.65 - - [08/Feb/2026 08:07:07] "GET / HTTP/1.1" 200 - +65.49.20.69 - - [08/Feb/2026 09:31:52] "GET / HTTP/1.1" 200 - +65.49.20.69 - - [08/Feb/2026 09:32:15] "GET /favicon.ico HTTP/1.1" 404 - +167.94.138.184 - - [08/Feb/2026 10:01:18] code 400, message Bad request version ('À\x14À') +167.94.138.184 - - [08/Feb/2026 10:01:18] "\x16\x03\x01\x00î\x01\x00\x00ê\x03\x03\x85yåÌ@\x87Ư\x1b«ÒZ\x0c\x98ÜÚ¦ñI\x14Ø\x8a\x09²û?OÜ÷÷\x80ª \x9dAürÕòp\x7f[ÇF©¸s&DU\x1d\x9eþ1\x1ekæ\x98}&@¤õ=­\x00&̨̩À/À0À+À,À\x13À\x09À\x14À" HTTPStatus.BAD_REQUEST - +167.94.138.184 - - [08/Feb/2026 10:01:18] "GET / HTTP/1.1" 200 - +167.94.138.184 - - [08/Feb/2026 10:01:19] code 505, message Invalid HTTP version (2.0) +167.94.138.184 - - [08/Feb/2026 10:01:19] "PRI * HTTP/2.0" HTTPStatus.HTTP_VERSION_NOT_SUPPORTED - +167.94.138.184 - - [08/Feb/2026 10:01:21] "GET /favicon.ico HTTP/1.1" 404 - +167.94.138.184 - - [08/Feb/2026 10:01:37] code 400, message Bad request version ('À\x14À') +167.94.138.184 - - [08/Feb/2026 10:01:37] "\x16\x03\x01\x00î\x01\x00\x00ê\x03\x03ø\x17Ò\x15_\x95ê\x11J7ZD\x10Ï\\x²¢B`\x988õ\x7f\x0e\x13²\x85Æ)µ# ì®Õ\x8b/ÛFéQî\x12DI¤_*dÔÑß½\x0b³e92Ä&\x94køP\x00&̨̩À/À0À+À,À\x13À\x09À\x14À" HTTPStatus.BAD_REQUEST - +167.94.138.184 - - [08/Feb/2026 10:01:38] "GET /.well-known/security.txt HTTP/1.1" 404 - +123.58.196.49 - - [08/Feb/2026 10:10:14] code 400, message Bad request version ('\x00\x9ę̪\x003\x00=\x00\x16À') +123.58.196.49 - - [08/Feb/2026 10:10:14] "\x16\x03\x01\x00ü\x01\x00\x00ø\x03\x03#Õ\x01É\x8fµ\x16Ø\x15\x9f\x82.£\x94^\x80»Hô­\x05$ 7ÊÝÂ\x1c\x84\x8f ; ÚÅÂâ\x07^¼uYì]Vït\x85çêl©±\x89ooÕ§ðW\x8anËD\x18\x004̨̩À/À0À+À,À\x09\x00\x9ę̪\x003\x00=\x00\x16À" HTTPStatus.BAD_REQUEST - +123.58.196.49 - - [08/Feb/2026 10:10:25] "GET / HTTP/1.1" 200 - +123.58.196.49 - - [08/Feb/2026 10:10:26] "GET /favicon.ico HTTP/1.1" 404 - +123.58.196.49 - - [08/Feb/2026 10:10:26] "GET /robots.txt HTTP/1.1" 404 - +123.58.196.49 - - [08/Feb/2026 10:10:27] "GET /sitemap.xml HTTP/1.1" 404 - +123.58.196.49 - - [08/Feb/2026 10:10:28] "GET /js/main.js HTTP/1.1" 200 - +123.58.196.49 - - [08/Feb/2026 10:10:28] "GET /js/api.js HTTP/1.1" 200 - +123.58.196.49 - - [08/Feb/2026 10:10:28] "GET /config.json HTTP/1.1" 404 - +178.159.37.112 - - [08/Feb/2026 10:18:36] code 400, message Bad HTTP/0.9 request type ('\x03\x00\x00/*à\x00\x00\x00\x00\x00Cookie:') +178.159.37.112 - - [08/Feb/2026 10:18:36] "\x03\x00\x00/*à\x00\x00\x00\x00\x00Cookie: mstshash=Administr" HTTPStatus.BAD_REQUEST - +198.235.24.214 - - [08/Feb/2026 10:36:31] "GET / HTTP/1.1" 200 - +143.198.48.172 - - [08/Feb/2026 10:57:36] "GET / HTTP/1.1" 200 - +143.198.48.172 - - [08/Feb/2026 10:57:37] "GET /favicon.ico HTTP/1.1" 404 - +178.159.37.112 - - [08/Feb/2026 11:03:51] code 400, message Bad HTTP/0.9 request type ('\x03\x00\x00/*à\x00\x00\x00\x00\x00Cookie:') +178.159.37.112 - - [08/Feb/2026 11:03:51] "\x03\x00\x00/*à\x00\x00\x00\x00\x00Cookie: mstshash=Administr" HTTPStatus.BAD_REQUEST - +178.159.37.112 - - [08/Feb/2026 11:41:49] code 400, message Bad HTTP/0.9 request type ('\x03\x00\x00/*à\x00\x00\x00\x00\x00Cookie:') +178.159.37.112 - - [08/Feb/2026 11:41:49] "\x03\x00\x00/*à\x00\x00\x00\x00\x00Cookie: mstshash=Administr" HTTPStatus.BAD_REQUEST - +198.235.24.86 - - [08/Feb/2026 12:32:36] "GET /.well-known/security.txt HTTP/1.1" 404 - +91.231.89.229 - - [08/Feb/2026 16:09:09] code 400, message Bad request version ("ñ'") +91.231.89.229 - - [08/Feb/2026 16:09:09] "\x16\x03\x03\x01¦\x01\x00\x01¢\x03\x03À3\x93Û£GCæÑìp:\x0dØú¼sÏÙ£3K=³·I|p\x1dÿ{¯ ñ'" HTTPStatus.BAD_REQUEST - +91.231.89.73 - - [08/Feb/2026 16:09:13] "GET / HTTP/1.1" 200 - +91.231.89.73 - - [08/Feb/2026 16:11:09] "GET /favicon.ico HTTP/1.1" 404 - +205.210.31.75 - - [08/Feb/2026 16:49:22] "GET /.well-known/security.txt HTTP/1.1" 404 - +204.76.203.51 - - [08/Feb/2026 16:54:53] "GET http://azenv.net/ HTTP/1.1" 200 - +104.244.79.113 - - [08/Feb/2026 18:18:33] code 400, message Bad request version ('À\x13À') +104.244.79.113 - - [08/Feb/2026 18:18:33] "\x16\x03\x01\x05¨\x01\x00\x05¤\x03\x03\x00\x8a/\\Ä\x0fãC»I[\x9f\x11¦Ü\x80\x96\x80±\x94\x97Ï\x9fô\x15ú\x81'\x10mô\\ E\x94ºÏ\x05\x0cp\x06(\x1bËV\x13x\x0d\x8a\x94´Æ%ÊhdÓÁ:RÐõMtû\x00\x1aÀ+À/À,À0̨̩À\x09À\x13À" HTTPStatus.BAD_REQUEST - +135.119.97.71 - - [08/Feb/2026 19:31:16] "GET /v2/ HTTP/1.1" 404 - +199.45.155.70 - - [08/Feb/2026 20:43:27] "GET / HTTP/1.1" 200 - +199.45.155.70 - - [08/Feb/2026 20:43:35] code 400, message Bad request version ('À\x14À') +199.45.155.70 - - [08/Feb/2026 20:43:35] "\x16\x03\x01\x00î\x01\x00\x00ê\x03\x03W¯{X\x8e¨_ø96\x18bø¹è\x0cý¼åpSV\\\x19)/ãaP¼\x11\x0c ¾/g©8ã¶F\x0f\x83u°6Àc\x816Cë\x08AËLï\\\x1fØ~Ò\x12ÀQ\x00&̨̩À/À0À+À,À\x13À\x09À\x14À" HTTPStatus.BAD_REQUEST - +199.45.155.70 - - [08/Feb/2026 20:43:37] "GET / HTTP/1.1" 200 - +199.45.155.70 - - [08/Feb/2026 20:43:39] code 505, message Invalid HTTP version (2.0) +199.45.155.70 - - [08/Feb/2026 20:43:39] "PRI * HTTP/2.0" HTTPStatus.HTTP_VERSION_NOT_SUPPORTED - +199.45.155.70 - - [08/Feb/2026 20:43:42] "GET /favicon.ico HTTP/1.1" 404 - +199.45.155.70 - - [08/Feb/2026 20:44:04] code 400, message Bad HTTP/0.9 request type ('\x16\x03\x01\x00î\x01\x00\x00ê\x03\x03\x9c\x13fÍÀ') +199.45.155.70 - - [08/Feb/2026 20:44:04] "\x16\x03\x01\x00î\x01\x00\x00ê\x03\x03\x9c\x13fÍÀ\x0d½\x88=\x1aÝl#µ¡" HTTPStatus.BAD_REQUEST - +199.45.155.70 - - [08/Feb/2026 20:44:08] "GET /sitemap.xml HTTP/1.1" 404 - +204.76.203.51 - - [08/Feb/2026 20:51:12] "GET http://azenv.net/ HTTP/1.1" 200 - +82.147.84.134 - - [08/Feb/2026 23:14:21] code 400, message Bad request syntax ('\x03\x00\x00\x13\x0eà\x00\x00\x00\x00\x00\x01\x00\x08\x00\x02\x00\x00\x00') +82.147.84.134 - - [08/Feb/2026 23:14:21] "\x03\x00\x00\x13\x0eà\x00\x00\x00\x00\x00\x01\x00\x08\x00\x02\x00\x00\x00" HTTPStatus.BAD_REQUEST - +204.76.203.51 - - [09/Feb/2026 00:47:03] "GET http://azenv.net/ HTTP/1.1" 200 - +204.76.203.51 - - [09/Feb/2026 00:47:04] "CONNECT dnspod.qcloud.com:443 HTTP/1.1" 405 - +93.123.109.160 - - [09/Feb/2026 01:02:18] code 400, message Bad HTTP/0.9 request type ('\x03\x00\x00/*à\x00\x00\x00\x00\x00Cookie:') +93.123.109.160 - - [09/Feb/2026 01:02:18] "\x03\x00\x00/*à\x00\x00\x00\x00\x00Cookie: mstshash=Administr" HTTPStatus.BAD_REQUEST - +204.76.203.51 - - [09/Feb/2026 04:45:23] "GET http://azenv.net/ HTTP/1.1" 200 - +204.76.203.51 - - [09/Feb/2026 04:45:27] "CONNECT dnspod.qcloud.com:443 HTTP/1.1" 405 - +176.65.149.182 - - [09/Feb/2026 05:34:02] "CONNECT ipv4-internet.yandex.net:443 HTTP/1.1" 405 - +205.210.31.49 - - [09/Feb/2026 06:41:48] "GET / HTTP/1.0" 200 - +164.92.251.121 - - [09/Feb/2026 07:34:32] "GET / HTTP/1.0" 200 - +204.76.203.51 - - [09/Feb/2026 08:42:53] "GET http://azenv.net/ HTTP/1.1" 200 - +204.76.203.51 - - [09/Feb/2026 08:42:55] "CONNECT dnspod.qcloud.com:443 HTTP/1.1" 405 - +111.33.154.219 - - [09/Feb/2026 08:48:21] "HEAD / HTTP/1.1" 200 - +205.210.31.241 - - [09/Feb/2026 10:04:58] "GET / HTTP/1.1" 200 - +43.247.4.40 - - [09/Feb/2026 12:02:02] "HEAD / HTTP/1.1" 200 - +180.101.244.14 - - [09/Feb/2026 12:09:55] "GET / HTTP/1.1" 200 - +159.75.198.125 - - [09/Feb/2026 12:09:55] "GET /js/api.js HTTP/1.1" 200 - +180.101.245.252 - - [09/Feb/2026 12:10:59] "GET / HTTP/1.1" 200 - +159.75.198.232 - - [09/Feb/2026 12:11:00] "GET /js/api.js HTTP/1.1" 200 - +220.196.160.61 - - [09/Feb/2026 12:11:00] "GET / HTTP/1.1" 200 - +220.196.160.61 - - [09/Feb/2026 12:11:00] "GET /css/style.css HTTP/1.1" 200 - +220.196.160.61 - - [09/Feb/2026 12:11:00] "GET /js/api.js HTTP/1.1" 200 - +220.196.160.61 - - [09/Feb/2026 12:11:00] "GET /js/main.js HTTP/1.1" 200 - +204.76.203.51 - - [09/Feb/2026 12:38:49] "GET http://azenv.net/ HTTP/1.1" 200 - +204.76.203.51 - - [09/Feb/2026 12:38:52] "CONNECT dnspod.qcloud.com:443 HTTP/1.1" 405 - +93.123.109.160 - - [09/Feb/2026 14:57:32] code 400, message Bad HTTP/0.9 request type ('\x03\x00\x00/*à\x00\x00\x00\x00\x00Cookie:') +93.123.109.160 - - [09/Feb/2026 14:57:32] "\x03\x00\x00/*à\x00\x00\x00\x00\x00Cookie: mstshash=Administr" HTTPStatus.BAD_REQUEST - +206.168.34.117 - - [09/Feb/2026 15:08:19] code 400, message Bad request version ('À\x14À') +206.168.34.117 - - [09/Feb/2026 15:08:19] "\x16\x03\x01\x00î\x01\x00\x00ê\x03\x03¹ÕHÎç:ßW\x10:Î\x8dênrUN`\x93Ò\x0fÕuËQLM\x0f(Ì\x9cZ ¦3\x9d\x8b-È\x8dÜD\x86xà\x04X ©£Ú'\\ìÄÆÞ\x8abñ\x9eÉ=3Á\x00&̨̩À/À0À+À,À\x13À\x09À\x14À" HTTPStatus.BAD_REQUEST - +206.168.34.117 - - [09/Feb/2026 15:08:26] "GET / HTTP/1.1" 200 - +206.168.34.117 - - [09/Feb/2026 15:08:36] code 400, message Bad request version ('À\x14À') +206.168.34.117 - - [09/Feb/2026 15:08:36] "\x16\x03\x01\x00î\x01\x00\x00ê\x03\x03¹\x8fÐBuÇ\x9fæ\x0f\x17óä\x0fÉ\x16p6\\,Å\x94É|Ãù\x82[\x0c\x80¿\x9dW ¸²¿Úïe¦qÊ* 0\x920\x17\x9202\x07åw^­£§ª<\x10L®CÓ\x00&̨̩À/À0À+À,À\x13À\x09À\x14À" HTTPStatus.BAD_REQUEST - +206.168.34.117 - - [09/Feb/2026 15:08:39] "GET / HTTP/1.1" 200 - +206.168.34.117 - - [09/Feb/2026 15:08:41] code 505, message Invalid HTTP version (2.0) +206.168.34.117 - - [09/Feb/2026 15:08:41] "PRI * HTTP/2.0" HTTPStatus.HTTP_VERSION_NOT_SUPPORTED - +206.168.34.117 - - [09/Feb/2026 15:08:55] code 400, message Bad request version ('À\x14À') +206.168.34.117 - - [09/Feb/2026 15:08:55] "\x16\x03\x01\x00î\x01\x00\x00ê\x03\x03\\\x94ø\x80\x8b|\x84 j\x16¢·MVL\x94°%g\x8ciù®mRó±\x00&̨̩À/À0À+À,À\x13À\x09À\x14À" HTTPStatus.BAD_REQUEST - +206.168.34.117 - - [09/Feb/2026 15:08:57] "GET /.well-known/security.txt HTTP/1.1" 404 - +193.32.162.101 - - [09/Feb/2026 15:32:23] code 400, message Bad request syntax ('SSH-2.0-Go') +193.32.162.101 - - [09/Feb/2026 15:32:23] "SSH-2.0-Go" HTTPStatus.BAD_REQUEST - +193.32.162.25 - - [09/Feb/2026 15:36:19] code 400, message Bad request syntax ('SSH-2.0-Go') +193.32.162.25 - - [09/Feb/2026 15:36:19] "SSH-2.0-Go" HTTPStatus.BAD_REQUEST - +18.219.245.162 - - [09/Feb/2026 16:26:57] "GET / HTTP/1.1" 200 - +18.219.245.162 - - [09/Feb/2026 16:28:45] "GET / HTTP/1.1" 200 - +18.219.245.162 - - [09/Feb/2026 16:29:19] code 400, message Bad request version ('À\x14À') +18.219.245.162 - - [09/Feb/2026 16:29:19] "\x16\x03\x01\x00{\x01\x00\x00w\x03\x03^\\ßó½w\x02¼\x97Í×\x9aÄ@a\x1e´9ÕÊ¿º\x87ÆëÏ\x1d<Ñ­\x09m\x00\x00\x1aÀ/À+À\x11À\x07À\x13À\x09À\x14À" HTTPStatus.BAD_REQUEST - +18.219.245.162 - - [09/Feb/2026 16:30:27] code 400, message Bad request syntax ('SSH-2.0-Go') +18.219.245.162 - - [09/Feb/2026 16:30:27] "SSH-2.0-Go" HTTPStatus.BAD_REQUEST - +18.219.245.162 - - [09/Feb/2026 16:31:36] code 400, message Bad request version ('À\x14À') +18.219.245.162 - - [09/Feb/2026 16:31:36] "\x16\x03\x01\x00{\x01\x00\x00w\x03\x03³\x97ïtá\x08\\Ò\x1b\x86Ê\x0d¹wÎ3Ho'Ü@Y\x1a^Ô\x95\x00½\x00\x04ÖÕ\x00\x00\x1aÀ/À+À\x11À\x07À\x13À\x09À\x14À" HTTPStatus.BAD_REQUEST - +204.76.203.51 - - [09/Feb/2026 16:34:40] "GET http://azenv.net/ HTTP/1.1" 200 - +204.76.203.51 - - [09/Feb/2026 16:34:41] "CONNECT dnspod.qcloud.com:443 HTTP/1.1" 405 - +111.33.154.219 - - [09/Feb/2026 19:29:07] "HEAD / HTTP/1.1" 200 - +221.238.131.250 - - [09/Feb/2026 20:30:19] "HEAD / HTTP/1.1" 200 - +204.76.203.51 - - [09/Feb/2026 21:45:17] "GET http://azenv.net/ HTTP/1.1" 200 - +82.147.84.53 - - [09/Feb/2026 21:52:52] code 400, message Bad request syntax ('\x03\x00\x00\x13\x0eà\x00\x00\x00\x00\x00\x01\x00\x08\x00\x02\x00\x00\x00') +82.147.84.53 - - [09/Feb/2026 21:52:52] "\x03\x00\x00\x13\x0eà\x00\x00\x00\x00\x00\x01\x00\x08\x00\x02\x00\x00\x00" HTTPStatus.BAD_REQUEST - +163.172.113.209 - - [09/Feb/2026 22:19:27] "GET / HTTP/1.1" 200 - +163.172.113.209 - - [09/Feb/2026 22:19:28] "GET /favicon.ico HTTP/1.1" 404 - +163.172.113.209 - - [09/Feb/2026 22:19:29] "GET /HNAP1 HTTP/1.1" 404 - +163.172.113.209 - - [09/Feb/2026 22:19:29] "POST /onvif/device_service HTTP/1.1" 405 - +163.172.113.209 - - [09/Feb/2026 22:19:30] "GET /PSIA/index HTTP/1.1" 404 - +204.76.203.51 - - [09/Feb/2026 22:57:41] "GET http://azenv.net/ HTTP/1.1" 200 - +93.123.109.160 - - [10/Feb/2026 02:36:38] code 400, message Bad HTTP/0.9 request type ('\x03\x00\x00/*à\x00\x00\x00\x00\x00Cookie:') +93.123.109.160 - - [10/Feb/2026 02:36:38] "\x03\x00\x00/*à\x00\x00\x00\x00\x00Cookie: mstshash=Administr" HTTPStatus.BAD_REQUEST - +204.76.203.51 - - [10/Feb/2026 02:54:48] "GET http://azenv.net/ HTTP/1.1" 200 - +204.76.203.51 - - [10/Feb/2026 02:54:51] "CONNECT dnspod.qcloud.com:443 HTTP/1.1" 405 - +147.185.132.103 - - [10/Feb/2026 04:05:12] "GET / HTTP/1.1" 200 - +204.76.203.51 - - [10/Feb/2026 06:51:58] "GET http://azenv.net/ HTTP/1.1" 200 - +204.76.203.51 - - [10/Feb/2026 06:52:00] "CONNECT dnspod.qcloud.com:443 HTTP/1.1" 405 - +111.7.96.146 - - [10/Feb/2026 07:08:11] code 400, message Bad request version ('RTSP/1.0') +111.7.96.146 - - [10/Feb/2026 07:08:11] "OPTIONS / RTSP/1.0" HTTPStatus.BAD_REQUEST - +111.7.96.146 - - [10/Feb/2026 07:08:34] code 400, message Bad request version ('À\x13À') +111.7.96.146 - - [10/Feb/2026 07:08:34] "\x16\x03\x01\x00î\x01\x00\x00ê\x03\x03I\x06²ù¼\x99iÀYûøºe-\x83Ud]\x0e\x8d·b8´gÆÝ9dZ8f gÁ\x81i³öò˰©\x19NÖ½\x95g|püé 分析日期:2026-02-10 +> 分析范围:项目架构、前端、后端、API、数据、测试、部署及问题汇总 + +--- + +## 目录 + +1. [项目概览](#1-项目概览) +2. [项目结构](#2-项目结构) +3. [技术栈总览](#3-技术栈总览) +4. [后端分析](#4-后端分析) +5. [前端分析](#5-前端分析) +6. [API 接口分析](#6-api-接口分析) +7. [数据流分析](#7-数据流分析) +8. [第三方依赖与外部服务](#8-第三方依赖与外部服务) +9. [测试分析](#9-测试分析) +10. [部署现状](#10-部署现状) +11. [已有文档评估](#11-已有文档评估) +12. [存在的问题与风险](#12-存在的问题与风险) +13. [优化建议](#13-优化建议) +14. [总结](#14-总结) + +--- + +## 1. 项目概览 + +### 1.1 产品定位 + +这是一个基于 H5 的 **AI 发型更换与发色更换应用**,用户可以: + +- **上传照片** → **选择发型** → 获取 AI 换发型后的效果图 +- **上传照片** → **选择颜色和强度** → 获取 AI 换发色后的效果图 + +核心价值:帮助用户在剪发/染发前,直观预览不同发型和发色效果。 + +### 1.2 功能模块 + +| 模块 | 功能 | 状态 | +|------|------|------| +| 图片上传 | 支持上传本地照片 | ✅ 已实现 | +| 发型更换 | 从发型库选择发型,AI 替换 | ✅ 已实现 | +| 发色更换 | 通过颜色选择器选色,AI 替换发色 | ✅ 已实现 | +| 结果预览 | 查看效果图,支持保存下载 | ✅ 已实现 | +| 拍照功能 | 移动端拍照 | ❌ PRD中提及但未实现 | +| 性别分类 | 按男/女分类发型 | ❌ PRD中提及但未实现 | +| 分享功能 | 社交分享 | ❌ PRD中提及但未实现 | + +--- + +## 2. 项目结构 + +``` +hair_change_color/ +├── backend/ # 后端 Python/Flask 服务 +│ ├── app.py # Flask 主应用(391行)- 路由、API、业务逻辑 +│ ├── config.py # 配置文件(20行)- API URL、超时、文件限制 +│ ├── data/ +│ │ └── hairstyles.json # 发型数据(实际未使用,数据从 hairId2url.txt 加载) +│ ├── requirements.txt # Python 依赖(Flask, Flask-CORS, requests) +│ ├── app.log # Gunicorn 日志 +│ └── nohup.out # 开发服务器日志 +├── frontend/ # 前端 H5 页面 +│ ├── index.html # 主页面(142行)- 单页应用 +│ ├── css/ +│ │ └── style.css # 样式文件(393行)- 响应式设计 +│ ├── js/ +│ │ ├── main.js # 主逻辑(452行)- 状态管理、交互、API调用 +│ │ └── api.js # API封装(32行)- 实际未被引用 +│ └── frontend.log # 前端日志 +├── docs/ # 项目文档 +│ ├── PRD.md # 产品需求文档 +│ ├── ARCHITECTURE.md # 系统架构文档 +│ ├── API.md # API接口文档 +│ └── DEPLOYMENT.md # 部署文档 +├── test/ # 测试目录 +│ ├── test_hair.py # 发型更换API压测脚本 +│ └── wertwert.jpg # 测试用图片 +└── hairId2url.txt # 发型ID到OSS URL的映射文件(60条记录) +``` + +--- + +## 3. 技术栈总览 + +### 3.1 前端 + +| 技术 | 用途 | +|------|------| +| HTML5 | 页面结构,单页应用 | +| CSS3 | 响应式布局、动画、Flexbox/Grid | +| JavaScript (ES6+) | 交互逻辑、DOM操作 | +| Axios (CDN) | HTTP 请求 | +| FileReader API | 本地图片读取预览 | + +### 3.2 后端 + +| 技术 | 用途 | +|------|------| +| Python 3.8+ | 核心语言 | +| Flask | Web 框架 | +| Flask-CORS | 跨域支持 | +| requests | 远程 API 调用 | +| Gunicorn | 生产部署 WSGI 服务器 | + +### 3.3 外部服务 + +| 服务 | 地址 | 用途 | +|------|------|------| +| 发型替换 API | `http://xiangsilian.com:18801/api/swapHair/v1` | AI 发型替换 | +| 发色替换 API | `http://xiangsilian.com:18801/hairColor/v2` | AI 发色替换 | +| 阿里云 OSS | `xiangsilian.oss-cn-beijing.aliyuncs.com` | 发型图片存储 | + +--- + +## 4. 后端分析 + +### 4.1 应用入口 (`app.py`) + +Flask 应用作为 **API 网关/代理层**,主要职责: +1. 管理发型数据,向前端提供发型列表 +2. 接收用户上传的图片,转发给远程 AI API 处理 +3. 将处理结果(base64图片)返回前端 +4. 开发模式下提供前端静态文件 + +### 4.2 配置管理 (`config.py`) + +```python +APP_NAME = "Hair Change Color" +REMOTE_API_URL = "http://xiangsilian.com:18801/api/swapHair/v1" +REMOTE_HAIR_COLOR_API_URL = "http://xiangsilian.com:18801/hairColor/v2" +API_TIMEOUT = 120 # 超时120秒(远程AI处理耗时较长) +MAX_CONTENT_LENGTH = 16MB # 上传文件限制 +ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} +DEBUG = True +``` + +### 4.3 数据加载机制 + +- 启动时从 `../hairId2url.txt` 读取发型 ID 与 OSS URL 的映射 +- 解析规则:`文件名.jpg -> OSS URL`,提取文件名作为 ID +- 自动生成发型名称:`发型{ID后4位}` +- 共 **60 个发型** 可供选择 +- **注意**:`data/hairstyles.json` 文件存在但实际未被使用 + +### 4.4 核心处理流程 + +#### 发型更换流程 + +``` +用户上传图片 + 选择发型ID + ↓ +后端接收 (POST /api/change-hair) + ↓ +验证文件类型和参数 + ↓ +图片转 base64 + ↓ +调用远程 API (swapHair/v1) + - 参数: hair_id, task_id, user_img_path(base64), is_hr, output_format + ↓ +解析响应,提取处理后的图片 + ↓ +返回 base64 data URL 给前端 +``` + +#### 发色更换流程 + +``` +用户上传图片 + 选择RGB颜色 + 设置强度(ratio) + ↓ +后端接收 (POST /api/change-hair-color) + ↓ +验证文件类型和参数 + ↓ +图片转 base64,解析 RGB 数组 + ↓ +调用远程 API (hairColor/v2) + - 参数: img(base64), userId, rgb, ratio, output_format + ↓ +解析响应,提取处理后的图片 + ↓ +返回 base64 data URL 给前端 +``` + +### 4.5 错误处理策略 + +| 类型 | 状态码 | 处理方式 | +|------|--------|----------| +| 缺少图片/参数 | 400 | 返回中文错误消息 | +| 文件类型不合法 | 400 | 返回错误消息 | +| 远程API失败 | 500 | 返回原图作为降级方案 | +| 其他异常 | 500 | 返回通用错误消息 | + +--- + +## 5. 前端分析 + +### 5.1 页面结构(单页应用) + +采用 **手动页面切换** 的单页应用模式(无路由库): + +| 页面 | DOM ID | 功能 | +|------|--------|------| +| 首页 | `homePage` | 图片上传 + 功能入口(换发型/换发色) | +| 发型选择页 | `hairstylePage` | 发型网格列表,点击选择 | +| 发色选择页 | `hairColorPage` | 颜色选择器 + 强度滑块 | +| 结果预览页 | `resultPage` | 结果展示 + 重选/保存操作 | + +### 5.2 状态管理 + +使用全局变量管理状态: + +```javascript +uploadedImage // 上传的文件对象 +selectedHairstyle // 选中的发型ID +hairstylesData // 缓存的发型列表 +selectedColor // 选中的颜色(hex) +selectedRatio // 颜色强度(0-1) +currentFunction // 当前模式:'hairstyle' | 'color' +``` + +### 5.3 用户交互流程 + +``` +首页 + ├── 上传图片 → 显示预览 + ├── 点击"更换发型" → 发型选择页 + │ ├── 选择发型 → 高亮反馈 + │ └── 确认 → Loading → 结果页 + └── 点击"更换头发颜色" → 发色选择页 + ├── 选择颜色 → 实时预览RGB值 + ├── 调节强度 → 滑块 + └── 确认 → Loading → 结果页 + +结果页 + ├── 重新选择发型/颜色 + ├── 重新上传图片 + └── 保存结果(下载图片) +``` + +### 5.4 UI设计 + +- **设计风格**:现代简约,蓝色(#4A90E2)为主色调,青色(#50E3C2)为辅色 +- **响应式**:三个断点(移动端 < 768px / 平板 768-1024px / PC > 1024px) +- **交互效果**:悬停阴影、选中高亮边框、加载动画 +- **导航**:顶部固定导航栏 + 返回按钮 + +### 5.5 关键工具函数 + +| 函数 | 功能 | +|------|------| +| `hexToRgb()` | 将 hex 颜色值转换为 RGB 对象 | +| `showPage()` | 管理页面可见性切换 | +| `showLoading()` / `hideLoading()` | 全屏加载遮罩控制 | +| `getHairstyles()` | 获取发型列表 | +| `changeHair()` | 调用发型更换 API | +| `changeHairColor()` | 调用发色更换 API | + +--- + +## 6. API 接口分析 + +### 6.1 接口总览 + +| 接口 | 方法 | 功能 | 认证 | +|------|------|------|------| +| `/api/hairstyles` | GET | 获取发型列表 | 无 | +| `/api/change-hair` | POST | AI 发型替换 | 无 | +| `/api/change-hair-color` | POST | AI 发色替换 | 无 | +| `/` 及 `/` | GET | 静态文件服务 | 无 | + +### 6.2 接口详情 + +#### GET `/api/hairstyles` + +**响应示例:** +```json +{ + "success": true, + "data": { + "hairstyles": [ + { + "id": "1953267161121464322", + "name": "发型4322", + "image_url": "https://xiangsilian.oss-cn-beijing.aliyuncs.com/hair_images/1953267161121464322.jpg" + } + ] + } +} +``` + +#### POST `/api/change-hair` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `image` | File | 是 | 用户照片 | +| `hairstyle_id` | String | 是 | 发型 ID | + +**响应:** `{ "success": true, "data": { "result_image": "data:image/jpeg;base64,..." } }` + +#### POST `/api/change-hair-color` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `image` | File | 是 | 用户照片 | +| `rgb` | String | 是 | RGB 数组字符串,如 `"[255, 106, 0]"` | +| `ratio` | String | 是 | 替换强度 0.0-1.0 | + +**响应:** `{ "success": true, "data": { "result_image": "data:image/jpeg;base64,..." } }` + +### 6.3 API 文档与实现差异 + +| 项目 | 文档描述 | 实际实现 | +|------|----------|----------| +| `/api/change-hair-color` | 文档中 **未记录** | 已实现 | +| 发型数据源 | `data/hairstyles.json` | 实际从 `hairId2url.txt` 加载 | +| 性别分类 | 文档描述有 `gender` 字段 | 未实现 | +| 响应格式 | 统一 `message` 字段 | 部分接口缺少 `message` 字段 | + +--- + +## 7. 数据流分析 + +``` +┌──────────────┐ HTTP ┌──────────────┐ HTTP ┌──────────────────┐ +│ 前端 H5 │ ◄──────────► │ Flask 后端 │ ◄──────────► │ 远程 AI API │ +│ (浏览器) │ JSON/Base64 │ (代理层) │ JSON/Base64 │ xiangsilian.com │ +└──────────────┘ └──────────────┘ └──────────────────┘ + │ │ │ + │ 1. 上传图片+参数 │ │ + │ ─────────────────────────► │ │ + │ │ 2. base64编码+转发 │ + │ │ ────────────────────────────► │ + │ │ │ + │ │ 3. AI处理结果(base64) │ + │ │ ◄──────────────────────────── │ + │ 4. 返回结果图片 │ │ + │ ◄───────────────────────── │ │ + │ │ │ + │ 发型图片 │ │ + │ ◄─────────────────────────────────── 阿里云 OSS ─────────┘ +``` + +**数据传输特点:** +- 图片全程使用 **base64 编码** 传输,不存储在服务器文件系统 +- 远程 API 超时设置为 **120秒**(AI 处理耗时较长) +- 发型缩略图直接从 **阿里云 OSS** CDN 加载 + +--- + +## 8. 第三方依赖与外部服务 + +### 8.1 Python 依赖 + +| 包名 | 用途 | 风险评估 | +|------|------|----------| +| Flask | Web 框架 | 低 - 成熟稳定 | +| Flask-CORS | 跨域支持 | 低 | +| requests | HTTP 客户端 | 低 | + +### 8.2 前端依赖 + +| 库 | 来源 | 用途 | +|----|------|------| +| Axios | CDN (jsdelivr) | HTTP 请求 | + +### 8.3 外部服务依赖 + +| 服务 | 可控性 | 风险 | +|------|--------|------| +| AI API (xiangsilian.com:18801) | **不可控** | 高 - 服务不可用则核心功能失效 | +| 阿里云 OSS | 可控 | 中 - 影响发型图片展示 | + +--- + +## 9. 测试分析 + +### 9.1 现有测试 + +项目仅有一个测试文件 `test/test_hair.py`,功能为: +- **压力测试脚本**:多线程并发请求发型更换 API +- 将本地图片编码为 base64 发送请求 +- 记录 QPS 和总耗时 +- 保存 API 返回的结果图片 + +### 9.2 测试覆盖不足 + +| 测试类型 | 状态 | +|----------|------| +| 单元测试 | ❌ 无 | +| 接口测试 | ❌ 无(仅有手动压测脚本) | +| 前端测试 | ❌ 无 | +| 端到端测试 | ❌ 无 | +| 发色更换测试 | ❌ 无 | +| 错误场景测试 | ❌ 无 | + +--- + +## 10. 部署现状 + +### 10.1 当前部署方式 + +根据日志文件分析: + +| 组件 | 部署方式 | 端口 | +|------|----------|------| +| 后端 | Gunicorn (生产) / Flask dev server (开发) | 48080 / 5000 | +| 前端 | 由后端 Flask 提供静态文件 | 同后端 | + +### 10.2 存在的问题 + +- 日志显示 **Gunicorn worker 超时**,表明远程 API 响应慢导致请求阻塞 +- 日志中有大量 **非法 HTTP 请求**,可能是扫描/攻击流量 +- `DEBUG = True` 在生产环境中仍然开启 +- 使用 **HTTP**(非 HTTPS)暴露在公网 + +--- + +## 11. 已有文档评估 + +| 文档 | 文件 | 完整性 | 准确性 | 备注 | +|------|------|--------|--------|------| +| 产品需求文档 | `docs/PRD.md` | ⭐⭐⭐ | ⭐⭐ | 缺少发色更换功能描述,部分功能未实现 | +| 架构文档 | `docs/ARCHITECTURE.md` | ⭐⭐⭐ | ⭐⭐ | 缺少发色更换 API 描述,数据源描述与实际不符 | +| API 文档 | `docs/API.md` | ⭐⭐ | ⭐⭐ | 缺少 `/api/change-hair-color` 接口 | +| 部署文档 | `docs/DEPLOYMENT.md` | ⭐⭐⭐⭐ | ⭐⭐⭐ | 较为完整但缺少实际端口配置 | + +**总体评估**:文档在项目初期创建,后续代码演进(新增发色更换功能、数据源更改等)后 **未同步更新**。 + +--- + +## 12. 存在的问题与风险 + +### 12.1 代码质量问题 + +| 严重度 | 问题 | 位置 | 说明 | +|--------|------|------|------| +| 🔴 高 | `api.js` 未被引用 | `frontend/js/api.js` | 使用 ES6 export 但 main.js 未 import,API 函数在 main.js 中重复定义 | +| 🔴 高 | `userId` 硬编码 | `backend/app.py` | 发色 API 中 userId 硬编码为 `"18701620166"` | +| 🟡 中 | 拼写错误 | `frontend/js/main.js` | `disabled` 拼写为 `disbled` | +| 🟡 中 | `hairstyles.json` 未使用 | `backend/data/` | 存在但不被加载,容易误导 | +| 🟡 中 | 无环境变量支持 | `backend/config.py` | 所有配置硬编码,无法按环境切换 | +| 🟢 低 | 代码重复 | `backend/app.py` | `change_hair` 和 `change_hair_color` 有大量重复逻辑 | + +### 12.2 安全问题 + +| 严重度 | 问题 | 说明 | +|--------|------|------| +| 🔴 高 | 无认证机制 | 所有 API 完全公开,无任何访问控制 | +| 🔴 高 | 无限流保护 | 无 Rate Limiting,可被恶意调用 | +| 🔴 高 | DEBUG 模式生产环境开启 | 可能泄露敏感信息 | +| 🔴 高 | HTTP 明文传输 | 未使用 HTTPS | +| 🟡 中 | CORS 全开 | 允许所有来源跨域请求 | +| 🟡 中 | 无请求体大小验证 | 虽配置了 16MB 限制,但缺少前端校验 | + +### 12.3 可靠性问题 + +| 严重度 | 问题 | 说明 | +|--------|------|------| +| 🔴 高 | 远程 API 单点依赖 | 远程 AI API 不可用则核心功能全部失效 | +| 🔴 高 | 同步阻塞处理 | 请求阻塞最长120秒,占用 worker 进程 | +| 🟡 中 | 无重试机制 | 远程 API 调用失败不会重试 | +| 🟡 中 | 错误降级返回原图 | 用户可能不知道处理失败 | +| 🟡 中 | Worker 超时 | Gunicorn 日志显示频繁 worker timeout | + +### 12.4 文档与代码不一致 + +| 问题 | 说明 | +|------|------| +| 发色更换 API 未记录 | 已实现的 `/api/change-hair-color` 未出现在 API 文档中 | +| 数据源不一致 | 文档说使用 `hairstyles.json`,实际使用 `hairId2url.txt` | +| 目录结构过时 | 文档中的目录结构缺少 `test/`、`hairId2url.txt` 等 | +| PRD 与实现偏差 | 性别分类、拍照功能、分享功能均未实现 | + +--- + +## 13. 优化建议 + +### 13.1 优先级高(建议立即修复) + +1. **关闭生产环境 DEBUG 模式** + - `config.py` 中 `DEBUG = True` 改为通过环境变量控制 + +2. **添加基础安全防护** + - 添加 Rate Limiting(如 `flask-limiter`) + - 配置 CORS 白名单,而非允许所有来源 + - 启用 HTTPS + +3. **修复代码缺陷** + - 修复 `main.js` 中的拼写错误 + - 移除或整合未使用的 `api.js` + - 清理未使用的 `hairstyles.json` + +4. **同步更新文档** + - API 文档补充 `/api/change-hair-color` 接口 + - 更新架构文档中的数据源描述 + +### 13.2 优先级中(建议近期优化) + +5. **引入环境变量配置** + ```python + import os + REMOTE_API_URL = os.getenv('REMOTE_API_URL', 'http://...') + DEBUG = os.getenv('FLASK_DEBUG', 'false').lower() == 'true' + ``` + +6. **异步处理优化** + - 使用 `gevent` 或 `asyncio` 替代同步阻塞 + - 或使用任务队列(如 Celery)处理长耗时请求 + +7. **增加错误提示** + - 远程 API 失败时明确告知用户"处理失败",而非默默返回原图 + +8. **添加基础测试** + - 编写 API 接口单元测试 + - 添加参数校验边界测试 + +### 13.3 优先级低(长期改进) + +9. **前端重构** + - 考虑使用 Vue/React 等框架重构 + - 引入前端构建工具(Vite 等) + +10. **添加缓存层** + - 对相同图片+相同发型的请求结果进行缓存 + +11. **完善监控** + - 引入结构化日志 + - 添加 API 调用指标监控 + +12. **实现 PRD 中未完成的功能** + - 性别分类筛选 + - 移动端拍照 + - 社交分享 + +--- + +## 14. 总结 + +### 14.1 项目整体评价 + +| 维度 | 评分 | 说明 | +|------|------|------| +| 功能完成度 | ⭐⭐⭐ | 核心功能(换发型+换发色)已实现,部分 PRD 功能未实现 | +| 代码质量 | ⭐⭐ | 结构清晰但存在代码重复、未使用文件、拼写错误等问题 | +| 架构设计 | ⭐⭐⭐ | 前后端分离合理,代理模式设计得当 | +| 安全性 | ⭐ | 缺少认证、限流、HTTPS 等基本安全措施 | +| 可维护性 | ⭐⭐ | 文档与代码不同步,缺少测试覆盖 | +| 可扩展性 | ⭐⭐⭐ | 架构简单,扩展方便 | +| 部署运维 | ⭐⭐ | 有基础部署但缺少监控和自动化 | + +### 14.2 一句话总结 + +> 这是一个功能可用的 **AI 发型/发色更换 H5 应用**,采用经典的 Flask 代理 + 原生前端架构,核心业务逻辑依赖远程 AI API。项目已具备基本功能但在 **安全防护、测试覆盖、文档维护** 方面存在明显不足,建议优先解决安全问题和文档同步,再逐步优化架构和代码质量。 diff --git a/frontend/frontend.log b/frontend/frontend.log new file mode 100644 index 0000000..e160144 --- /dev/null +++ b/frontend/frontend.log @@ -0,0 +1,70 @@ +nohup: ignoring input +212.73.148.13 - - [05/Feb/2026 18:50:49] "GET / HTTP/1.1" 200 - +212.73.148.13 - - [05/Feb/2026 18:50:51] code 404, message File not found +212.73.148.13 - - [05/Feb/2026 18:50:51] "GET /favicon.ico HTTP/1.1" 404 - +111.172.7.26 - - [05/Feb/2026 19:02:15] "GET / HTTP/1.1" 200 - +111.172.7.26 - - [05/Feb/2026 19:02:15] "GET /css/style.css HTTP/1.1" 200 - +111.172.7.26 - - [05/Feb/2026 19:02:15] "GET /js/api.js HTTP/1.1" 200 - +111.172.7.26 - - [05/Feb/2026 19:02:16] "GET /js/main.js HTTP/1.1" 200 - +111.172.7.26 - - [05/Feb/2026 19:02:29] code 400, message Bad request version ('\\x00\\x12\\x00\\x10\\x04\\x03\\x08\\x04\\x04\\x01\\x05\\x03\\x08\\x05\\x05\\x01\\x08\\x06\\x06\\x01\\x00-\\x00\\x02\\x01\\x01\\x00') +111.172.7.26 - - [05/Feb/2026 19:02:29] "\x16\x03\x01\x02\x00\x01\x00\x01ü\x03\x03F]|K\x1b\x8d\x0bh\x981\x09¢B\x82\x81e\x9b\x06¡\x98ñ¾\x0e\x81ʤ0@o°M© ×d°rÁ½$=ël\x0f\x1afÀÓc¿q£\x13\x0b\x9e|\x7fT;\x7fKàs\x96ö\x00 **\x13\x01\x13\x02\x13\x03À+À/À,À0̨̩À\x13À\x14\x00\x9c\x00\x9d\x00/\x005\x01\x00\x01\x93::\x00\x00\x00\x1b\x00\x03\x02\x00\x02\x00#\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x10\x00\x0e\x00\x0c\x02h2\x08http/1.1\x003\x00+\x00)\x9a\x9a\x00\x01\x00\x00\x1d\x00 Ô\x80Nr!;Ý\x13Ð\x1a\x1e\x8fZ\x98xÝ\x84Î:\x914\x8bú)k«\x877\x9df&F\x00\x00\x00\x14\x00\x12\x00\x00\x0fxiangsilian.com\x00+\x00\x07\x06êê\x03\x04\x03\x03ÿ\x01\x00\x01\x00\x00\x12\x00\x00Di\x00\x05\x00\x03\x02h2\x00\x17\x00\x00\x00\x0d\x00\x12\x00\x10\x04\x03\x08\x04\x04\x01\x05\x03\x08\x05\x05\x01\x08\x06\x06\x01\x00-\x00\x02\x01\x01\x00" 400 - +111.172.7.26 - - [05/Feb/2026 19:02:30] code 400, message Bad request version ('\\x00\\x02\\x01\\x00\\x00+\\x00\\x07\\x06JJ\\x03\\x04\\x03\\x03\\x00\\x1b\\x00\\x03\\x02\\x00\\x02\\x00\\x12\\x00\\x00\\x00') +111.172.7.26 - - [05/Feb/2026 19:02:30] "\x16\x03\x01\x02\x00\x01\x00\x01ü\x03\x03\x10\x86\\-\x92\x1fÄ~¨ó\x1fÆ:¤IÈÐØ\x1f´ðè\x18\x97f¾kbP\x96;± ð_g\x14\x97\x85÷\x9co\x07ó$ 6\x97\x9c]×Ì\x1b\x9dÛ!D9»?³©qeñ\x00 ÚÚ\x13\x01\x13\x02\x13\x03À+À/À,À0̨̩À\x13À\x14\x00\x9c\x00\x9d\x00/\x005\x01\x00\x01\x93\x8a\x8a\x00\x00\x00-\x00\x02\x01\x01\x003\x00+\x00)jj\x00\x01\x00\x00\x1d\x00 \x8cW^\x06\x18Zîdè\x86æg±2«è+\x00f[NÝÅ®u\x9fïÊ\x18R\x17FDi\x00\x05\x00\x03\x02h2\x00\x10\x00\x0e\x00\x0c\x02h2\x08http/1.1\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x17\x00\x00ÿ\x01\x00\x01\x00\x00\x0b\x00\x02\x01\x00\x00+\x00\x07\x06JJ\x03\x04\x03\x03\x00\x1b\x00\x03\x02\x00\x02\x00\x12\x00\x00\x00" 400 - +111.172.7.26 - - [05/Feb/2026 19:02:33] code 400, message Bad request version ('\\x02h2\\x08http/1.1\\x00') +111.172.7.26 - - [05/Feb/2026 19:02:33] "\x16\x03\x01\x02\x00\x01\x00\x01ü\x03\x03@ ¬\x9e\x0b²\x87\x0d\x0b1ÖêBkC`s.ºhûùø²fìø]IÝ|à vtÀ£\x15:éGúº,\x94¦§-Ðp¾\x8f[\x1c-ú\x0b»­4ûÖSÃ=\x00 ºº\x13\x01\x13\x02\x13\x03À+À/À,À0̨̩À\x13À\x14\x00\x9c\x00\x9d\x00/\x005\x01\x00\x01\x93ÚÚ\x00\x00\x00+\x00\x07\x06êê\x03\x04\x03\x03\x003\x00+\x00)zz\x00\x01\x00\x00\x1d\x00 <0\x90(]¶ä±òü\x0e4_\x83¿Õoü¯\x95ÙYÇ&\x00AÇnä¬\x8e<\x00\x00\x00\x14\x00\x12\x00\x00\x0fxiangsilian.com\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x0b\x00\x02\x01\x00\x00#\x00\x00Di\x00\x05\x00\x03\x02h2\x00-\x00\x02\x01\x01\x00\x17\x00\x00\x00\x10\x00\x0e\x00\x0c\x02h2\x08http/1.1\x00" 400 - +111.172.7.26 - - [05/Feb/2026 19:02:34] code 400, message Bad request version ('¦¼\\x9b%\\x00\\x05\\x00\\x05\\x01\\x00\\x00\\x00\\x00ÿ\\x01\\x00\\x01\\x00\\x00\\x00\\x00\\x14\\x00\\x12\\x00\\x00\\x0fxiangsilian.comDi\\x00\\x05\\x00\\x03\\x02h2\\x00\\x12\\x00\\x00\\x00\\x1b\\x00\\x03\\x02\\x00\\x02\\x00') +111.172.7.26 - - [05/Feb/2026 19:02:34] "\x16\x03\x01\x02\x00\x01\x00\x01ü\x03\x03=\x8d&AK1XG\x15£ÏÍ\x12#\x97\x09ÝèG\x09üÔÂ\x96ý\x1cÆ\x82\x19«3Ö PÌĪ\x91Í\x12\x0dÀ§ë:\x8fÓª9\x9fÓâét\x82$â\x9c,qý@Ä3~\x00 ::\x13\x01\x13\x02\x13\x03À+À/À,À0̨̩À\x13À\x14\x00\x9c\x00\x9d\x00/\x005\x01\x00\x01\x93\x9a\x9a\x00\x00\x00\x0b\x00\x02\x01\x00\x00#\x00\x00\x00+\x00\x07\x06ÊÊ\x03\x04\x03\x03\x003\x00+\x00)\x8a\x8a\x00\x01\x00\x00\x1d\x00 üt\x18\x16"¬­ërÐsm&®p\x81*N\x08\x87-þ\x0f\x8cû>ü ¦¼\x9b%\x00\x05\x00\x05\x01\x00\x00\x00\x00ÿ\x01\x00\x01\x00\x00\x00\x00\x14\x00\x12\x00\x00\x0fxiangsilian.comDi\x00\x05\x00\x03\x02h2\x00\x12\x00\x00\x00\x1b\x00\x03\x02\x00\x02\x00" 400 - +85.217.140.7 - - [07/Feb/2026 19:16:34] "GET / HTTP/1.1" 200 - +85.217.140.7 - - [07/Feb/2026 19:16:34] code 404, message File not found +85.217.140.7 - - [07/Feb/2026 19:16:34] "GET /favicon.ico HTTP/1.1" 404 - +91.202.233.23 - - [09/Feb/2026 17:03:25] code 400, message Bad HTTP/0.9 request type ('\\x03\\x00\\x00/*à\\x00\\x00\\x00\\x00\\x00Cookie:') +91.202.233.23 - - [09/Feb/2026 17:03:25] "\x03\x00\x00/*à\x00\x00\x00\x00\x00Cookie: mstshash=Administr" 400 - +31.14.32.5 - - [09/Feb/2026 22:09:06] "GET / HTTP/1.1" 200 - +31.14.32.5 - - [09/Feb/2026 22:09:07] code 404, message File not found +31.14.32.5 - - [09/Feb/2026 22:09:07] "GET /favicon.ico HTTP/1.1" 404 - +1.202.76.38 - - [10/Feb/2026 09:35:39] "GET / HTTP/1.1" 200 - +1.202.76.38 - - [10/Feb/2026 09:35:39] "GET /css/style.css HTTP/1.1" 200 - +1.202.76.38 - - [10/Feb/2026 09:35:39] "GET /js/main.js HTTP/1.1" 200 - +1.202.76.38 - - [10/Feb/2026 09:35:39] "GET /js/api.js HTTP/1.1" 200 - +1.202.76.38 - - [10/Feb/2026 09:35:54] code 404, message File not found +1.202.76.38 - - [10/Feb/2026 09:35:54] "GET /favicon.ico HTTP/1.1" 404 - +127.0.0.1 - - [10/Feb/2026 09:57:02] code 404, message File not found +127.0.0.1 - - [10/Feb/2026 09:57:02] "GET /api/hairstyles HTTP/1.1" 404 - +1.202.76.38 - - [10/Feb/2026 09:57:54] code 404, message File not found +1.202.76.38 - - [10/Feb/2026 09:57:54] "GET /favicon.ico HTTP/1.1" 404 - +127.0.0.1 - - [10/Feb/2026 09:59:53] code 404, message File not found +127.0.0.1 - - [10/Feb/2026 09:59:53] "GET /api/hairstyles HTTP/1.1" 404 - +118.89.233.55 - - [10/Feb/2026 09:59:54] code 404, message File not found +118.89.233.55 - - [10/Feb/2026 09:59:54] "GET /api/hairstyles HTTP/1.1" 404 - +127.0.0.1 - - [10/Feb/2026 10:00:06] "GET / HTTP/1.1" 200 - +1.202.76.38 - - [10/Feb/2026 10:01:15] code 404, message File not found +1.202.76.38 - - [10/Feb/2026 10:01:15] "GET /favicon.ico HTTP/1.1" 404 - +1.202.76.38 - - [10/Feb/2026 10:01:51] "GET / HTTP/1.1" 200 - +1.202.76.38 - - [10/Feb/2026 10:01:51] "GET /css/style.css HTTP/1.1" 200 - +1.202.76.38 - - [10/Feb/2026 10:01:51] "GET /js/api.js HTTP/1.1" 200 - +1.202.76.38 - - [10/Feb/2026 10:01:51] "GET /js/main.js HTTP/1.1" 200 - +1.202.76.38 - - [10/Feb/2026 10:01:55] "GET / HTTP/1.1" 200 - +1.202.76.38 - - [10/Feb/2026 10:01:55] "GET /js/api.js HTTP/1.1" 200 - +1.202.76.38 - - [10/Feb/2026 10:01:55] "GET /css/style.css HTTP/1.1" 200 - +1.202.76.38 - - [10/Feb/2026 10:01:55] "GET /js/main.js HTTP/1.1" 200 - +1.202.76.38 - - [10/Feb/2026 10:02:00] code 404, message File not found +1.202.76.38 - - [10/Feb/2026 10:02:00] "GET /favicon.ico HTTP/1.1" 404 - +1.202.76.38 - - [10/Feb/2026 10:02:27] "GET /js/main.js HTTP/1.1" 304 - +1.202.76.38 - - [10/Feb/2026 10:02:40] code 404, message File not found +1.202.76.38 - - [10/Feb/2026 10:02:40] "GET /favicon.ico HTTP/1.1" 404 - +118.89.233.55 - - [10/Feb/2026 10:04:45] "GET /js/axios.min.js HTTP/1.1" 200 - +118.89.233.55 - - [10/Feb/2026 10:04:45] "GET / HTTP/1.1" 200 - +1.202.76.38 - - [10/Feb/2026 10:05:07] "GET /js/main.js HTTP/1.1" 304 - +127.0.0.1 - - [10/Feb/2026 10:06:37] "GET / HTTP/1.1" 200 - +118.89.233.55 - - [10/Feb/2026 10:06:52] "GET / HTTP/1.1" 200 - +118.89.233.55 - - [10/Feb/2026 10:06:52] "HEAD /js/axios.min.js HTTP/1.1" 200 - +118.89.233.55 - - [10/Feb/2026 10:06:52] "GET / HTTP/1.1" 200 - +118.89.233.55 - - [10/Feb/2026 10:06:52] "GET /js/main.js HTTP/1.1" 200 - +1.202.76.38 - - [10/Feb/2026 10:07:15] "GET / HTTP/1.1" 200 - +1.202.76.38 - - [10/Feb/2026 10:07:15] "GET /js/axios.min.js HTTP/1.1" 200 - +1.202.76.38 - - [10/Feb/2026 10:07:15] "GET /js/main.js HTTP/1.1" 200 - +1.202.76.38 - - [10/Feb/2026 10:07:15] "GET /css/style.css HTTP/1.1" 200 - +1.202.76.38 - - [10/Feb/2026 10:07:15] code 404, message File not found +1.202.76.38 - - [10/Feb/2026 10:07:15] "GET /js/axios.min.js.map HTTP/1.1" 404 - +1.202.76.38 - - [10/Feb/2026 10:07:25] code 404, message File not found +1.202.76.38 - - [10/Feb/2026 10:07:25] "GET /favicon.ico HTTP/1.1" 404 - diff --git a/frontend/index.html b/frontend/index.html index 213ea8e..8d913ec 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -5,7 +5,7 @@ 发型更换 - +
@@ -25,7 +25,7 @@
📷

点击上传图片或拍摄照片

- +
@@ -136,7 +136,6 @@ - \ No newline at end of file diff --git a/frontend/js/api.js b/frontend/js/api.js index fa4126e..0ebd088 100644 --- a/frontend/js/api.js +++ b/frontend/js/api.js @@ -1,5 +1,5 @@ // API调用封装 -const API_BASE_URL = '/api'; +const API_BASE_URL = 'http://localhost:5000/api'; // 获取发型列表 export async function getHairstyles() { diff --git a/frontend/js/axios.min.js b/frontend/js/axios.min.js new file mode 100644 index 0000000..d97e5ed --- /dev/null +++ b/frontend/js/axios.min.js @@ -0,0 +1,5 @@ +/*! Axios v1.13.5 Copyright (c) 2026 Matt Zabriskie and contributors */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).axios=t()}(this,function(){"use strict";function e(e,t){this.v=e,this.k=t}function t(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n3?(o=h===r)&&(s=i[(u=i[4])?5:(u=3,3)],i[4]=i[5]=e):i[0]<=p&&((o=n<2&&pr||r>h)&&(i[4]=n,i[5]=r,d.n=h,u=0))}if(o||n>1)return a;throw l=!0,r}return function(o,f,h){if(c>1)throw TypeError("Generator is already running");for(l&&1===f&&p(f,h),u=f,s=h;(t=u<2?e:s)||!l;){i||(u?u<3?(u>1&&(d.n=-1),p(u,s)):d.n=s:d.v=s);try{if(c=2,i){if(u||(o="next"),t=i[o]){if(!(t=t.call(i,s)))throw TypeError("iterator result is not an object");if(!t.done)return t;s=t.value,u<2&&(u=0)}else 1===u&&(t=i.return)&&t.call(i),u<2&&(s=TypeError("The iterator does not provide a '"+o+"' method"),u=1);i=e}else if((t=(l=d.n<0)?s:n.call(r,d))!==a)break}catch(t){i=e,u=1,s=t}finally{c=1}}return{value:t,done:l}}}(n,o,i),!0),c}var a={};function u(){}function s(){}function c(){}t=Object.getPrototypeOf;var f=[][r]?t(t([][r]())):(g(t={},r,function(){return this}),t),l=c.prototype=u.prototype=Object.create(f);function d(e){return Object.setPrototypeOf?Object.setPrototypeOf(e,c):(e.__proto__=c,g(e,o,"GeneratorFunction")),e.prototype=Object.create(l),e}return s.prototype=c,g(l,"constructor",c),g(c,"constructor",s),s.displayName="GeneratorFunction",g(c,o,"GeneratorFunction"),g(l),g(l,o,"Generator"),g(l,r,function(){return this}),g(l,"toString",function(){return"[object Generator]"}),(m=function(){return{w:i,m:d}})()}function g(e,t,n,r){var o=Object.defineProperty;try{o({},"",{})}catch(e){o=0}g=function(e,t,n,r){function i(t,n){g(e,t,function(e){return this._invoke(t,n,e)})}t?o?o(e,t,{value:n,enumerable:!r,configurable:!r,writable:!r}):e[t]=n:(i("next",0),i("throw",1),i("return",2))},g(e,t,n,r)}function w(e){if(null!=e){var t=e["function"==typeof Symbol&&Symbol.iterator||"@@iterator"],n=0;if(t)return t.call(e);if("function"==typeof e.next)return e;if(!isNaN(e.length))return{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}}}throw new TypeError(typeof e+" is not iterable")}function O(e,t){return O=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},O(e,t)}function E(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var r,o,i,a,u=[],s=!0,c=!1;try{if(i=(n=n.call(e)).next,0===t){if(Object(n)!==n)return;s=!1}else for(;!(s=(r=i.call(n)).done)&&(u.push(r.value),u.length!==t);s=!0);}catch(e){c=!0,o=e}finally{try{if(!s&&null!=n.return&&(a=n.return(),Object(a)!==a))return}finally{if(c)throw o}}return u}}(e,t)||A(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function R(e){return function(e){if(Array.isArray(e))return t(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||A(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function S(e){var t=function(e,t){if("object"!=typeof e||!e)return e;var n=e[Symbol.toPrimitive];if(void 0!==n){var r=n.call(e,t||"default");if("object"!=typeof r)return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:t+""}function T(e){return T="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},T(e)}function A(e,n){if(e){if("string"==typeof e)return t(e,n);var r={}.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?t(e,n):void 0}}function j(e){return function(){return new k(e.apply(this,arguments))}}function k(t){var n,r;function o(n,r){try{var a=t[n](r),u=a.value,s=u instanceof e;Promise.resolve(s?u.v:u).then(function(e){if(s){var r="return"===n?"return":"next";if(!u.k||e.done)return o(r,e);e=t[r](e).value}i(a.done?"return":"normal",e)},function(e){o("throw",e)})}catch(e){i("throw",e)}}function i(e,t){switch(e){case"return":n.resolve({value:t,done:!0});break;case"throw":n.reject(t);break;default:n.resolve({value:t,done:!1})}(n=n.next)?o(n.key,n.arg):r=null}this._invoke=function(e,t){return new Promise(function(i,a){var u={key:e,arg:t,resolve:i,reject:a,next:null};r?r=r.next=u:(n=r=u,o(e,t))})},"function"!=typeof t.return&&(this.return=void 0)}function P(e){var t="function"==typeof Map?new Map:void 0;return P=function(e){if(null===e||!function(e){try{return-1!==Function.toString.call(e).indexOf("[native code]")}catch(t){return"function"==typeof e}}(e))return e;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,n)}function n(){return function(e,t,n){if(y())return Reflect.construct.apply(null,arguments);var r=[null];r.push.apply(r,t);var o=new(e.bind.apply(e,r));return n&&O(o,n.prototype),o}(e,arguments,p(this).constructor)}return n.prototype=Object.create(e.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}}),O(n,e)},P(e)}function _(e,t){return function(){return e.apply(t,arguments)}}k.prototype["function"==typeof Symbol&&Symbol.asyncIterator||"@@asyncIterator"]=function(){return this},k.prototype.next=function(e){return this._invoke("next",e)},k.prototype.throw=function(e){return this._invoke("throw",e)},k.prototype.return=function(e){return this._invoke("return",e)};var x,N=Object.prototype.toString,C=Object.getPrototypeOf,U=Symbol.iterator,F=Symbol.toStringTag,D=(x=Object.create(null),function(e){var t=N.call(e);return x[t]||(x[t]=t.slice(8,-1).toLowerCase())}),L=function(e){return e=e.toLowerCase(),function(t){return D(t)===e}},B=function(e){return function(t){return T(t)===e}},I=Array.isArray,q=B("undefined");function M(e){return null!==e&&!q(e)&&null!==e.constructor&&!q(e.constructor)&&J(e.constructor.isBuffer)&&e.constructor.isBuffer(e)}var z=L("ArrayBuffer");var H=B("string"),J=B("function"),W=B("number"),K=function(e){return null!==e&&"object"===T(e)},V=function(e){if("object"!==D(e))return!1;var t=C(e);return!(null!==t&&t!==Object.prototype&&null!==Object.getPrototypeOf(t)||F in e||U in e)},G=L("Date"),X=L("File"),$=L("Blob"),Q=L("FileList"),Y=L("URLSearchParams"),Z=E(["ReadableStream","Request","Response","Headers"].map(L),4),ee=Z[0],te=Z[1],ne=Z[2],re=Z[3];function oe(e,t){var n,r,o=(arguments.length>2&&void 0!==arguments[2]?arguments[2]:{}).allOwnKeys,i=void 0!==o&&o;if(null!=e)if("object"!==T(e)&&(e=[e]),I(e))for(n=0,r=e.length;n0;)if(t===(n=r[o]).toLowerCase())return n;return null}var ae="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:global,ue=function(e){return!q(e)&&e!==ae};var se,ce=(se="undefined"!=typeof Uint8Array&&C(Uint8Array),function(e){return se&&e instanceof se}),fe=L("HTMLFormElement"),le=function(){var e=Object.prototype.hasOwnProperty;return function(t,n){return e.call(t,n)}}(),de=L("RegExp"),pe=function(e,t){var n=Object.getOwnPropertyDescriptors(e),r={};oe(n,function(n,o){var i;!1!==(i=t(n,o,e))&&(r[o]=i||n)}),Object.defineProperties(e,r)};var he,ye,ve,be,me=L("AsyncFunction"),ge=(he="function"==typeof setImmediate,ye=J(ae.postMessage),he?setImmediate:ye?(ve="axios@".concat(Math.random()),be=[],ae.addEventListener("message",function(e){var t=e.source,n=e.data;t===ae&&n===ve&&be.length&&be.shift()()},!1),function(e){be.push(e),ae.postMessage(ve,"*")}):function(e){return setTimeout(e)}),we="undefined"!=typeof queueMicrotask?queueMicrotask.bind(ae):"undefined"!=typeof process&&process.nextTick||ge,Oe={isArray:I,isArrayBuffer:z,isBuffer:M,isFormData:function(e){var t;return e&&("function"==typeof FormData&&e instanceof FormData||J(e.append)&&("formdata"===(t=D(e))||"object"===t&&J(e.toString)&&"[object FormData]"===e.toString()))},isArrayBufferView:function(e){return"undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&z(e.buffer)},isString:H,isNumber:W,isBoolean:function(e){return!0===e||!1===e},isObject:K,isPlainObject:V,isEmptyObject:function(e){if(!K(e)||M(e))return!1;try{return 0===Object.keys(e).length&&Object.getPrototypeOf(e)===Object.prototype}catch(e){return!1}},isReadableStream:ee,isRequest:te,isResponse:ne,isHeaders:re,isUndefined:q,isDate:G,isFile:X,isBlob:$,isRegExp:de,isFunction:J,isStream:function(e){return K(e)&&J(e.pipe)},isURLSearchParams:Y,isTypedArray:ce,isFileList:Q,forEach:oe,merge:function e(){for(var t=ue(this)&&this||{},n=t.caseless,r=t.skipUndefined,o={},i=function(t,i){if("__proto__"!==i&&"constructor"!==i&&"prototype"!==i){var a=n&&ie(o,i)||i;V(o[a])&&V(t)?o[a]=e(o[a],t):V(t)?o[a]=e({},t):I(t)?o[a]=t.slice():r&&q(t)||(o[a]=t)}},a=0,u=arguments.length;a3&&void 0!==arguments[3]?arguments[3]:{}).allOwnKeys}),e},trim:function(e){return e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")},stripBOM:function(e){return 65279===e.charCodeAt(0)&&(e=e.slice(1)),e},inherits:function(e,t,n,r){e.prototype=Object.create(t.prototype,r),Object.defineProperty(e.prototype,"constructor",{value:e,writable:!0,enumerable:!1,configurable:!0}),Object.defineProperty(e,"super",{value:t.prototype}),n&&Object.assign(e.prototype,n)},toFlatObject:function(e,t,n,r){var o,i,a,u={};if(t=t||{},null==e)return t;do{for(i=(o=Object.getOwnPropertyNames(e)).length;i-- >0;)a=o[i],r&&!r(a,e,t)||u[a]||(t[a]=e[a],u[a]=!0);e=!1!==n&&C(e)}while(e&&(!n||n(e,t))&&e!==Object.prototype);return t},kindOf:D,kindOfTest:L,endsWith:function(e,t,n){e=String(e),(void 0===n||n>e.length)&&(n=e.length),n-=t.length;var r=e.indexOf(t,n);return-1!==r&&r===n},toArray:function(e){if(!e)return null;if(I(e))return e;var t=e.length;if(!W(t))return null;for(var n=new Array(t);t-- >0;)n[t]=e[t];return n},forEachEntry:function(e,t){for(var n,r=(e&&e[U]).call(e);(n=r.next())&&!n.done;){var o=n.value;t.call(e,o[0],o[1])}},matchAll:function(e,t){for(var n,r=[];null!==(n=e.exec(t));)r.push(n);return r},isHTMLForm:fe,hasOwnProperty:le,hasOwnProp:le,reduceDescriptors:pe,freezeMethods:function(e){pe(e,function(t,n){if(J(e)&&-1!==["arguments","caller","callee"].indexOf(n))return!1;var r=e[n];J(r)&&(t.enumerable=!1,"writable"in t?t.writable=!1:t.set||(t.set=function(){throw Error("Can not rewrite read-only method '"+n+"'")}))})},toObjectSet:function(e,t){var n={},r=function(e){e.forEach(function(e){n[e]=!0})};return I(e)?r(e):r(String(e).split(t)),n},toCamelCase:function(e){return e.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(e,t,n){return t.toUpperCase()+n})},noop:function(){},toFiniteNumber:function(e,t){return null!=e&&Number.isFinite(e=+e)?e:t},findKey:ie,global:ae,isContextDefined:ue,isSpecCompliantForm:function(e){return!!(e&&J(e.append)&&"FormData"===e[F]&&e[U])},toJSONObject:function(e){var t=new Array(10),n=function(e,r){if(K(e)){if(t.indexOf(e)>=0)return;if(M(e))return e;if(!("toJSON"in e)){t[r]=e;var o=I(e)?[]:{};return oe(e,function(e,t){var i=n(e,r+1);!q(i)&&(o[t]=i)}),t[r]=void 0,o}}return e};return n(e,0)},isAsyncFn:me,isThenable:function(e){return e&&(K(e)||J(e))&&J(e.then)&&J(e.catch)},setImmediate:ge,asap:we,isIterable:function(e){return null!=e&&J(e[U])}},Ee=function(e){function t(e,n,r,o,i){var a;return c(this,t),(a=s(this,t,[e])).name="AxiosError",a.isAxiosError=!0,n&&(a.code=n),r&&(a.config=r),o&&(a.request=o),i&&(a.response=i,a.status=i.status),a}return h(t,e),l(t,[{key:"toJSON",value:function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:Oe.toJSONObject(this.config),code:this.code,status:this.status}}}],[{key:"from",value:function(e,n,r,o,i,a){var u=new t(e.message,n||e.code,r,o,i);return u.cause=e,u.name=e.name,a&&Object.assign(u,a),u}}])}(P(Error));Ee.ERR_BAD_OPTION_VALUE="ERR_BAD_OPTION_VALUE",Ee.ERR_BAD_OPTION="ERR_BAD_OPTION",Ee.ECONNABORTED="ECONNABORTED",Ee.ETIMEDOUT="ETIMEDOUT",Ee.ERR_NETWORK="ERR_NETWORK",Ee.ERR_FR_TOO_MANY_REDIRECTS="ERR_FR_TOO_MANY_REDIRECTS",Ee.ERR_DEPRECATED="ERR_DEPRECATED",Ee.ERR_BAD_RESPONSE="ERR_BAD_RESPONSE",Ee.ERR_BAD_REQUEST="ERR_BAD_REQUEST",Ee.ERR_CANCELED="ERR_CANCELED",Ee.ERR_NOT_SUPPORT="ERR_NOT_SUPPORT",Ee.ERR_INVALID_URL="ERR_INVALID_URL";var Re=Ee;function Se(e){return Oe.isPlainObject(e)||Oe.isArray(e)}function Te(e){return Oe.endsWith(e,"[]")?e.slice(0,-2):e}function Ae(e,t,n){return e?e.concat(t).map(function(e,t){return e=Te(e),!n&&t?"["+e+"]":e}).join(n?".":""):t}var je=Oe.toFlatObject(Oe,{},null,function(e){return/^is[A-Z]/.test(e)});function ke(e,t,n){if(!Oe.isObject(e))throw new TypeError("target must be an object");t=t||new FormData;var r=(n=Oe.toFlatObject(n,{metaTokens:!0,dots:!1,indexes:!1},!1,function(e,t){return!Oe.isUndefined(t[e])})).metaTokens,o=n.visitor||c,i=n.dots,a=n.indexes,u=(n.Blob||"undefined"!=typeof Blob&&Blob)&&Oe.isSpecCompliantForm(t);if(!Oe.isFunction(o))throw new TypeError("visitor must be a function");function s(e){if(null===e)return"";if(Oe.isDate(e))return e.toISOString();if(Oe.isBoolean(e))return e.toString();if(!u&&Oe.isBlob(e))throw new Re("Blob is not supported. Use a Buffer instead.");return Oe.isArrayBuffer(e)||Oe.isTypedArray(e)?u&&"function"==typeof Blob?new Blob([e]):Buffer.from(e):e}function c(e,n,o){var u=e;if(e&&!o&&"object"===T(e))if(Oe.endsWith(n,"{}"))n=r?n:n.slice(0,-2),e=JSON.stringify(e);else if(Oe.isArray(e)&&function(e){return Oe.isArray(e)&&!e.some(Se)}(e)||(Oe.isFileList(e)||Oe.endsWith(n,"[]"))&&(u=Oe.toArray(e)))return n=Te(n),u.forEach(function(e,r){!Oe.isUndefined(e)&&null!==e&&t.append(!0===a?Ae([n],r,i):null===a?n:n+"[]",s(e))}),!1;return!!Se(e)||(t.append(Ae(o,n,i),s(e)),!1)}var f=[],l=Object.assign(je,{defaultVisitor:c,convertValue:s,isVisitable:Se});if(!Oe.isObject(e))throw new TypeError("data must be an object");return function e(n,r){if(!Oe.isUndefined(n)){if(-1!==f.indexOf(n))throw Error("Circular reference detected in "+r.join("."));f.push(n),Oe.forEach(n,function(n,i){!0===(!(Oe.isUndefined(n)||null===n)&&o.call(t,n,Oe.isString(i)?i.trim():i,r,l))&&e(n,r?r.concat(i):[i])}),f.pop()}}(e),t}function Pe(e){var t={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+","%00":"\0"};return encodeURIComponent(e).replace(/[!'()~]|%20|%00/g,function(e){return t[e]})}function _e(e,t){this._pairs=[],e&&ke(e,this,t)}var xe=_e.prototype;function Ne(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+")}function Ce(e,t,n){if(!t)return e;var r,o=n&&n.encode||Ne,i=Oe.isFunction(n)?{serialize:n}:n,a=i&&i.serialize;if(r=a?a(t,i):Oe.isURLSearchParams(t)?t.toString():new _e(t,i).toString(o)){var u=e.indexOf("#");-1!==u&&(e=e.slice(0,u)),e+=(-1===e.indexOf("?")?"?":"&")+r}return e}xe.append=function(e,t){this._pairs.push([e,t])},xe.toString=function(e){var t=e?function(t){return e.call(this,t,Pe)}:Pe;return this._pairs.map(function(e){return t(e[0])+"="+t(e[1])},"").join("&")};var Ue=function(){return l(function e(){c(this,e),this.handlers=[]},[{key:"use",value:function(e,t,n){return this.handlers.push({fulfilled:e,rejected:t,synchronous:!!n&&n.synchronous,runWhen:n?n.runWhen:null}),this.handlers.length-1}},{key:"eject",value:function(e){this.handlers[e]&&(this.handlers[e]=null)}},{key:"clear",value:function(){this.handlers&&(this.handlers=[])}},{key:"forEach",value:function(e){Oe.forEach(this.handlers,function(t){null!==t&&e(t)})}}])}(),Fe={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1,legacyInterceptorReqResOrdering:!0},De={isBrowser:!0,classes:{URLSearchParams:"undefined"!=typeof URLSearchParams?URLSearchParams:_e,FormData:"undefined"!=typeof FormData?FormData:null,Blob:"undefined"!=typeof Blob?Blob:null},protocols:["http","https","file","blob","url","data"]},Le="undefined"!=typeof window&&"undefined"!=typeof document,Be="object"===("undefined"==typeof navigator?"undefined":T(navigator))&&navigator||void 0,Ie=Le&&(!Be||["ReactNative","NativeScript","NS"].indexOf(Be.product)<0),qe="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope&&"function"==typeof self.importScripts,Me=Le&&window.location.href||"http://localhost",ze=b(b({},Object.freeze({__proto__:null,hasBrowserEnv:Le,hasStandardBrowserWebWorkerEnv:qe,hasStandardBrowserEnv:Ie,navigator:Be,origin:Me})),De);function He(e){function t(e,n,r,o){var i=e[o++];if("__proto__"===i)return!0;var a=Number.isFinite(+i),u=o>=e.length;return i=!i&&Oe.isArray(r)?r.length:i,u?(Oe.hasOwnProp(r,i)?r[i]=[r[i],n]:r[i]=n,!a):(r[i]&&Oe.isObject(r[i])||(r[i]=[]),t(e,n,r[i],o)&&Oe.isArray(r[i])&&(r[i]=function(e){var t,n,r={},o=Object.keys(e),i=o.length;for(t=0;t-1,i=Oe.isObject(e);if(i&&Oe.isHTMLForm(e)&&(e=new FormData(e)),Oe.isFormData(e))return o?JSON.stringify(He(e)):e;if(Oe.isArrayBuffer(e)||Oe.isBuffer(e)||Oe.isStream(e)||Oe.isFile(e)||Oe.isBlob(e)||Oe.isReadableStream(e))return e;if(Oe.isArrayBufferView(e))return e.buffer;if(Oe.isURLSearchParams(e))return t.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),e.toString();if(i){if(r.indexOf("application/x-www-form-urlencoded")>-1)return function(e,t){return ke(e,new ze.classes.URLSearchParams,b({visitor:function(e,t,n,r){return ze.isNode&&Oe.isBuffer(e)?(this.append(t,e.toString("base64")),!1):r.defaultVisitor.apply(this,arguments)}},t))}(e,this.formSerializer).toString();if((n=Oe.isFileList(e))||r.indexOf("multipart/form-data")>-1){var a=this.env&&this.env.FormData;return ke(n?{"files[]":e}:e,a&&new a,this.formSerializer)}}return i||o?(t.setContentType("application/json",!1),function(e,t,n){if(Oe.isString(e))try{return(t||JSON.parse)(e),Oe.trim(e)}catch(e){if("SyntaxError"!==e.name)throw e}return(n||JSON.stringify)(e)}(e)):e}],transformResponse:[function(e){var t=this.transitional||Je.transitional,n=t&&t.forcedJSONParsing,r="json"===this.responseType;if(Oe.isResponse(e)||Oe.isReadableStream(e))return e;if(e&&Oe.isString(e)&&(n&&!this.responseType||r)){var o=!(t&&t.silentJSONParsing)&&r;try{return JSON.parse(e,this.parseReviver)}catch(e){if(o){if("SyntaxError"===e.name)throw Re.from(e,Re.ERR_BAD_RESPONSE,this,null,this.response);throw e}}}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:ze.classes.FormData,Blob:ze.classes.Blob},validateStatus:function(e){return e>=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};Oe.forEach(["delete","get","head","post","put","patch"],function(e){Je.headers[e]={}});var We=Je,Ke=Oe.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),Ve=Symbol("internals");function Ge(e){return e&&String(e).trim().toLowerCase()}function Xe(e){return!1===e||null==e?e:Oe.isArray(e)?e.map(Xe):String(e)}function $e(e,t,n,r,o){return Oe.isFunction(r)?r.call(this,t,n):(o&&(t=n),Oe.isString(t)?Oe.isString(r)?-1!==t.indexOf(r):Oe.isRegExp(r)?r.test(t):void 0:void 0)}var Qe=function(){return l(function e(t){c(this,e),t&&this.set(t)},[{key:"set",value:function(e,t,n){var r=this;function o(e,t,n){var o=Ge(t);if(!o)throw new Error("header name must be a non-empty string");var i=Oe.findKey(r,o);(!i||void 0===r[i]||!0===n||void 0===n&&!1!==r[i])&&(r[i||t]=Xe(e))}var i=function(e,t){return Oe.forEach(e,function(e,n){return o(e,n,t)})};if(Oe.isPlainObject(e)||e instanceof this.constructor)i(e,t);else if(Oe.isString(e)&&(e=e.trim())&&!/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(e.trim()))i(function(e){var t,n,r,o={};return e&&e.split("\n").forEach(function(e){r=e.indexOf(":"),t=e.substring(0,r).trim().toLowerCase(),n=e.substring(r+1).trim(),!t||o[t]&&Ke[t]||("set-cookie"===t?o[t]?o[t].push(n):o[t]=[n]:o[t]=o[t]?o[t]+", "+n:n)}),o}(e),t);else if(Oe.isObject(e)&&Oe.isIterable(e)){var a,u,s,c={},f=function(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(!n){if(Array.isArray(e)||(n=A(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0,o=function(){};return{s:o,n:function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}},e:function(e){throw e},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i,a=!0,u=!1;return{s:function(){n=n.call(e)},n:function(){var e=n.next();return a=e.done,e},e:function(e){u=!0,i=e},f:function(){try{a||null==n.return||n.return()}finally{if(u)throw i}}}}(e);try{for(f.s();!(s=f.n()).done;){var l=s.value;if(!Oe.isArray(l))throw TypeError("Object iterator must return a key-value pair");c[u=l[0]]=(a=c[u])?Oe.isArray(a)?[].concat(R(a),[l[1]]):[a,l[1]]:l[1]}}catch(e){f.e(e)}finally{f.f()}i(c,t)}else null!=e&&o(t,e,n);return this}},{key:"get",value:function(e,t){if(e=Ge(e)){var n=Oe.findKey(this,e);if(n){var r=this[n];if(!t)return r;if(!0===t)return function(e){for(var t,n=Object.create(null),r=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;t=r.exec(e);)n[t[1]]=t[2];return n}(r);if(Oe.isFunction(t))return t.call(this,r,n);if(Oe.isRegExp(t))return t.exec(r);throw new TypeError("parser must be boolean|regexp|function")}}}},{key:"has",value:function(e,t){if(e=Ge(e)){var n=Oe.findKey(this,e);return!(!n||void 0===this[n]||t&&!$e(0,this[n],n,t))}return!1}},{key:"delete",value:function(e,t){var n=this,r=!1;function o(e){if(e=Ge(e)){var o=Oe.findKey(n,e);!o||t&&!$e(0,n[o],o,t)||(delete n[o],r=!0)}}return Oe.isArray(e)?e.forEach(o):o(e),r}},{key:"clear",value:function(e){for(var t=Object.keys(this),n=t.length,r=!1;n--;){var o=t[n];e&&!$e(0,this[o],o,e,!0)||(delete this[o],r=!0)}return r}},{key:"normalize",value:function(e){var t=this,n={};return Oe.forEach(this,function(r,o){var i=Oe.findKey(n,o);if(i)return t[i]=Xe(r),void delete t[o];var a=e?function(e){return e.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,function(e,t,n){return t.toUpperCase()+n})}(o):String(o).trim();a!==o&&delete t[o],t[a]=Xe(r),n[a]=!0}),this}},{key:"concat",value:function(){for(var e,t=arguments.length,n=new Array(t),r=0;r1?n-1:0),o=1;o2&&void 0!==arguments[2]?arguments[2]:3,r=0,o=function(e,t){e=e||10;var n,r=new Array(e),o=new Array(e),i=0,a=0;return t=void 0!==t?t:1e3,function(u){var s=Date.now(),c=o[a];n||(n=s),r[i]=u,o[i]=s;for(var f=a,l=0;f!==i;)l+=r[f++],f%=e;if((i=(i+1)%e)===a&&(a=(a+1)%e),!(s-n1&&void 0!==arguments[1]?arguments[1]:Date.now();o=i,n=null,r&&(clearTimeout(r),r=null),e.apply(void 0,R(t))};return[function(){for(var e=Date.now(),t=e-o,u=arguments.length,s=new Array(u),c=0;c=i?a(s,e):(n=s,r||(r=setTimeout(function(){r=null,a(n)},i-t)))},function(){return n&&a(n)}]}(function(n){var i=n.loaded,a=n.lengthComputable?n.total:void 0,u=i-r,s=o(u);r=i;var c=d({loaded:i,total:a,progress:a?i/a:void 0,bytes:u,rate:s||void 0,estimated:s&&a&&i<=a?(a-i)/s:void 0,event:n,lengthComputable:null!=a},t?"download":"upload",!0);e(c)},n)},ot=function(e,t){var n=null!=e;return[function(r){return t[0]({lengthComputable:n,total:e,loaded:r})},t[1]]},it=function(e){return function(){for(var t=arguments.length,n=new Array(t),r=0;r1?t-1:0),r=1;r1?"since :\n"+s.map(Pt).join("\n"):" "+Pt(s[0]):"as no adapter specified";throw new Re("There is no suitable adapter to dispatch the request "+c,"ERR_NOT_SUPPORT")}return r},adapters:kt};function Nt(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw new tt(null,e)}function Ct(e){return Nt(e),e.headers=Ye.from(e.headers),e.data=Ze.call(e,e.transformRequest),-1!==["post","put","patch"].indexOf(e.method)&&e.headers.setContentType("application/x-www-form-urlencoded",!1),xt.getAdapter(e.adapter||We.adapter,e)(e).then(function(t){return Nt(e),t.data=Ze.call(e,e.transformResponse,t),t.headers=Ye.from(t.headers),t},function(t){return et(t)||(Nt(e),t&&t.response&&(t.response.data=Ze.call(e,e.transformResponse,t.response),t.response.headers=Ye.from(t.response.headers))),Promise.reject(t)})}var Ut="1.13.5",Ft={};["object","boolean","number","function","string","symbol"].forEach(function(e,t){Ft[e]=function(n){return T(n)===e||"a"+(t<1?"n ":" ")+e}});var Dt={};Ft.transitional=function(e,t,n){function r(e,t){return"[Axios v"+Ut+"] Transitional option '"+e+"'"+t+(n?". "+n:"")}return function(n,o,i){if(!1===e)throw new Re(r(o," has been removed"+(t?" in "+t:"")),Re.ERR_DEPRECATED);return t&&!Dt[o]&&(Dt[o]=!0,console.warn(r(o," has been deprecated since v"+t+" and will be removed in the near future"))),!e||e(n,o,i)}},Ft.spelling=function(e){return function(t,n){return console.warn("".concat(n," is likely a misspelling of ").concat(e)),!0}};var Lt={assertOptions:function(e,t,n){if("object"!==T(e))throw new Re("options must be an object",Re.ERR_BAD_OPTION_VALUE);for(var r=Object.keys(e),o=r.length;o-- >0;){var i=r[o],a=t[i];if(a){var u=e[i],s=void 0===u||a(u,i,e);if(!0!==s)throw new Re("option "+i+" must be "+s,Re.ERR_BAD_OPTION_VALUE)}else if(!0!==n)throw new Re("Unknown option "+i,Re.ERR_BAD_OPTION)}},validators:Ft},Bt=Lt.validators,It=function(){return l(function e(t){c(this,e),this.defaults=t||{},this.interceptors={request:new Ue,response:new Ue}},[{key:"request",value:(e=a(m().m(function e(t,n){var r,o,i;return m().w(function(e){for(;;)switch(e.p=e.n){case 0:return e.p=0,e.n=1,this._request(t,n);case 1:return e.a(2,e.v);case 2:if(e.p=2,(i=e.v)instanceof Error){r={},Error.captureStackTrace?Error.captureStackTrace(r):r=new Error,o=r.stack?r.stack.replace(/^.+\n/,""):"";try{i.stack?o&&!String(i.stack).endsWith(o.replace(/^.+\n.+\n/,""))&&(i.stack+="\n"+o):i.stack=o}catch(e){}}throw i;case 3:return e.a(2)}},e,this,[[0,2]])})),function(t,n){return e.apply(this,arguments)})},{key:"_request",value:function(e,t){"string"==typeof e?(t=t||{}).url=e:t=e||{};var n=t=ft(this.defaults,t),r=n.transitional,o=n.paramsSerializer,i=n.headers;void 0!==r&&Lt.assertOptions(r,{silentJSONParsing:Bt.transitional(Bt.boolean),forcedJSONParsing:Bt.transitional(Bt.boolean),clarifyTimeoutError:Bt.transitional(Bt.boolean),legacyInterceptorReqResOrdering:Bt.transitional(Bt.boolean)},!1),null!=o&&(Oe.isFunction(o)?t.paramsSerializer={serialize:o}:Lt.assertOptions(o,{encode:Bt.function,serialize:Bt.function},!0)),void 0!==t.allowAbsoluteUrls||(void 0!==this.defaults.allowAbsoluteUrls?t.allowAbsoluteUrls=this.defaults.allowAbsoluteUrls:t.allowAbsoluteUrls=!0),Lt.assertOptions(t,{baseUrl:Bt.spelling("baseURL"),withXsrfToken:Bt.spelling("withXSRFToken")},!0),t.method=(t.method||this.defaults.method||"get").toLowerCase();var a=i&&Oe.merge(i.common,i[t.method]);i&&Oe.forEach(["delete","get","head","post","put","patch","common"],function(e){delete i[e]}),t.headers=Ye.concat(a,i);var u=[],s=!0;this.interceptors.request.forEach(function(e){if("function"!=typeof e.runWhen||!1!==e.runWhen(t)){s=s&&e.synchronous;var n=t.transitional||Fe;n&&n.legacyInterceptorReqResOrdering?u.unshift(e.fulfilled,e.rejected):u.push(e.fulfilled,e.rejected)}});var c,f=[];this.interceptors.response.forEach(function(e){f.push(e.fulfilled,e.rejected)});var l,d=0;if(!s){var p=[Ct.bind(this),void 0];for(p.unshift.apply(p,u),p.push.apply(p,f),l=p.length,c=Promise.resolve(t);d0;)r._listeners[t](e);r._listeners=null}}),this.promise.then=function(e){var t,n=new Promise(function(e){r.subscribe(e),t=e}).then(e);return n.cancel=function(){r.unsubscribe(t)},n},t(function(e,t,o){r.reason||(r.reason=new tt(e,t,o),n(r.reason))})}return l(e,[{key:"throwIfRequested",value:function(){if(this.reason)throw this.reason}},{key:"subscribe",value:function(e){this.reason?e(this.reason):this._listeners?this._listeners.push(e):this._listeners=[e]}},{key:"unsubscribe",value:function(e){if(this._listeners){var t=this._listeners.indexOf(e);-1!==t&&this._listeners.splice(t,1)}}},{key:"toAbortSignal",value:function(){var e=this,t=new AbortController,n=function(e){t.abort(e)};return this.subscribe(n),t.signal.unsubscribe=function(){return e.unsubscribe(n)},t.signal}}],[{key:"source",value:function(){var t;return{token:new e(function(e){t=e}),cancel:t}}}])}(),zt=Mt;var Ht={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511,WebServerIsDown:521,ConnectionTimedOut:522,OriginIsUnreachable:523,TimeoutOccurred:524,SslHandshakeFailed:525,InvalidSslCertificate:526};Object.entries(Ht).forEach(function(e){var t=E(e,2),n=t[0],r=t[1];Ht[r]=n});var Jt=Ht;var Wt=function e(t){var n=new qt(t),r=_(qt.prototype.request,n);return Oe.extend(r,qt.prototype,n,{allOwnKeys:!0}),Oe.extend(r,n,null,{allOwnKeys:!0}),r.create=function(n){return e(ft(t,n))},r}(We);return Wt.Axios=qt,Wt.CanceledError=tt,Wt.CancelToken=zt,Wt.isCancel=et,Wt.VERSION=Ut,Wt.toFormData=ke,Wt.AxiosError=Re,Wt.Cancel=Wt.CanceledError,Wt.all=function(e){return Promise.all(e)},Wt.spread=function(e){return function(t){return e.apply(null,t)}},Wt.isAxiosError=function(e){return Oe.isObject(e)&&!0===e.isAxiosError},Wt.mergeConfig=ft,Wt.AxiosHeaders=Ye,Wt.formToJSON=function(e){return He(Oe.isHTMLForm(e)?new FormData(e):e)},Wt.getAdapter=xt.getAdapter,Wt.HttpStatusCode=Jt,Wt.default=Wt,Wt}); +//# sourceMappingURL=axios.min.js.map diff --git a/frontend/js/main.js b/frontend/js/main.js index 66862c5..77f6c77 100644 --- a/frontend/js/main.js +++ b/frontend/js/main.js @@ -1,7 +1,10 @@ +// API基础URL +const API_BASE_URL = 'http://xiangsilian.com:5000/api'; + // API调用函数 async function getHairstyles() { try { - const response = await axios.get('/api/hairstyles'); + const response = await axios.get(`${API_BASE_URL}/hairstyles`); return response.data; } catch (error) { console.error('获取发型列表失败:', error); @@ -15,7 +18,7 @@ async function changeHair(imageFile, hairstyleId) { formData.append('image', imageFile); formData.append('hairstyle_id', hairstyleId); - const response = await axios.post('/api/change-hair', formData, { + const response = await axios.post(`${API_BASE_URL}/change-hair`, formData, { headers: { 'Content-Type': 'multipart/form-data' } @@ -34,7 +37,7 @@ async function changeHairColor(imageFile, rgb, ratio) { formData.append('rgb', rgb); formData.append('ratio', ratio); - const response = await axios.post('/api/change-hair-color', formData, { + const response = await axios.post(`${API_BASE_URL}/change-hair-color`, formData, { headers: { 'Content-Type': 'multipart/form-data' }