diff --git a/Server/package.json b/Server/package.json index 20bb5e3..8cc4802 100644 --- a/Server/package.json +++ b/Server/package.json @@ -23,6 +23,7 @@ "cors": "^2.8.5", "express": "^4.19.2", "ioredis": "^5.4.1", - "mysql2": "^3.9.7" + "mysql2": "^3.9.7", + "sharp": "^0.33.5" } } diff --git a/Server/pnpm-lock.yaml b/Server/pnpm-lock.yaml index 6b03d61..45f84e1 100644 --- a/Server/pnpm-lock.yaml +++ b/Server/pnpm-lock.yaml @@ -23,6 +23,9 @@ dependencies: mysql2: specifier: ^3.9.7 version: 3.9.7 + sharp: + specifier: ^0.33.5 + version: 0.33.5 devDependencies: '@types/cors': @@ -50,6 +53,206 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true + /@emnapi/runtime@1.2.0: + resolution: {integrity: sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==} + requiresBuild: true + dependencies: + tslib: 2.7.0 + dev: false + optional: true + + /@img/sharp-darwin-arm64@0.33.5: + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + dev: false + optional: true + + /@img/sharp-darwin-x64@0.33.5: + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + dev: false + optional: true + + /@img/sharp-libvips-darwin-arm64@1.0.4: + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-darwin-x64@1.0.4: + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-arm64@1.0.4: + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-arm@1.0.5: + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-s390x@1.0.4: + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-x64@1.0.4: + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linuxmusl-arm64@1.0.4: + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + libc: [musl] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linuxmusl-x64@1.0.4: + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + libc: [musl] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-linux-arm64@0.33.5: + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + dev: false + optional: true + + /@img/sharp-linux-arm@0.33.5: + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + dev: false + optional: true + + /@img/sharp-linux-s390x@0.33.5: + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + dev: false + optional: true + + /@img/sharp-linux-x64@0.33.5: + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + dev: false + optional: true + + /@img/sharp-linuxmusl-arm64@0.33.5: + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + dev: false + optional: true + + /@img/sharp-linuxmusl-x64@0.33.5: + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + dev: false + optional: true + + /@img/sharp-wasm32@0.33.5: + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + requiresBuild: true + dependencies: + '@emnapi/runtime': 1.2.0 + dev: false + optional: true + + /@img/sharp-win32-ia32@0.33.5: + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-win32-x64@0.33.5: + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@ioredis/commands@1.2.0: resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} dev: false @@ -248,6 +451,32 @@ packages: engines: {node: '>=0.10.0'} dev: false + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: false + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: false + + /color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + dev: false + + /color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + dev: false + /combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -340,6 +569,11 @@ packages: engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} dev: false + /detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + dev: false + /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -559,6 +793,10 @@ packages: engines: {node: '>= 0.10'} dev: false + /is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + dev: false + /is-property@1.0.2: resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} dev: false @@ -738,6 +976,12 @@ packages: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: false + /semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + dev: false + /send@0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} engines: {node: '>= 0.8.0'} @@ -791,6 +1035,36 @@ packages: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} dev: false + /sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + requiresBuild: true + dependencies: + color: 4.2.3 + detect-libc: 2.0.3 + semver: 7.6.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + dev: false + /side-channel@1.0.6: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} @@ -801,6 +1075,12 @@ packages: object-inspect: 1.13.1 dev: false + /simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + dependencies: + is-arrayish: 0.3.2 + dev: false + /sqlstring@2.3.3: resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} engines: {node: '>= 0.6'} @@ -851,6 +1131,12 @@ packages: yn: 3.1.1 dev: true + /tslib@2.7.0: + resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + requiresBuild: true + dev: false + optional: true + /type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} diff --git a/Server/src/APIs/CheckCaptcha.ts b/Server/src/APIs/CheckCaptcha.ts new file mode 100644 index 0000000..4f29bf1 --- /dev/null +++ b/Server/src/APIs/CheckCaptcha.ts @@ -0,0 +1,41 @@ +import { API } from "../Plugs/API/API"; +import ServerStdResponse from "../ServerStdResponse"; +import captchaSession from "../Plugs/Service/captchaSession"; + +// 检查人机验证 +class CheckCaptcha extends API { + constructor() { + super('POST', '/checkCaptcha'); + } + + public async onRequset(data: any, res: any) { + let { session, rotateDeg } = data; + if (!session || !rotateDeg) { + return res.json(ServerStdResponse.PARAMS_MISSING); + } + switch (await captchaSession.check(session, rotateDeg)) { + case 0: + // 验证码已过期或服务器错误 + res.json(ServerStdResponse.CAPTCHA.NOTFOUND); + break; + case 1: + // 验证通过 + res.json(ServerStdResponse.OK); + break; + case -1: + // 超过最大尝试次数 + res.json(ServerStdResponse.CAPTCHA.MAX_TRY_COUNT); + break; + case -2: + // 角度不正确 + res.json(ServerStdResponse.CAPTCHA.NOTRIGHT); + break; + default: + // 未知错误 + res.json(ServerStdResponse.SERVER_ERROR); + break; + } + } +} + +export default CheckCaptcha; \ No newline at end of file diff --git a/Server/src/APIs/GetCaptcha.ts b/Server/src/APIs/GetCaptcha.ts new file mode 100644 index 0000000..242c27c --- /dev/null +++ b/Server/src/APIs/GetCaptcha.ts @@ -0,0 +1,36 @@ +import fs from "fs"; +import { API } from "../Plugs/API/API"; +import ServerStdResponse from "../ServerStdResponse"; +import captchaSession from "../Plugs/Service/captchaSession"; +import path from "path"; +import sharp from "sharp"; +import crypto from 'crypto' + +// 获取人机验证图片及标识符 +class GetCaptcha extends API { + constructor() { + super('GET', '/captcha'); + } + + public async onRequset(data: any, res: any) { + const imgsPath = path.join(__dirname, '../assets/captchaImgs'); + const fileList = fs.readdirSync(imgsPath) + const imgPath = path.join(imgsPath, fileList[Math.floor(Math.random() * fileList.length)]); + const rotateDeg = Math.floor(Math.random() * 240) + 60; + const img = Buffer.from(await sharp(imgPath).rotate(-rotateDeg).toBuffer()).toString('base64') + const session = crypto.createHash('md5').update(`${Math.random()} ${Date.now()}`).digest('hex'); + if (await captchaSession.add(session, rotateDeg)) { + return res.json({ + ...ServerStdResponse.OK, data: { + img: img, + session: session, + imgPreStr: 'data:image/jpeg;base64,' + } + }); + } else { + return res.json(ServerStdResponse.SERVER_ERROR) + } + } +} + +export default GetCaptcha; \ No newline at end of file diff --git a/Server/src/Plugs/RedisConnection.ts b/Server/src/Plugs/RedisConnection.ts new file mode 100644 index 0000000..403087e --- /dev/null +++ b/Server/src/Plugs/RedisConnection.ts @@ -0,0 +1,43 @@ +import Redis from 'ioredis'; +import config from '../config'; +import Logger from './Logger'; + +class RedisConnection { + private pool?: Redis + private logger = new Logger('Redis') + + constructor() { + try { + this.pool = new Redis({ + port: config.redis.port, + host: config.redis.host, + password: config.redis.password, + maxRetriesPerRequest: 10, + }); + this.logger.info('数据库连接池已创建') + } catch (error) { + this.logger.error('数据库连接池创建失败:' + error) + } + setTimeout(async () => { + if(this.pool == undefined) + return; + try { + let res = await this.pool.set('redis_test', '1'); + if (res) + this.logger.info('数据库测试成功') + else + throw new Error('返回值错误') + } catch (error) { + this.logger.error('数据库测试失败:' + error) + } + + }, 10); + } + + public getPool(): Redis { + return this.pool; + } +} + +const redisConnection = new RedisConnection(); +export default redisConnection.getPool(); \ No newline at end of file diff --git a/Server/src/Plugs/Service/captchaSession.ts b/Server/src/Plugs/Service/captchaSession.ts new file mode 100644 index 0000000..053813d --- /dev/null +++ b/Server/src/Plugs/Service/captchaSession.ts @@ -0,0 +1,114 @@ +import Logger from '../Logger'; +import RedisConnection from '../RedisConnection'; + +class _captchaSession { + private logger = new Logger('Service][captchaSession'); + private AllowMaxTryCount: number = 5; + private AllowMaxAngleDiff: number = 8; + private ExpriedTimeSec: number = 60; + private RedisCommonKey: string = 'Service:captchaSession:'; + + constructor() { + this.logger.info('旋转图像验证服务已启动'); + } + + private async get(session: string) { + try { + const result = await RedisConnection.get(this.RedisCommonKey + session); + if(result === null) + return; + return JSON.parse(result); + } catch (error) { + this.logger.error(`获取session[${session}]时发生错误:${error}`); + return; + } + } + + private async remove(session: string) { + try { + await RedisConnection.del(this.RedisCommonKey + session); + } catch (error) { + this.logger.error(`删除session[${session}]失败`); + } + } + + /** + * + * @param session 验证会话标识符 + * @param rotateDeg 图片旋转角度 + * @returns true存储成功 false存储失败 + */ + public async add(session: string, rotateDeg: number): Promise { + const result = { + rotateDeg: rotateDeg, + tryCount: 0, + isPassed: false + } + try { + const res = await RedisConnection.set(this.RedisCommonKey + session, JSON.stringify(result)); + if(res && res === 'OK') { + RedisConnection.expire(this.RedisCommonKey + session, this.ExpriedTimeSec); + this.logger.info(`session[${session}]及角度[${rotateDeg}]已存储`); + return true; + } + this.logger.error(`session[${session}]及角度[${rotateDeg}]存储失败`); + return false; + } catch (error) { + this.logger.error(`session[${session}]及角度[${rotateDeg}]存储失败:${error}`); + return false; + } + } + + /** + * + * @param session 验证会话标识符 + * @returns true已验证过期 false未通过 + */ + public async isPassed(session: string): Promise { + const result = await this.get(session); + if(!result) + return false; + if(result.isPassed) + this.remove(session); + return result.isPassed; + } + + /** + * + * @param session 验证会话标识符 + * @param rotateDeg 图片旋转角度 + * @returns 0验证已过期或服务器错误 1通过验证 -1超过最大允许尝试次数 -2角度差异过大 + */ + public async check(session: string, rotateDeg: number): Promise { + try { + let result = await this.get(session); + if(!result){ + return 0; + } + if(result.isPassed){ + this.logger.info(`session[${session}]已通过验证,无需重复验证`); + return 1; + } + if(Math.abs(result.rotateDeg - rotateDeg) <= this.AllowMaxAngleDiff) { + result.isPassed = true; + await RedisConnection.del(this.RedisCommonKey + session); + await RedisConnection.set(this.RedisCommonKey + session, JSON.stringify(result)); + RedisConnection.expire(this.RedisCommonKey + session, this.ExpriedTimeSec); + return 1; + } + result.tryCount++; + if(result.tryCount >= this.AllowMaxTryCount) { + this.remove(session); + return -1; + } + RedisConnection.set(this.RedisCommonKey + session, JSON.stringify(result)); + return -2; + } catch (error) { + this.logger.error(`检查session[${session}]时发生错误:${error}`); + return 0; + } + } +} + +const captchaSession = new _captchaSession(); +export default captchaSession; \ No newline at end of file diff --git a/Server/src/Server/Server.ts b/Server/src/Server/Server.ts index a8415b2..0777dec 100644 --- a/Server/src/Server/Server.ts +++ b/Server/src/Server/Server.ts @@ -2,11 +2,17 @@ import Logger from "../Plugs/Logger"; import { APILoader } from "../Plugs/API/APILoader"; import config from "../config"; +// 加载Plugs +import '../Plugs/Service/captchaSession' + +// 加载API import GetTest from "../APIs/GetTest"; import GetResourceList from "../APIs/GetResourceList"; import GetBlogList from "../APIs/GetBlogList"; import GetBlogContent from "../APIs/GetBlogContent"; import BlogLike from "../APIs/BlogLike"; +import GetCaptcha from "../APIs/GetCaptcha"; +import CheckCaptcha from "../APIs/CheckCaptcha"; class Server { private logger = new Logger('Server'); @@ -18,12 +24,14 @@ class Server { } public async start() { - // 加载前台API + // 加载API this.apiLoader.add(GetTest); this.apiLoader.add(GetResourceList); this.apiLoader.add(GetBlogList); this.apiLoader.add(GetBlogContent); this.apiLoader.add(BlogLike); + this.apiLoader.add(GetCaptcha); + this.apiLoader.add(CheckCaptcha); this.apiLoader.start(config.apiPort); } diff --git a/Server/src/ServerStdResponse.ts b/Server/src/ServerStdResponse.ts index c85e70d..2c787a3 100644 --- a/Server/src/ServerStdResponse.ts +++ b/Server/src/ServerStdResponse.ts @@ -29,6 +29,20 @@ const ServerStdResponse = { message: 'Blog not found' } }, + CAPTCHA: { + NOTFOUND: { + code: -5000, + message: 'captcha session not found' + }, + MAX_TRY_COUNT: { + code: -5001, + message: 'captcha session try max count' + }, + NOTRIGHT: { + code: -5002, + message: 'captcha is not right, please try again' + } + } } as const; export default ServerStdResponse; \ No newline at end of file diff --git a/Server/src/assets/captchaImgs/1.jpg b/Server/src/assets/captchaImgs/1.jpg new file mode 100644 index 0000000..ce688c7 Binary files /dev/null and b/Server/src/assets/captchaImgs/1.jpg differ diff --git a/Server/src/assets/captchaImgs/2.jpg b/Server/src/assets/captchaImgs/2.jpg new file mode 100644 index 0000000..a83748f Binary files /dev/null and b/Server/src/assets/captchaImgs/2.jpg differ diff --git a/Server/src/assets/captchaImgs/3.jpg b/Server/src/assets/captchaImgs/3.jpg new file mode 100644 index 0000000..344c824 Binary files /dev/null and b/Server/src/assets/captchaImgs/3.jpg differ diff --git a/Server/src/assets/captchaImgs/4.jpg b/Server/src/assets/captchaImgs/4.jpg new file mode 100644 index 0000000..6c056c6 Binary files /dev/null and b/Server/src/assets/captchaImgs/4.jpg differ diff --git a/Server/src/assets/captchaImgs/5.jpg b/Server/src/assets/captchaImgs/5.jpg new file mode 100644 index 0000000..42824de Binary files /dev/null and b/Server/src/assets/captchaImgs/5.jpg differ diff --git a/Server/src/assets/captchaImgs/6.jpg b/Server/src/assets/captchaImgs/6.jpg new file mode 100644 index 0000000..10b1269 Binary files /dev/null and b/Server/src/assets/captchaImgs/6.jpg differ