[add] 第一版完成
This commit is contained in:
parent
82c94e8549
commit
14ab663c03
10
.env.dev
10
.env.dev
@ -1,5 +1,13 @@
|
||||
URLPATH = uploads
|
||||
PORT = 3101
|
||||
LINE_ACCESS_TOKEN = lbXrLMGoiOvBTG3vFNlVNQbolcVkQzqi920DVwrbtr1
|
||||
prikeyPath = ./certificate/RSA-privkey.pem
|
||||
certPath = ./certificate/RSA-cert.pem
|
||||
cafilePath = ./certificate/RSA-chain.pem
|
||||
cafilePath = ./certificate/RSA-chain.pem
|
||||
|
||||
# DB----------------------------------------------
|
||||
DB_HOST = 192.168.0.15
|
||||
DB_PORT = 3307
|
||||
DB_USER = jianmiau
|
||||
DB_PASSWORD = VQ*ZetC7xcc9%dTW
|
||||
DB_DATABASE = badminton
|
10
.env.prod
10
.env.prod
@ -1,5 +1,13 @@
|
||||
URLPATH = uploads
|
||||
PORT = 3100
|
||||
LINE_ACCESS_TOKEN = lbXrLMGoiOvBTG3vFNlVNQbolcVkQzqi920DVwrbtr1
|
||||
prikeyPath = /certificate/RSA-privkey.pem
|
||||
certPath = /certificate/RSA-cert.pem
|
||||
cafilePath = /certificate/RSA-chain.pem
|
||||
cafilePath = /certificate/RSA-chain.pem
|
||||
|
||||
# DB----------------------------------------------
|
||||
DB_HOST = 192.168.0.15
|
||||
DB_PORT = 3307
|
||||
DB_USER = jianmiau
|
||||
DB_PASSWORD = VQ*ZetC7xcc9%dTW
|
||||
DB_DATABASE = badminton
|
21
.vscode/launch.json
vendored
Normal file
21
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
// 使用 IntelliSense 以得知可用的屬性。
|
||||
// 暫留以檢視現有屬性的描述。
|
||||
// 如需詳細資訊,請瀏覽: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "Attach to Remote",
|
||||
"address": "jianmiau.tk:9229/87f42d5b-97bf-4d25-a4d7-37306985459a",
|
||||
"port": 9229,
|
||||
"localRoot": "${workspaceFolder}",
|
||||
// "remoteRoot": "W:/home/www/api",
|
||||
"remoteRoot": "/volume1/homes/JianMiau/www/api",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
46
Dockerfile
46
Dockerfile
@ -1,24 +1,19 @@
|
||||
# sudo docker build -t linebotts .
|
||||
|
||||
# 将 Docker 镜像保存为文件
|
||||
# sudo docker save -o linebotts.tar linebotts
|
||||
|
||||
# 使用 scp 传输文件到目标机器
|
||||
# scp -P 20022 linebotts.tar jianmiau@192.168.0.77:~/Nas/docker
|
||||
|
||||
# 在目标机器上加载镜像
|
||||
# sudo docker load -i /path/to/destination/linebotts.tar
|
||||
|
||||
|
||||
|
||||
# sudo docker run -v /volume1/homes/JianMiau/www/certificate:/certificate -e TZ=Asia/Taipei --name=linebotts --restart always --net=host linebotts
|
||||
# sudo docker build -t api .
|
||||
|
||||
# 後續查看容器
|
||||
# docker ps
|
||||
# sudo docker exec -it [Container ID] /bin/bash
|
||||
|
||||
# 選擇node
|
||||
FROM node:19.4.0
|
||||
FROM node:20.9.0
|
||||
|
||||
# 安装系统依赖
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libcairo2-dev \
|
||||
libjpeg-dev \
|
||||
libpango1.0-dev \
|
||||
libgif-dev \
|
||||
build-essential
|
||||
|
||||
# 指定NODE_ENV為production
|
||||
ENV NODE_ENV=production
|
||||
@ -28,8 +23,8 @@ WORKDIR /app
|
||||
|
||||
VOLUME ["/certificate"]
|
||||
|
||||
# 只copy package.json檔案
|
||||
COPY ["package.json", "./"]
|
||||
# 复制 package.json
|
||||
COPY package.json ./
|
||||
|
||||
# 安裝dependencies
|
||||
# If you are building your code for production
|
||||
@ -40,4 +35,19 @@ RUN npm install
|
||||
COPY . .
|
||||
|
||||
# 指定啟動container後執行命令
|
||||
CMD [ "npm", "start" ]
|
||||
CMD [ "npm", "start" ]
|
||||
|
||||
|
||||
|
||||
# 将 Docker 镜像保存为文件
|
||||
# sudo docker save -o api.tar api
|
||||
|
||||
# 使用 scp 传输文件到目标机器
|
||||
# scp -P 20022 api.tar jianmiau@192.168.0.77:~/Nas/docker
|
||||
|
||||
# 在目标机器上加载镜像
|
||||
# sudo docker load -i /path/to/destination/api.tar
|
||||
|
||||
|
||||
|
||||
# sudo docker run -v /volume1/homes/JianMiau/www/certificate:/certificate -e TZ=Asia/Taipei --name=api --restart always --net=host api
|
308
package-lock.json
generated
308
package-lock.json
generated
@ -1,22 +1,26 @@
|
||||
{
|
||||
"name": "canvas",
|
||||
"name": "api",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "canvas",
|
||||
"name": "api",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^1.7.7",
|
||||
"canvas": "^2.11.2",
|
||||
"dateformat": "^4.5.1",
|
||||
"dayjs": "^1.11.7",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.20.0",
|
||||
"form-data": "^4.0.0",
|
||||
"fs": "^0.0.1-security",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"nodemon": "^3.1.4"
|
||||
"mysql": "^2.18.1",
|
||||
"nodemon": "^3.1.4",
|
||||
"ts-node": "^10.9.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dateformat": "^5.0.0",
|
||||
@ -25,6 +29,39 @@
|
||||
"@types/node": "^22.5.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "0.3.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
||||
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
|
||||
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.0.3",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
|
||||
@ -44,6 +81,26 @@
|
||||
"node-pre-gyp": "bin/node-pre-gyp"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
||||
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw=="
|
||||
},
|
||||
"node_modules/@tsconfig/node12": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
|
||||
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag=="
|
||||
},
|
||||
"node_modules/@tsconfig/node14": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
|
||||
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow=="
|
||||
},
|
||||
"node_modules/@tsconfig/node16": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
|
||||
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA=="
|
||||
},
|
||||
"node_modules/@types/body-parser": {
|
||||
"version": "1.19.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
|
||||
@ -118,7 +175,6 @@
|
||||
"version": "22.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
|
||||
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
@ -173,6 +229,28 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.12.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
||||
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-walk": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
|
||||
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
|
||||
"dependencies": {
|
||||
"acorn": "^8.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
@ -227,16 +305,44 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.7",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
|
||||
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"node_modules/bignumber.js": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz",
|
||||
"integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||
@ -413,6 +519,17 @@
|
||||
"color-support": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@ -501,6 +618,11 @@
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
|
||||
},
|
||||
"node_modules/create-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
|
||||
},
|
||||
"node_modules/dateformat": {
|
||||
"version": "4.6.3",
|
||||
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
|
||||
@ -557,6 +679,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delegates": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
@ -587,6 +717,14 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.4.5",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
||||
@ -751,6 +889,38 @@
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@ -1096,6 +1266,11 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
@ -1255,6 +1430,55 @@
|
||||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/mysql": {
|
||||
"version": "2.18.1",
|
||||
"resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz",
|
||||
"integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==",
|
||||
"dependencies": {
|
||||
"bignumber.js": "9.0.0",
|
||||
"readable-stream": "2.3.7",
|
||||
"safe-buffer": "5.1.2",
|
||||
"sqlstring": "2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mysql/node_modules/readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/mysql/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"node_modules/mysql/node_modules/sqlstring": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz",
|
||||
"integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mysql/node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nan": {
|
||||
"version": "2.20.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz",
|
||||
@ -1435,6 +1659,11 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/pstree.remy": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
||||
@ -1847,6 +2076,48 @@
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"node_modules/ts-node": {
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||
"dependencies": {
|
||||
"@cspotcode/source-map-support": "^0.8.0",
|
||||
"@tsconfig/node10": "^1.0.7",
|
||||
"@tsconfig/node12": "^1.0.7",
|
||||
"@tsconfig/node14": "^1.0.0",
|
||||
"@tsconfig/node16": "^1.0.2",
|
||||
"acorn": "^8.4.1",
|
||||
"acorn-walk": "^8.1.1",
|
||||
"arg": "^4.1.0",
|
||||
"create-require": "^1.1.0",
|
||||
"diff": "^4.0.1",
|
||||
"make-error": "^1.1.1",
|
||||
"v8-compile-cache-lib": "^3.0.1",
|
||||
"yn": "3.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"ts-node": "dist/bin.js",
|
||||
"ts-node-cwd": "dist/bin-cwd.js",
|
||||
"ts-node-esm": "dist/bin-esm.js",
|
||||
"ts-node-script": "dist/bin-script.js",
|
||||
"ts-node-transpile-only": "dist/bin-transpile.js",
|
||||
"ts-script": "dist/bin-script-deprecated.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@swc/core": ">=1.2.50",
|
||||
"@swc/wasm": ">=1.2.50",
|
||||
"@types/node": "*",
|
||||
"typescript": ">=2.7"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@swc/core": {
|
||||
"optional": true
|
||||
},
|
||||
"@swc/wasm": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
@ -1864,6 +2135,19 @@
|
||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
|
||||
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undefsafe": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
||||
@ -1872,8 +2156,7 @@
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
@ -1896,6 +2179,11 @@
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/v8-compile-cache-lib": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
@ -1943,6 +2231,14 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,14 +13,18 @@
|
||||
"license": "ISC",
|
||||
"keywords": [],
|
||||
"dependencies": {
|
||||
"axios": "^1.7.7",
|
||||
"canvas": "^2.11.2",
|
||||
"dateformat": "^4.5.1",
|
||||
"dayjs": "^1.11.7",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.20.0",
|
||||
"form-data": "^4.0.0",
|
||||
"fs": "^0.0.1-security",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"nodemon": "^3.1.4"
|
||||
"mysql": "^2.18.1",
|
||||
"nodemon": "^3.1.4",
|
||||
"ts-node": "^10.9.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dateformat": "^5.0.0",
|
||||
@ -28,4 +32,4 @@
|
||||
"@types/multer": "^1.4.12",
|
||||
"@types/node": "^22.5.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
116
src/Engine/CCExtensions/ArrayExtension.ts
Normal file
116
src/Engine/CCExtensions/ArrayExtension.ts
Normal file
@ -0,0 +1,116 @@
|
||||
declare interface Array<T> {
|
||||
/**
|
||||
* 移除一個值並且回傳
|
||||
* @param index
|
||||
*/
|
||||
ExRemoveAt(index: number): T;
|
||||
/**
|
||||
* 移除全部值(注意. 參考的也會被清空)
|
||||
* @example
|
||||
*
|
||||
* let bar: number[] = [1, 2, 3];
|
||||
* let bar2: number[] = bar;
|
||||
* bar.Clear();
|
||||
* console.log(bar, bar2);
|
||||
*
|
||||
* // {
|
||||
* // "bar": [],
|
||||
* // "bar2": []
|
||||
* // }
|
||||
*/
|
||||
Clear(): void;
|
||||
/**
|
||||
* 物件陣列排序,asc&key陣列長度請一樣
|
||||
* PS. boolean 帶false是先true在false
|
||||
* @link JavaScript Object 排序 http://www.eion.com.tw/Blogger/?Pid=1170#:~:text=JavaScript%20Object%20排序
|
||||
* @param asc 是否升序排列(小到大)
|
||||
* @param key 需排序的key(優先順序左到右)(沒有就放空)
|
||||
*/
|
||||
ObjectSort(asc?: boolean[], key?: string[]): any[];
|
||||
/**
|
||||
* 設計給Array<cc.Component.EventHandler>forHoldButton使用
|
||||
* Add a non persistent listener to the UnityEvent.
|
||||
* @param call Callback function.
|
||||
*/
|
||||
AddListener(call: Function): void;
|
||||
}
|
||||
|
||||
Array.prototype.ExRemoveAt || Object.defineProperty(Array.prototype, "ExRemoveAt", {
|
||||
enumerable: false,
|
||||
value: function (index: number): any {
|
||||
let item: any = this.splice(index, 1);
|
||||
return item[0];
|
||||
}
|
||||
});
|
||||
|
||||
Array.prototype.Clear || Object.defineProperty(Array.prototype, "Clear", {
|
||||
enumerable: false,
|
||||
value: function (): void {
|
||||
this.length = 0;
|
||||
|
||||
// let foo: number[] = [1, 2, 3];
|
||||
// let bar: number[] = [1, 2, 3];
|
||||
// let foo2: number[] = foo;
|
||||
// let bar2: number[] = bar;
|
||||
// foo = [];
|
||||
// bar.length = 0;
|
||||
// console.log(foo, bar, foo2, bar2);
|
||||
|
||||
// {
|
||||
// "foo": [],
|
||||
// "bar": [],
|
||||
// "foo2": [
|
||||
// 1,
|
||||
// 2,
|
||||
// 3
|
||||
// ],
|
||||
// "bar2": []
|
||||
// }
|
||||
}
|
||||
});
|
||||
|
||||
Array.prototype.ObjectSort || Object.defineProperty(Array.prototype, "ObjectSort", {
|
||||
enumerable: false,
|
||||
/**
|
||||
* @param asc 是否升序排列(小到大)
|
||||
* @param key 需排序的key(優先順序左到右)(沒有就放空)
|
||||
*/
|
||||
value: function (asc: boolean[] = [true], key?: string[]): any[] {
|
||||
if (this.length === 0) {
|
||||
return this;
|
||||
} else if (!key || key.length === 0) {
|
||||
console.error(`ObjectSort key error`);
|
||||
return this;
|
||||
} else if (asc.length !== key.length) {
|
||||
console.error(`ObjectSort key asc error asc.length: ${asc.length}, key.length: ${key.length}`);
|
||||
return this;
|
||||
}
|
||||
for (let i: number = 0; i < key.length; i++) {
|
||||
const keyname: string = key[i];
|
||||
if (this[0][keyname] === undefined) {
|
||||
console.error(`ObjectSort has not key[${i}]: ${keyname}`);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
let count: number = key ? key.length : 1;
|
||||
let arr: any[];
|
||||
for (let i: number = count - 1; i >= 0; i--) {
|
||||
arr = this.sort(function (a: any, b: any): 1 | -1 {
|
||||
let mya: any = a;
|
||||
let myb: any = b;
|
||||
if (key) {
|
||||
mya = a[key[i]];
|
||||
myb = b[key[i]];
|
||||
}
|
||||
|
||||
// 加個等於數字相同不要再去排序到
|
||||
if (asc[i]) {
|
||||
return mya >= myb ? 1 : -1;
|
||||
} else {
|
||||
return mya <= myb ? 1 : -1;
|
||||
}
|
||||
});
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
});
|
10
src/Engine/CCExtensions/CCExtension.ts.meta
Normal file
10
src/Engine/CCExtensions/CCExtension.ts.meta
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"ver": "1.1.0",
|
||||
"uuid": "b373f805-9297-4af5-8ea6-0a250649b5b0",
|
||||
"importer": "typescript",
|
||||
"isPlugin": false,
|
||||
"loadPluginInWeb": true,
|
||||
"loadPluginInNative": true,
|
||||
"loadPluginInEditor": false,
|
||||
"subMetas": {}
|
||||
}
|
189
src/Engine/CCExtensions/NumberExtension.ts
Normal file
189
src/Engine/CCExtensions/NumberExtension.ts
Normal file
@ -0,0 +1,189 @@
|
||||
|
||||
declare interface Number {
|
||||
|
||||
/**
|
||||
* 金額每三位數(千)加逗號, 並且補到小數點第2位
|
||||
* 輸出 41,038,560.00
|
||||
* @param precision 補到小數點第幾位
|
||||
* @param isPadZero 是否要補零
|
||||
* */
|
||||
ExFormatNumberWithComma(precision?: number, isPadZero?: boolean): string;
|
||||
/**
|
||||
* 基本4位數(9,999-999B-T)
|
||||
* */
|
||||
ExTransferToBMK(precision?: number,offset?: number): string;
|
||||
/**
|
||||
* 數字轉字串, 頭補0
|
||||
* @param size
|
||||
*/
|
||||
Pad(size: number): string;
|
||||
/**
|
||||
* 四捨五入到小數點第X位 (同server計算規則)
|
||||
* @param precision
|
||||
*/
|
||||
ExToNumRoundDecimal(precision: number): number;
|
||||
/**
|
||||
* 無條件捨去到小數點第X位
|
||||
* @param precision
|
||||
*/
|
||||
ExToNumFloorDecimal(precision: number): number;
|
||||
/**
|
||||
* 無條件捨去強制保留X位小數,如:2,會在2後面補上00.即2.00
|
||||
* @param precision 補到小數點第幾位
|
||||
* @param isPadZero 是否要補零
|
||||
*/
|
||||
ExToStringFloorDecimal(precision: number, isPadZero?: boolean): string;
|
||||
/**
|
||||
* 取整數)
|
||||
*/
|
||||
ExToInt():number;
|
||||
/**
|
||||
* 小數轉整數(支援科學符號)
|
||||
*/
|
||||
Float2Fixed():number;
|
||||
/**
|
||||
* 數字長度(支援科學符號)
|
||||
*/
|
||||
DigitLength():number;
|
||||
|
||||
target: number;
|
||||
|
||||
|
||||
}
|
||||
|
||||
Number.prototype.ExFormatNumberWithComma || Object.defineProperty(Number.prototype, 'ExFormatNumberWithComma', {
|
||||
enumerable: false,
|
||||
value: function (precision: number = 2, isPadZero: boolean = true) {
|
||||
|
||||
// let arr = String(this).split('.');
|
||||
let arr = this.ExToStringFloorDecimal(precision, isPadZero).split('.');
|
||||
let num = arr[0], result = '';
|
||||
while (num.length > 3) {
|
||||
result = ',' + num.slice(-3) + result;
|
||||
num = num.slice(0, num.length - 3);
|
||||
}
|
||||
if (num.length > 0) result = num + result;
|
||||
return arr[1] ? result + '.' + arr[1] : result;
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Number.prototype.ExTransferToBMK || Object.defineProperty(Number.prototype, 'ExTransferToBMK', {
|
||||
enumerable: false,
|
||||
value: function (precision: number=2,offset: number = 0) {
|
||||
/**千 */
|
||||
let MONEY_1K: number = 1000;
|
||||
/**萬 */
|
||||
// let MONEY_10K: number = 10000;
|
||||
/**十萬 */
|
||||
// let MONEY_100K: number = 100000;
|
||||
/**百萬 */
|
||||
let MONEY_1M: number = 1000000;
|
||||
/**千萬 */
|
||||
// let MONEY_10M: number = 10000000;
|
||||
/**億 */
|
||||
// let MONEY_100M: number = 100000000;
|
||||
/**十億 */
|
||||
let MONEY_1B: number = 1000000000;
|
||||
/**百億 */
|
||||
// let MONEY_10B: number = 10000000000;
|
||||
/**千億 */
|
||||
// let MONEY_100B: number = 100000000000;
|
||||
/**兆 */
|
||||
// let MONEY_1T: number = 1000000000000;
|
||||
offset = Math.pow(10, offset);
|
||||
// if (this >= MONEY_1T * offset) {
|
||||
// //(3)1,000T
|
||||
// //1T~
|
||||
// return (~~(this / MONEY_1T)).ExFormatNumberWithComma(0) + "T";
|
||||
// }
|
||||
if (this >= MONEY_1B * offset) {
|
||||
//1,000B~900,000B
|
||||
//1B~900B
|
||||
return (this / MONEY_1B).ExFormatNumberWithComma(3, false) + "B";
|
||||
}
|
||||
else if (this >= MONEY_1M * offset) {
|
||||
//1,000M~900,000M
|
||||
//1M~900M
|
||||
return (this / MONEY_1M).ExFormatNumberWithComma(3, false) + "M";
|
||||
}
|
||||
else if (this >= MONEY_1K * offset) {
|
||||
//1,000K~900,000K
|
||||
//1K~90K
|
||||
return (this / MONEY_1K).ExFormatNumberWithComma(3, false) + "K";
|
||||
}
|
||||
else {
|
||||
//0~9,000,000
|
||||
//0~9,000
|
||||
return this.ExFormatNumberWithComma(precision);
|
||||
}
|
||||
}
|
||||
})
|
||||
Number.prototype.Pad || Object.defineProperty(Number.prototype, 'Pad', {
|
||||
enumerable: false,
|
||||
value: function (size: number) {
|
||||
let s = this + "";
|
||||
while (s.length < size) s = "0" + s;
|
||||
return s;
|
||||
}
|
||||
})
|
||||
Number.prototype.ExToNumRoundDecimal || Object.defineProperty(Number.prototype, 'ExToNumRoundDecimal', {
|
||||
enumerable: false,
|
||||
value: function (precision: number) {
|
||||
return Math.round(Math.round(this * Math.pow(10, (precision || 0) + 1)) / 10) / Math.pow(10, (precision || 0));
|
||||
}
|
||||
})
|
||||
Number.prototype.ExToInt || Object.defineProperty(Number.prototype, 'ExToInt',{
|
||||
enumerable: false,
|
||||
value: function (){
|
||||
return ~~this;
|
||||
}
|
||||
})
|
||||
Number.prototype.ExToNumFloorDecimal || Object.defineProperty(Number.prototype, 'ExToNumFloorDecimal', {
|
||||
enumerable: false,
|
||||
value: function (precision: number) {
|
||||
let str = this.toPrecision(12);
|
||||
let dotPos = str.indexOf('.');
|
||||
return dotPos == -1 ? this : +`${str.substr(0, dotPos + 1 + precision)}`;
|
||||
}
|
||||
})
|
||||
Number.prototype.ExToStringFloorDecimal || Object.defineProperty(Number.prototype, 'ExToStringFloorDecimal', {
|
||||
enumerable: false,
|
||||
value: function (precision: number, isPadZero: boolean = true) {
|
||||
// 取小數點第X位
|
||||
let f = this.ExToNumFloorDecimal(precision);
|
||||
let s = f.toString();
|
||||
// 補0
|
||||
if (isPadZero) {
|
||||
let rs = s.indexOf('.');
|
||||
if (rs < 0) {
|
||||
rs = s.length;
|
||||
s += '.';
|
||||
}
|
||||
while (s.length <= rs + precision) {
|
||||
s += '0';
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
})
|
||||
Number.prototype.Float2Fixed || Object.defineProperty(Number.prototype, 'Float2Fixed', {
|
||||
enumerable: false,
|
||||
value: function () {
|
||||
if (this.toString().indexOf('e') === -1) {
|
||||
return Number(this.toString().replace('.', ''));
|
||||
}
|
||||
const dLen = this.DigitLength();
|
||||
return dLen > 0 ? +parseFloat((this * Math.pow(10, dLen)).toPrecision(12)) : this;
|
||||
}
|
||||
})
|
||||
Number.prototype.DigitLength || Object.defineProperty(Number.prototype, 'DigitLength', {
|
||||
enumerable: false,
|
||||
value: function () {
|
||||
const eSplit = this.toString().split(/[eE]/);
|
||||
const len = (eSplit[0].split('.')[1] || '').length - (+(eSplit[1] || 0));
|
||||
return len > 0 ? len : 0;
|
||||
}
|
||||
})
|
||||
|
||||
|
84
src/Engine/Number/NumberEx.ts
Normal file
84
src/Engine/Number/NumberEx.ts
Normal file
@ -0,0 +1,84 @@
|
||||
export module NumberEx {
|
||||
|
||||
/**
|
||||
* 检测数字是否越界,如果越界给出提示
|
||||
* @param {*number} num 输入数
|
||||
*/
|
||||
function checkBoundary(num: number) {
|
||||
if (_boundaryCheckingState) {
|
||||
if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) {
|
||||
console.warn(`${num} is beyond boundary when transfer to integer, the results may not be accurate`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 精确乘法
|
||||
*/
|
||||
export function times(num1: number, num2: number, ...others: number[]): number {
|
||||
if (others.length > 0) {
|
||||
return times(times(num1, num2), others[0], ...others.slice(1));
|
||||
}
|
||||
const num1Changed = num1.Float2Fixed();
|
||||
const num2Changed = num2.Float2Fixed();
|
||||
const baseNum = num1.DigitLength() + num2.DigitLength();
|
||||
const leftValue = num1Changed * num2Changed;
|
||||
|
||||
checkBoundary(leftValue);
|
||||
|
||||
return leftValue / Math.pow(10, baseNum);
|
||||
}
|
||||
|
||||
/**
|
||||
* 精确加法
|
||||
*/
|
||||
export function plus(num1: number, num2: number, ...others: number[]): number {
|
||||
if (others.length > 0) {
|
||||
return plus(plus(num1, num2), others[0], ...others.slice(1));
|
||||
}
|
||||
const baseNum = Math.pow(10, Math.max(num1.DigitLength(), num2.DigitLength()));
|
||||
return (times(num1, baseNum) + times(num2, baseNum)) / baseNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* 精确减法
|
||||
*/
|
||||
export function minus(num1: number, num2: number, ...others: number[]): number {
|
||||
if (others.length > 0) {
|
||||
return minus(minus(num1, num2), others[0], ...others.slice(1));
|
||||
}
|
||||
const baseNum = Math.pow(10, Math.max(num1.DigitLength(), num2.DigitLength()));
|
||||
return (times(num1, baseNum) - times(num2, baseNum)) / baseNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* 精确除法
|
||||
*/
|
||||
export function divide(num1: number, num2: number, ...others: number[]): number {
|
||||
if (others.length > 0) {
|
||||
return divide(divide(num1, num2), others[0], ...others.slice(1));
|
||||
}
|
||||
const num1Changed = num1.Float2Fixed();
|
||||
const num2Changed = num2.Float2Fixed();
|
||||
checkBoundary(num1Changed);
|
||||
checkBoundary(num2Changed);
|
||||
return times((num1Changed / num2Changed), Math.pow(10, num2.DigitLength() - num1.DigitLength()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 四舍五入
|
||||
*/
|
||||
export function round(num: number, ratio: number): number {
|
||||
const base = Math.pow(10, ratio);
|
||||
return divide(Math.round(times(num, base)), base);
|
||||
}
|
||||
|
||||
let _boundaryCheckingState = false;
|
||||
/**
|
||||
* 是否进行边界检查
|
||||
* @param flag 标记开关,true 为开启,false 为关闭
|
||||
*/
|
||||
function enableBoundaryChecking(flag = true) {
|
||||
_boundaryCheckingState = flag;
|
||||
}
|
||||
}
|
90
src/Engine/Number/RandomEx.ts
Normal file
90
src/Engine/Number/RandomEx.ts
Normal file
@ -0,0 +1,90 @@
|
||||
|
||||
export module RandomEx {
|
||||
|
||||
/**
|
||||
* 取得隨機布林值
|
||||
*/
|
||||
export function GetBool() {
|
||||
return GetInt() >= 0;
|
||||
}
|
||||
/**
|
||||
* 取得隨機整數(回傳min ~ max - 1)
|
||||
* @param min
|
||||
* @param max
|
||||
*/
|
||||
export function GetInt(min: number = Number.MIN_VALUE, max: number = Number.MAX_VALUE): number {
|
||||
return Math.floor(Math.random() * (max - min)) + min;
|
||||
}
|
||||
/**
|
||||
* 取得隨機小數
|
||||
* @param min
|
||||
* @param max
|
||||
*/
|
||||
export function GetFloat(min: number = Number.MIN_VALUE, max: number = Number.MAX_VALUE): number {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
/**
|
||||
* 隨機取得複數個不重複回傳
|
||||
* @param num 取得數量
|
||||
* @param items 陣列
|
||||
*/
|
||||
export function GetMultiNoRepeat(num: number, items: any[]): any[] {
|
||||
let result: any[] = [];
|
||||
for (let i: number = 0; i < num; i++) {
|
||||
let ran: number = Math.floor(Math.random() * items.length);
|
||||
let item = items.splice(ran, 1)[0];
|
||||
if (result.indexOf(item) == -1) {
|
||||
result.push(item);
|
||||
}
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根據權重取得複數個不重複回傳
|
||||
* @param prize 獎項
|
||||
* @param weights 機率
|
||||
* @param count 數量
|
||||
*/
|
||||
export function GetMultiNoRepeatByWeight(prize: any[], weights: number[] = null, count: number = 1): any[] {
|
||||
if (weights === null) {
|
||||
weights = [];
|
||||
for (let i: number = 0; i < prize.length; i++) {
|
||||
weights.push(1);
|
||||
}
|
||||
}
|
||||
let target: any[] = [];
|
||||
for (let i: number = 0; i < count; i++) {
|
||||
let results: number[] = RandomEx.GetPrizeByWeight(prize, weights);
|
||||
prize.splice(results[0], 1);
|
||||
weights.splice(results[0], 1);
|
||||
target.push(results[1]);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根據權重隨機取值
|
||||
* @param prize 獎項
|
||||
* @param weights 機率
|
||||
*/
|
||||
export function GetPrizeByWeight(prize: any[], weights: number[]): any[] {
|
||||
if (prize.length !== weights.length) {
|
||||
console.error(`GetWeight error -> prize.length:${prize.length} !== weights.length:${weights.length}`);
|
||||
return null;
|
||||
}
|
||||
let totalWeight: number = 0;
|
||||
for (let i: number = 0; i < weights.length; i++) {
|
||||
totalWeight += weights[i];
|
||||
}
|
||||
let random: number = RandomEx.GetInt(0, totalWeight) + 1;
|
||||
let nowWeight: number = weights[0];
|
||||
for (let i: number = 0; i < weights.length; i++) {
|
||||
if (nowWeight >= random) {
|
||||
return [i, prize[i]];
|
||||
}
|
||||
nowWeight += weights[i + 1];
|
||||
}
|
||||
}
|
||||
}
|
16
src/Engine/String.ts
Normal file
16
src/Engine/String.ts
Normal file
@ -0,0 +1,16 @@
|
||||
interface StringConstructor {
|
||||
IsNullOrEmpty: (value: string) => boolean;
|
||||
Format: (format: string, ...args: any[]) => string;
|
||||
}
|
||||
|
||||
String.IsNullOrEmpty = function (value: string): boolean {
|
||||
return value === undefined || value === null || value.trim() === "";
|
||||
};
|
||||
|
||||
String.Format = function (format: string, ...args: any[]): string {
|
||||
return format.replace(/{(\d+)}/g, (match, index) => {
|
||||
let value: any = args[index];
|
||||
if (value === null || value === undefined) { return ""; }
|
||||
return "" + value;
|
||||
});
|
||||
};
|
13
src/Tools.ts
Normal file
13
src/Tools.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Tools
|
||||
*/
|
||||
export default class Tools {
|
||||
|
||||
//#region Custom
|
||||
|
||||
public static Sleep(ms: number): Promise<any> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
27
src/app.ts
27
src/app.ts
@ -6,12 +6,15 @@
|
||||
import dayjs from "dayjs";
|
||||
import "dayjs/locale/zh-tw";
|
||||
import dotenv from "dotenv";
|
||||
import "./Engine/CCExtensions/ArrayExtension";
|
||||
import "./Engine/CCExtensions/NumberExtension";
|
||||
import "./Engine/String";
|
||||
|
||||
import bodyParser from "body-parser";
|
||||
import dateFormat from "dateformat";
|
||||
import express from 'express';
|
||||
import express from "express";
|
||||
import fs from "fs";
|
||||
import https from 'https';
|
||||
import multer from 'multer';
|
||||
import https from "https";
|
||||
import { generateImage } from "./canvas";
|
||||
|
||||
dayjs.locale("zh-tw");
|
||||
@ -24,7 +27,6 @@ if (process.env.NODE_ENV) {
|
||||
const path: string = process.env.URLPATH || "/";
|
||||
const port: number = +process.env.PORT || 3000;
|
||||
const app = express();
|
||||
const upload = multer({ dest: `${path}/` }); // 用于处理文件上传
|
||||
|
||||
//讀取憑證及金鑰
|
||||
const prikey: string = fs.readFileSync(process.env.prikeyPath, "utf8");
|
||||
@ -38,11 +40,18 @@ const credentials: Object = {
|
||||
ca: cafile
|
||||
};
|
||||
|
||||
// 处理 POST 请求并返回生成的图片
|
||||
app.post(`/${path}`, upload.single('file'), (req, res) => {
|
||||
const imageBuffer = generateImage();
|
||||
res.set('Content-Type', 'image/png');
|
||||
res.send(imageBuffer);
|
||||
app.use(bodyParser.json());
|
||||
// 设置 POST 路由来返回图片
|
||||
app.post(`/${path}`, (req, res) => {
|
||||
try {
|
||||
const data = req.body;
|
||||
const imageBuffer = generateImage(data);
|
||||
res.setHeader("Content-Type", "image/png");
|
||||
res.send(imageBuffer);
|
||||
} catch (error) {
|
||||
console.error("Error generating image:", error);
|
||||
res.status(500).send("Error generating image");
|
||||
}
|
||||
});
|
||||
|
||||
// 创建 HTTPS 服务器
|
||||
|
131
src/canvas.ts
131
src/canvas.ts
@ -1,24 +1,36 @@
|
||||
import { createCanvas } from 'canvas';
|
||||
import { createCanvas, registerFont } from "canvas";
|
||||
import dateFormat from "dateformat";
|
||||
import { lineNotify } from "./lineNotify";
|
||||
import { sqlSendQuery } from "./sql";
|
||||
|
||||
// 生成图片的函数
|
||||
export function generateImage() {
|
||||
const boys = ['男生A', '男生B', '男生C'];
|
||||
const girls = ['女生A', '女生B', '女生C'];
|
||||
const rounds = 3; // 可以根据需要设置轮数
|
||||
const person = boys.length > girls.length ? boys.length : girls.length;
|
||||
if (boys.length > girls.length) {
|
||||
girls.push("那個")
|
||||
} else if (boys.length < girls.length) {
|
||||
boys.push("那個")
|
||||
export function generateImage(props: ICanvas) {
|
||||
const { date, team, rounds = 3 } = props;
|
||||
const formatDate = `${date.slice(0, 4)}-${date.slice(4, 6)}-${date.slice(6, 8)}`;
|
||||
const { aTeam, bTeam } = splitTeams(team);
|
||||
|
||||
if (!aTeam || !bTeam) {
|
||||
return "";
|
||||
}
|
||||
const person = aTeam.length > bTeam.length ? aTeam.length : bTeam.length;
|
||||
if (aTeam.length > bTeam.length) {
|
||||
bTeam.push("那個")
|
||||
} else if (aTeam.length < bTeam.length) {
|
||||
aTeam.push("那個")
|
||||
}
|
||||
const canvasWidth = 800; // 根据需要调整宽度
|
||||
const canvasHeight = (rounds * 100) + (person * 10); // 根据需要调整高度
|
||||
const canvas = createCanvas(canvasWidth, canvasHeight);
|
||||
const ctx = canvas.getContext('2d');
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
|
||||
// 注册自定义字体
|
||||
const fontPath = "./src/fonts/PMingLiU.ttf";
|
||||
registerFont(fontPath, { family: "PMingLiU" });
|
||||
const font = "PMingLiU";
|
||||
|
||||
// 设定背景颜色
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fillStyle = "white";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// 追踪已使用的组合
|
||||
@ -27,11 +39,11 @@ export function generateImage() {
|
||||
// 生成不重复的队伍组合并确保每人上场
|
||||
function generateTeams(): { teamA: string, teamB: string }[] {
|
||||
const roundTeams: { teamA: string, teamB: string }[] = [];
|
||||
const availableBoys = [...boys];
|
||||
const availableGirls = [...girls];
|
||||
const availableBoys = [...aTeam];
|
||||
const availableGirls = [...bTeam];
|
||||
let attempts = 0;
|
||||
|
||||
while (roundTeams.length < boys.length) {
|
||||
while (roundTeams.length < aTeam.length) {
|
||||
const boyIndex = Math.floor(Math.random() * availableBoys.length);
|
||||
const girlIndex = Math.floor(Math.random() * availableGirls.length);
|
||||
const team = `${availableBoys[boyIndex]}-${availableGirls[girlIndex]}`;
|
||||
@ -49,7 +61,7 @@ export function generateImage() {
|
||||
|
||||
// 如果尝试过多次未找到合适组合,强制生成剩余队伍
|
||||
attempts++;
|
||||
if (attempts > 10 && roundTeams.length < boys.length) {
|
||||
if (attempts > 10 && roundTeams.length < aTeam.length) {
|
||||
for (let i = 0; i < availableBoys.length; i++) {
|
||||
roundTeams.push({
|
||||
teamA: availableBoys[i],
|
||||
@ -70,43 +82,43 @@ export function generateImage() {
|
||||
teamCombinations.push(newRound);
|
||||
|
||||
// 如果所有组合都用过了,重置组合记录
|
||||
if (usedCombinations.size >= boys.length * girls.length) {
|
||||
if (usedCombinations.size >= aTeam.length * bTeam.length) {
|
||||
usedCombinations.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制标题和日期
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillStyle = "black";
|
||||
ctx.font = `bold 24px "${font}"`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('比賽隊伍', canvasWidth / 2, 40);
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(`${formatDate} 比賽隊伍`, canvasWidth / 2, 40);
|
||||
|
||||
ctx.font = `italic 16px "${font}"`;
|
||||
ctx.fillText('2024-09-09', canvasWidth / 2, 70);
|
||||
|
||||
const roundTitleYStart = 120;
|
||||
const roundTitleYStart = 80;
|
||||
const teamYStart = 60;
|
||||
const lineHeight = 30;
|
||||
const roundsPerRow = 3;
|
||||
const rowHeight = 300;
|
||||
|
||||
const result = {};
|
||||
teamCombinations.forEach((roundTeams, roundIndex) => {
|
||||
result[roundIndex] = []
|
||||
const rowIndex = Math.floor(roundIndex / roundsPerRow);
|
||||
const colIndex = roundIndex % roundsPerRow;
|
||||
const roundXPosition = (canvasWidth / roundsPerRow) * colIndex + (canvasWidth / roundsPerRow) / 2;
|
||||
const roundYPosition = roundTitleYStart + rowIndex * rowHeight;
|
||||
|
||||
ctx.font = `bold 18px "${font}"`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(`第 ${roundIndex + 1} 輪`, roundXPosition, roundYPosition);
|
||||
|
||||
ctx.font = `16px "${font}"`;
|
||||
|
||||
roundTeams.forEach((team, teamIndex) => {
|
||||
result[roundIndex].push([team.teamA, team.teamB])
|
||||
const teamYPosition = roundYPosition + teamYStart + teamIndex * lineHeight;
|
||||
if (teamIndex === 0) {
|
||||
ctx.fillText('1號隊友', roundXPosition - 50, teamYPosition);
|
||||
ctx.fillText('2號隊友', roundXPosition + 50, teamYPosition);
|
||||
ctx.fillText("1號隊友", roundXPosition - 50, teamYPosition);
|
||||
ctx.fillText("2號隊友", roundXPosition + 50, teamYPosition);
|
||||
}
|
||||
ctx.fillText(team.teamA, roundXPosition - 50, teamYPosition + lineHeight);
|
||||
ctx.fillText(team.teamB, roundXPosition + 50, teamYPosition + lineHeight);
|
||||
@ -114,8 +126,67 @@ export function generateImage() {
|
||||
});
|
||||
|
||||
// 导出图片
|
||||
const buffer = canvas.toBuffer('image/png');
|
||||
// fs.writeFileSync('./team_combination.png', buffer);
|
||||
console.log('圖片已生成!');
|
||||
const buffer = canvas.toBuffer("image/png");
|
||||
// fs.writeFileSync("./team_combination.png", buffer);
|
||||
let dateTime: string = dateFormat(new Date(), "yyyy-mm-dd HH:MM:ss");
|
||||
console.log(`${dateTime} [canvas] 圖片已生成!`);
|
||||
|
||||
|
||||
const personnelStr: string = JSON.stringify(team);
|
||||
const resultStr: string = JSON.stringify(result);
|
||||
const query: string = String.Format("INSERT INTO `badminton` (time,personnel,battlecombination) VALUES ({0},'{1}','{2}') ON DUPLICATE KEY UPDATE personnel='{1}',battlecombination='{2}';"
|
||||
, date, personnelStr, resultStr);
|
||||
sqlSendQuery(query);
|
||||
|
||||
const message = `\n${formatDate} 比賽隊伍`;
|
||||
lineNotify({
|
||||
LINE_ACCESS_TOKEN: process.env.LINE_ACCESS_TOKEN,
|
||||
message,
|
||||
imageFile: buffer
|
||||
})
|
||||
return buffer;
|
||||
}
|
||||
|
||||
interface ICanvas {
|
||||
date: string;
|
||||
team: any;
|
||||
rounds: number;
|
||||
}
|
||||
|
||||
function splitTeams(data: [number, string][]): { aTeam: string[], bTeam: string[] } {
|
||||
const aTeam: string[] = [];
|
||||
const bTeam: string[] = [];
|
||||
|
||||
// 分离 boys 和 girls
|
||||
data.forEach(([type, name]) => {
|
||||
if (type === 1) {
|
||||
aTeam.push(name);
|
||||
} else if (type === 0) {
|
||||
bTeam.push(name);
|
||||
}
|
||||
});
|
||||
|
||||
// 进行平衡
|
||||
while (aTeam.length > bTeam.length + 1) {
|
||||
bTeam.push(aTeam.pop()!); // 移动一名男生到女生
|
||||
}
|
||||
|
||||
// 平衡后可能还有空缺
|
||||
const missingBoys = aTeam.length;
|
||||
const missingGirls = bTeam.length;
|
||||
const balanceSize = Math.max(missingBoys, missingGirls);
|
||||
|
||||
if (missingBoys > balanceSize) {
|
||||
// 若男生过多,添加补充人员到女生
|
||||
for (let i = 0; i < missingBoys; i++) {
|
||||
bTeam.push("那個");
|
||||
}
|
||||
} else if (missingGirls > balanceSize) {
|
||||
// 若女生过多,添加补充人员到男生
|
||||
for (let i = 0; i < missingGirls; i++) {
|
||||
aTeam.push("那個");
|
||||
}
|
||||
}
|
||||
|
||||
return { aTeam, bTeam };
|
||||
}
|
BIN
src/fonts/PMingLiU.ttf
Normal file
BIN
src/fonts/PMingLiU.ttf
Normal file
Binary file not shown.
35
src/lineNotify.ts
Normal file
35
src/lineNotify.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import axios from "axios";
|
||||
import FormData from "form-data";
|
||||
|
||||
// lineNotify
|
||||
export function lineNotify(props: ILineNotify) {
|
||||
const { LINE_ACCESS_TOKEN, message, imageFile } = props;
|
||||
let data = new FormData();
|
||||
data.append("message", message);
|
||||
if (imageFile) {
|
||||
// 添加圖像文件
|
||||
data.append("imageFile", imageFile, "image.jpg");
|
||||
}
|
||||
|
||||
let config = {
|
||||
method: "post",
|
||||
maxBodyLength: Infinity,
|
||||
url: "https://notify-api.line.me/api/notify",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${LINE_ACCESS_TOKEN}`,
|
||||
...data.getHeaders()
|
||||
},
|
||||
data: data
|
||||
};
|
||||
|
||||
axios.request(config)
|
||||
.then((response) => {
|
||||
// console.log(JSON.stringify(response.data));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
interface ILineNotify { LINE_ACCESS_TOKEN: string, message: string, imageFile?: Buffer }
|
36
src/sql.ts
Normal file
36
src/sql.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import mysql from "mysql";
|
||||
import Tools from "./Tools";
|
||||
|
||||
export async function sqlSendQuery(query: string) {
|
||||
try {
|
||||
const connection: mysql.Connection = mysql.createConnection({
|
||||
host: process.env.DB_HOST,
|
||||
port: +process.env.DB_PORT,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE
|
||||
});
|
||||
connection.connect();
|
||||
|
||||
let resp: any = null;
|
||||
let run: boolean = true;
|
||||
connection.query(query, function (err: mysql.MysqlError, rows: any, fields: mysql.FieldInfo[]): void {
|
||||
if (err) {
|
||||
console.error(`${query} Error: \n${err.message}`);
|
||||
run = false;
|
||||
}
|
||||
resp = rows;
|
||||
run = false;
|
||||
});
|
||||
while (run) {
|
||||
await Tools.Sleep(100);
|
||||
}
|
||||
// 释放连接
|
||||
connection.end();
|
||||
return resp;
|
||||
|
||||
} catch (error) {
|
||||
console.error("MySQL 连接失败:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user