准备重构后端

This commit is contained in:
2024-08-29 13:02:27 +08:00
parent a0890ea8f5
commit 799a0cf43f
12 changed files with 0 additions and 505 deletions

View File

@@ -1,10 +0,0 @@
export default {
OK: {
code: 0,
msg: 'ok'
},
SERVER_ERROR: {
code: -1,
msg: '服务器错误'
}
}

View File

@@ -1,19 +0,0 @@
const express = require('express');
const router = express.Router();
import STANDARDRESPONSE from "../../STANDARDRESPONSE";
import Logger from "../../plugs/Logger";
import MySQLConnection from "../../plugs/database/MySQLConnection";
Logger.info('[API][/download/list] 加载成功')
router.get('/list', async (req: any, res: any) => {
let listRes = await MySQLConnection.execute('SELECT `id`,`recommand`,`title`,`describe`,`addition`,`icon_src` FROM `resource` WHERE `type` = "download"');
if(!listRes){
res.json(STANDARDRESPONSE.SERVER_ERROR)
return;
}
res.json({
...STANDARDRESPONSE.OK,
data: listRes
})
});
export default router;

View File

@@ -1,34 +0,0 @@
const express = require('express');
const router = express.Router();
const { Logger } = require('@plugs/Logger');
const { STANDARDRESPONSE } = require('@root/STANDARDRESPONSE')
const { RotationVerificationService } = require('@plugs/Service/RotationVerificationService');
Logger.info(`[API][/hmv/checkRotationVerification] 加载成功`);
// 验证旋转验证传入RVSession和用户旋转角度返回验证结果
router.post('/checkRotationVerification', async (req, res) => {
let { session, angle } = req.body;
// session为RVSession(32位UUID)angle为用户旋转角度
if(!session || !angle || session.length != 32){
Logger.warn(`[API][/hmv/checkRotationVerification]参数缺失或错误(session=${session}, angle=${angle})`);
res.json({...STANDARDRESPONSE.PARAMETER_MISSING});
return;
}
let result = await RotationVerificationService.check(session,angle);
switch(result){
case 0:
Logger.info(`[API][/hmv/checkRotationVerification] session[${session}]已过期,验证未通过`);
res.json({...STANDARDRESPONSE.ROTATION_VERIFICATION_EXPIRED, endpoint: result});
return;
case -1:
Logger.info(`[API][/hmv/checkRotationVerification] session[${session}]验证次数已达上限,验证未通过`);
res.json({...STANDARDRESPONSE.ROTATION_VERIFICATION_MAX_ATTEMPTS, endpoint: result});
return;
case -2:
Logger.info(`[API][/hmv/checkRotationVerification] session[${session}]验证角度差异过大,验证未通过`);
res.json({...STANDARDRESPONSE.ROTATION_VERIFICATION_ANGLE_DIFFERENCE, endpoint: result});
return;
}
Logger.info(`[API][/hmv/checkRotationVerification] session[${session}]验证通过`);
res.json({...STANDARDRESPONSE.OK});
})
module.exports = router;

View File

@@ -1,45 +0,0 @@
import Logger from "@plugs/Logger";
import STANDARDRESPONSE from "STANDARDRESPONSE";
const express = require('express');
const router = express.Router();
const sharp = require('sharp');
const fs = require('fs');
const path = require("path");
const { v4: uuidv4 } = require('uuid');
Logger.info(`[API][/hmv/rotationVerification] 加载成功`);
// 获取人机验证过程的图片返回一个RVSession用于后续的验证
router.get('/rotationVerification', async (req, res) => {
// 读取图像文件
try {
// 获取随机图片路径
const directoryPath = __dirname.slice(0, -8) + "/assets/RotationVerificationPicture/"
let files = fs.readdirSync(directoryPath);
files = files.filter(file => path.extname(file).toLowerCase() === '.jpg');
let filePath= directoryPath + files[Math.floor(Math.random() * files.length)]
Logger.info("[API][/hmv/rotationVerification] 获取人机验证图片:" + filePath)
// 生成session和角度信息
let session_uuid = uuidv4().replace(/-/g,'');// 32 个字符
const rotateDeg = 60 + Math.floor(Math.random() * 240 );// 限制角度范围 60 ~ 300
// 加入验证池
let RVAddRes = await RotationVerificationService.add(session_uuid, rotateDeg);
if(!RVAddRes){
Logger.err("[API][/hmv/rotationVerification] 添加验证信息到验证池中失败")
res.json({...STANDARDRESPONSE.SERVER_ERROR});
return;
}
// 图片旋转处理
const rotatedImage = await sharp(filePath)
.rotate(-rotateDeg)
.toBuffer();
// 转换为Base64编码
let imageBase64 = "data:image/jpg;base64," + rotatedImage.toString('base64');
Logger.info(`[API][/hmv/rotationVerification] session[${session_uuid}] 验证码图片已生成,角度[${rotateDeg}]`)
res.json({...STANDARDRESPONSE.OK,'data': imageBase64,'session': session_uuid})
} catch (error) {
Logger.err("[API][/hmv/rotationVerification] 获取人机验证图片错误:" + error)
res.json({...STANDARDRESPONSE.SERVER_ERROR})
}
})
module.exports = router;

View File

@@ -1,19 +0,0 @@
const express = require('express');
const router = express.Router();
import STANDARDRESPONSE from "../../STANDARDRESPONSE";
import Logger from "../../plugs/Logger";
import MySQLConnection from "../../plugs/database/MySQLConnection";
Logger.info('[API][/resource/list] 加载成功')
router.get('/list', async (req: any, res: any) => {
let listRes = await MySQLConnection.execute('SELECT `id`,`recommand`,`title`,`describe`,`addition`,`icon_src`,`src` FROM `resource` WHERE `type` = "resource"');
if(!listRes){
res.json(STANDARDRESPONSE.SERVER_ERROR)
return;
}
res.json({
...STANDARDRESPONSE.OK,
data: listRes
})
});
export default router;

View File

@@ -1,33 +0,0 @@
import Logger from "./plugs/Logger";
Logger.info('====== 服务器开始启动 ======');
require('./plugs/Settingconfig');
require('./plugs/database/MySQLConnection');
require('./plugs/database/RedisConnection');
const express = require('express');
const cors = require('cors')
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors({
origin: ['http://localhost:5173'],
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type','Authorization','Access-Control-Allow-Origin'],
credentials: true,
}))
app.use((req: any, res: any, next: Function) => {
Logger.info(`[API][${req.method}][${req.url}] 被请求`);
next();
});
// 注册接口
import resourceList from './apis/resource/list';
import downloadList from './apis/download/list';
app.use('/resource', resourceList);
app.use('/download', downloadList);
app.listen(8080, () => {
Logger.info('[Server] API服务已开放在http://localhost:8080');
});

View File

@@ -1,24 +0,0 @@
class _Logger{
constructor(){
}
formatTime(): string{
let date = new Date();
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
}
info(msg: string){
console.log(`[${this.formatTime()}][INFO] ${msg}`);
}
warn(msg: string){
console.log(`[${this.formatTime()}][WARN] ${msg}`);
}
error(msg: string){
console.log(`[${this.formatTime()}][ERROR] ${msg}`);
}
}
let Logger = new _Logger();
export default Logger;

View File

@@ -1,54 +0,0 @@
// 读取上级目录的Settingconfig.json文件
import * as fs from 'fs';
import * as path from 'path';
import Logger from './Logger';
interface MySqlConfig {
host: string;
user: string;
password: string;
database: string;
}
interface ServiceRotationVerificationType{
AllowMaxTryCount: number;
AllowMaxAngleDiff: number;
ExpriedTimeSec: number;
}
interface ServiceType {
rotationVerification: ServiceRotationVerificationType
}
interface SettingConfigType {
mysql: MySqlConfig;
service: ServiceType;
}
const SettingConfig: SettingConfigType = {
mysql:{
host: "server.tonesc.cn",
user: "root",
password: "245565",
database: "tonecn"
},
service: {
rotationVerification: {
AllowMaxTryCount: 5,// 最大允许尝试次数
AllowMaxAngleDiff: 10,// 允许的最大角度差异
ExpriedTimeSec: 60// 单次session过期时间,单位秒
}
}
};
// (async () => {
// const filePath = path.join(__dirname, '..', 'Settingconfig.json');
// let readRes = fs.readFileSync(filePath, 'utf-8');
// try {
// let config = JSON.parse(readRes);
// Object.assign(SettingConfig, config);
// } catch (error) {
// Logger.error(`配置文件出错 ${error}`)
// throw new Error('配置文件格式错误');
// }
// })()
export default SettingConfig

View File

@@ -1,69 +0,0 @@
// MYSQL数据库连接池
// 版本v0.1
const mysql = require('mysql2/promise');
import Logger from "../Logger";
import SettingConfig from "../Settingconfig";
class MySQLConnectPool{
pool: any;
constructor()
{
this.pool = this.createConnectPool();
Logger.info("[MySQL] 数据库连接池已创建")
setTimeout(async () => {
let res = await this.testConnection();
if(res)
Logger.info("[MySQL] 数据库测试成功")
else
Logger.error("[MySQL] 数据库测试失败")
}, 10);
}
// 内部函数,无需手动调用
createConnectPool(){
return mysql.createPool({
host: SettingConfig.mysql.host,
database: SettingConfig.mysql.database,
user: SettingConfig.mysql.user,
password: SettingConfig.mysql.password,
waitForConnections:true,
connectionLimit:10,
queueLimit:0
})
}
// 内部函数,无需手动调用
async testConnection(){
try {
let res = await this.execute("SELECT 1 + 1 As result");
if(res[0].result == 2)
return 1;
else
return 0;
} catch (error) {
Logger.error(`[MYSQL] 数据库测试发生了错误:`+error);
return 0;
}
}
// 执行SQL语句
async execute(sql: string,values = undefined){
let connection;
try {
connection = await this.pool.getConnection();
let [rows, fields] = await connection.execute(sql,values);
return rows;
} catch (error) {
Logger.error("[MYSQL] 数据库发生错误:"+error);
return undefined;
} finally{
if(connection)
connection.release();
}
}
}
let MySQLConnection = new MySQLConnectPool();
export default MySQLConnection;

View File

@@ -1,98 +0,0 @@
const Redis = require('ioredis');
import Logger from "../Logger";
class _RedisConnection{
pool: any;
constructor(){
this.pool = new Redis({
host: 'server.tonesc.cn',
password: '2Pj4Ss9al3mS1',
connectionPoolSize: 10,
});
Logger.info('[Redis] 数据库连接池已创建')
setTimeout(async () => {
let res = await this.set('redis_test', '1');
if(res)
Logger.info('[Redis] 数据库测试成功')
else
Logger.error('[Redis] 数据库测试失败')
}, 10);
}
/**
* 设置键值对
* @param {*} key
* @param {*} value
* @returns { Promise<Boolean> } 成功返回true失败返回false
*/
set(key: string, value: string){
return new Promise((resolve) => {
this.pool.set(key, value, (err: any, value: any) => {
if (err) {
Logger.error('[Redis] 设置键时发生错误:' + err);
resolve(false);
} else {
resolve(true);
}
})
})
}
/**
* 获取键值对成功返回值失败返回undefined
* @param {string} key
* @returns { Promise<String> } 成功返回值失败返回undefined
*/
get(key: string): Promise<string> {
return new Promise((resolve) => {
this.pool.get(key, (err: any, value: any) => {
if (err) {
Logger.error('[Redis] 获取键时发生错误:' + err);
throw new Error('Redis连接错误')
} else {
resolve(value);
}
})
})
}
/**
* 删除键值对成功返回true失败返回false
* @param {string} key
* @returns { Promise<Boolean> } 成功返回true失败返回false
*/
del(key: string){
return new Promise((resolve) => {
this.pool.del(key, (err: any, value: any) => {
if (err) {
Logger.error('[Redis] 删除键时发生错误:' + err);
resolve(false);
} else {
resolve(true);
}
})
})
}
/**
* 设置键的过期时间
* @param {*} key
* @param {*} seconds
* @returns { Promise<Boolean> } 成功返回true失败返回false
*/
expire(key: any, seconds: number){
return new Promise((resolve) => {
this.pool.expire(key, seconds, (err: number, value: number) => {
if (err) {
Logger.error('[Redis] 设置键过期时发生错误:' + err);
resolve(false);
} else {
resolve(true);
}
})
})
}
}
const RedisConnection = new _RedisConnection();
export default RedisConnection;

View File

@@ -1,100 +0,0 @@
import Logger from "@plugs/Logger";
import SettingConfig from "@plugs/Settingconfig";
import RedisConnection from "@plugs/database/RedisConnection";
class RotationVerificationService{
constructor(){
Logger.info('[Service][RotationVerificationService] 旋转图像验证服务已启动')
}
async #get(session: string){
// 通过session获取单个存储对象无需手动调用
try {
let result: = await RedisConnection.get('RotationVerificationService:' + session);
return JSON.parse(result);// 由于RVService的值为JSON字符串因此需要解析
} catch (error) {
// 不一定是数据库出错,有可能是已过期
Logger.warn(`[Service][RotationVerificationService] 获取session[${session}]时发生错误:${error}`);
return undefined;
}
}
async #remove(session: string){
// 删除session无需手动调用
let res = await RedisConnection.del('RotationVerificationService:' + session);
if(!res)
Logger.err(`[Service][RotationVerificationService] 删除session[${session}]失败`);
}
/**
* 将session和角度信息存储便于后续验证
* @param {*} session
* @param {*} rotateDeg
*/
async add(session: string,rotateDeg: number){
let result = {
rotateDeg: rotateDeg,
tryCount: 0,
isPassed: false
}
let res = await RedisConnection.set('RotationVerificationService:' + session, JSON.stringify(result));
if(!res)
{
Logger.err(`[Service][RotationVerificationService] 存储session[${session}]失败`);
return false;
}
// 设置过期时间
RedisConnection.expire('RotationVerificationService:' + session, this.ExpriedTimeSec);
return true;
}
/**
* 查询该session是否已通过验证使用后自动实效
* @param {*} session
* @returns {Boolean} 验证通过结果
*/
async isPassed(session){
// 通过后有效期60s
let result = await this.#get(session);
if(!result)
return false;
let res = result.isPassed;
if(res)
// 验证通过该session失效防止重复验证
this.#remove(session);
return res;
}
/**
* 检查session和角度信息
* @param {*} session
* @param {*} rotateDeg
* @returns {Number} 0,已过期(或未加入验证池) -1,超过最大尝试次数 1,验证通过 -2,角度差异过大
*/
async check(session,rotateDeg){
let result = await this.#get(session);
if(!result)
return 0;// 已过期(或未加入验证池)
if(Math.abs(result.rotateDeg - rotateDeg) <= this.AllowMaxAngleDiff)
{
// 验证通过标识为已通过并设定有效期为60s
result.isPassed = true;
await RedisConnection.del('RotationVerificationService:' + session);
await RedisConnection.set('RotationVerificationService:' + session, JSON.stringify(result));
RedisConnection.expire('RotationVerificationService:' + session, this.ExpriedTimeSec);
return 1;
}
result.tryCount++;// 浪费一次尝试次数
if(result.tryCount >= this.AllowMaxTryCount)
{
this.#remove(session);
return -1;// 超过最大尝试次数
}
// 保存尝试次数
RedisConnection.set('RotationVerificationService:' + session, JSON.stringify(result));
return -2;// 角度差异过大
}
}
let _RotationVerificationService = new RotationVerificationService();
module.exports.RotationVerificationService = _RotationVerificationService;