feat: 添加速率限制,单ip1分钟6次

This commit is contained in:
lyz05
2022-12-17 22:49:55 +08:00
parent 3ab4397c0f
commit 4ed7133a66
5 changed files with 126 additions and 76 deletions

23
app.js
View File

@@ -3,15 +3,17 @@ const express = require("express");
const path = require("path"); const path = require("path");
const cookieParser = require("cookie-parser"); const cookieParser = require("cookie-parser");
const logger = require("morgan"); const logger = require("morgan");
const rateLimit = require("express-rate-limit");
// 引入环境变量 // 引入环境变量
require("dotenv").config(); require("dotenv")
.config();
// 引入一个个路由模块 // 引入一个个路由模块
const danmakuRouter = require("./routes/danmaku"); const danmakuRouter = require("./routes/danmaku");
const ipinfoRouter = require("./routes/ipinfo"); const ipinfoRouter = require("./routes/ipinfo");
const airportsubRouter = require("./routes/airportsub"); const airportsubRouter = require("./routes/airportsub");
const DEBUG = process.env.DEBUG==="true" || false; const DEBUG = process.env.DEBUG === "true" || false;
const app = express(); const app = express();
@@ -22,14 +24,26 @@ app.set("trust proxy", true);
app.use(logger("dev")); app.use(logger("dev"));
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({extended: false})); app.use(express.urlencoded({ extended: false }));
app.use(cookieParser()); app.use(cookieParser());
// 加载静态资源
app.use(express.static(path.join(__dirname, "public"))); app.use(express.static(path.join(__dirname, "public")));
app.use("/assets", [ app.use("/assets", [
express.static(__dirname + "/node_modules/jquery/dist/"), express.static(__dirname + "/node_modules/jquery/dist/"),
express.static(__dirname + "/node_modules/bootstrap/dist/"), express.static(__dirname + "/node_modules/bootstrap/dist/"),
]); ]);
// Rate Limit
const apiLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 6, // limit each IP to 6 requests per windowMs
message: "Too many requests from this IP, please try again after an minute",
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
skipFailedRequests: true, // Don't count failed requests (status >= 400)
});
app.use(apiLimiter);
// 加载路由
app.use("/", danmakuRouter); app.use("/", danmakuRouter);
app.use("/ipinfo", ipinfoRouter); app.use("/ipinfo", ipinfoRouter);
app.use("/sub", airportsubRouter); app.use("/sub", airportsubRouter);
@@ -57,7 +71,8 @@ if (!DEBUG) {
console.log("PRODUCTION MODE!该模式下TG机器人正常运行"); console.log("PRODUCTION MODE!该模式下TG机器人正常运行");
// 引入TG机器人 // 引入TG机器人
require("./tgbot/tgbot"); require("./tgbot/tgbot");
} else } else {
console.log("DEBUG MODE!该模式下将关闭TG机器人"); console.log("DEBUG MODE!该模式下将关闭TG机器人");
}
module.exports = app; module.exports = app;

View File

@@ -22,7 +22,9 @@ const server = http.createServer(app);
* Listen on provided port, on all network interfaces. * Listen on provided port, on all network interfaces.
*/ */
server.listen(port); server.listen(port, () => {
console.log(`Listening on port ${port}`);
});
server.on('error', onError); server.on('error', onError);
server.on('listening', onListening); server.on('listening', onListening);

18
package-lock.json generated
View File

@@ -18,6 +18,7 @@
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"ejs": "^3.1.8", "ejs": "^3.1.8",
"express": "~4.18.2", "express": "~4.18.2",
"express-rate-limit": "^6.7.0",
"filesize": "^10.0.5", "filesize": "^10.0.5",
"got": "^11.8.2", "got": "^11.8.2",
"http-errors": "~1.6.3", "http-errors": "~1.6.3",
@@ -2959,6 +2960,17 @@
"node": ">= 0.10.0" "node": ">= 0.10.0"
} }
}, },
"node_modules/express-rate-limit": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz",
"integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==",
"engines": {
"node": ">= 12.9.0"
},
"peerDependencies": {
"express": "^4 || ^5"
}
},
"node_modules/express/node_modules/http-errors": { "node_modules/express/node_modules/http-errors": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@@ -10381,6 +10393,12 @@
} }
} }
}, },
"express-rate-limit": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz",
"integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==",
"requires": {}
},
"extend": { "extend": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",

View File

@@ -20,6 +20,7 @@
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"ejs": "^3.1.8", "ejs": "^3.1.8",
"express": "~4.18.2", "express": "~4.18.2",
"express-rate-limit": "^6.7.0",
"filesize": "^10.0.5", "filesize": "^10.0.5",
"got": "^11.8.2", "got": "^11.8.2",
"http-errors": "~1.6.3", "http-errors": "~1.6.3",

View File

@@ -1,106 +1,120 @@
const axios = require('axios'); const axios = require("axios");
const tgbot = require("../tgbot/tgbot.js"); const tgbot = require("../tgbot/tgbot.js");
const rootPath = 'https://eservice.ssm.gov.mo/covidvacbook/' const rootPath = "https://eservice.ssm.gov.mo/covidvacbook/";
const chatID = [619935997, 5646988443]; const chatID = [619935997, 5646988443];
const IDTYPES = ["J", "f", "M", "h", "O", "n"] const IDTYPES = ["J", "f", "M", "h", "O", "n"];
console.log('covidbook.js loaded') console.log("covidbook.js loaded");
function sendMessage(msg) { function sendMessage(msg) {
const bot = tgbot.hkaliyun; const bot = tgbot.hkaliyun;
chatID.forEach(id => { chatID.forEach(id => {
bot.sendMessage(id, msg); bot.sendMessage(id, msg);
}); });
} }
async function GetLocationQuotaList() { async function GetLocationQuotaList() {
const url = rootPath + 'Booking/GetLocationQuotaList'; const url = rootPath + "Booking/GetLocationQuotaList";
const res = await axios.post(url, {}); const res = await axios.post(url, {});
const data = res.data; const data = res.data;
const mrnalocationquotalist = data.filter(locationquota => (locationquota.rspsrv === 'HC6' || locationquota.rspsrv === 'HC8' || locationquota.rspsrv === 'CHCSJ(MRNA)' || locationquota.rspsrv === 'CHCSJ2(MRNA)' || locationquota.rspsrv === 'SSM1(MRNA)')); const mrnalocationquotalist = data.filter(locationquota => (locationquota.rspsrv === "HC6" || locationquota.rspsrv === "HC8" || locationquota.rspsrv === "CHCSJ(MRNA)" || locationquota.rspsrv === "CHCSJ2(MRNA)" || locationquota.rspsrv === "SSM1(MRNA)"));
const ivlocationquotalist = data.filter(locationquota => (locationquota.rspsrv === 'HC1' || locationquota.rspsrv === 'HC2' || locationquota.rspsrv === 'HC3' || locationquota.rspsrv === 'HC4' || locationquota.rspsrv === 'HC5' || locationquota.rspsrv === 'HC7' || locationquota.rspsrv === 'HC9' || locationquota.rspsrv === 'HC11' || locationquota.rspsrv === 'CHCSJ' || locationquota.rspsrv === 'CHCSJ2' || locationquota.rspsrv === 'KW1' || locationquota.rspsrv === 'SSM1' || locationquota.rspsrv === 'MUST1' || locationquota.rspsrv === 'FAOM1' || locationquota.rspsrv === 'FAOM2' || locationquota.rspsrv === 'SSM2')); const ivlocationquotalist = data.filter(locationquota => (locationquota.rspsrv === "HC1" || locationquota.rspsrv === "HC2" || locationquota.rspsrv === "HC3" || locationquota.rspsrv === "HC4" || locationquota.rspsrv === "HC5" || locationquota.rspsrv === "HC7" || locationquota.rspsrv === "HC9" || locationquota.rspsrv === "HC11" || locationquota.rspsrv === "CHCSJ" || locationquota.rspsrv === "CHCSJ2" || locationquota.rspsrv === "KW1" || locationquota.rspsrv === "SSM1" || locationquota.rspsrv === "MUST1" || locationquota.rspsrv === "FAOM1" || locationquota.rspsrv === "FAOM2" || locationquota.rspsrv === "SSM2"));
return { ivlocationquotalist, mrnalocationquotalist }; return {
ivlocationquotalist,
mrnalocationquotalist
};
} }
async function GetLocationPeriodByIdtype(idtype) { async function GetLocationPeriodByIdtype(idtype) {
const url = rootPath + 'Booking/GetLocationPeriodByIdtype'; const url = rootPath + "Booking/GetLocationPeriodByIdtype";
var data = { idtype: idtype }; var data = { idtype: idtype };
const res = await axios.post(url, data); const res = await axios.post(url, data);
return { periodlist: res.data } return { periodlist: res.data };
} }
async function getlocationbyidtype(idtype) { async function getlocationbyidtype(idtype) {
var data = { idtype: idtype }; var data = { idtype: idtype };
var url = rootPath + 'Booking/GetLocationByIdtype'; var url = rootPath + "Booking/GetLocationByIdtype";
const res = await axios.post(url, data); const res = await axios.post(url, data);
return { location: res.data } return { location: res.data };
} }
async function GetBookDate(idtype) { async function GetBookDate(idtype) {
var url = rootPath + 'Booking/GetBookDate'; var url = rootPath + "Booking/GetBookDate";
var checkquota = false; var checkquota = false;
let date = new Date() let date = new Date();
let year = date.getFullYear() let year = date.getFullYear();
let month = (date.getMonth() + 1).toString().padStart(2, '0') let month = (date.getMonth() + 1).toString()
let day = date.getDate().toString().padStart(2, '0') .padStart(2, "0");
var time2 = `${year}${month}${day}` let day = date.getDate()
.toString()
.padStart(2, "0");
var time2 = `${year}${month}${day}`;
var data = { idtype: idtype, afterdate: time2, checkquota: checkquota }; var data = {
const res = await axios.post(url, data); idtype: idtype,
return { bookdatelist: res.data } afterdate: time2,
checkquota: checkquota
};
const res = await axios.post(url, data);
return { bookdatelist: res.data };
} }
async function GetlocationList() { async function GetlocationList() {
var url = rootPath + 'Booking/GetlocationList'; var url = rootPath + "Booking/GetlocationList";
const res = await axios.post(url, {}); const res = await axios.post(url, {});
} }
async function main() { async function main() {
const { ivlocationquotalist, mrnalocationquotalist } = await GetLocationQuotaList(); const {
const quotalist = mrnalocationquotalist; ivlocationquotalist,
mrnalocationquotalist
} = await GetLocationQuotaList();
const quotalist = mrnalocationquotalist;
console.log('covidbook query') console.log("covidbook query");
//筛选出可预约的日期 //筛选出可预约的日期
for (const idtype of IDTYPES) { for (const idtype of IDTYPES) {
const { bookdatelist } = await GetBookDate(idtype); const { bookdatelist } = await GetBookDate(idtype);
const { location } = await getlocationbyidtype(idtype) const { location } = await getlocationbyidtype(idtype);
const name = location[0].name_c; const name = location[0].name_c;
if (bookdatelist.length != 0) { if (bookdatelist.length != 0) {
sendMessage(name + "\n" + bookdatelist.join('\n')); sendMessage(name + "\n" + bookdatelist.join("\n"));
} }
} }
// // 筛选出当日有余量的接种站 // // 筛选出当日有余量的接种站
// const quotalistfilter = quotalist.filter(x => x.sum != '0') // const quotalistfilter = quotalist.filter(x => x.sum != '0')
// if (quotalistfilter.length != 0) { // if (quotalistfilter.length != 0) {
// console.log('有余量'); // console.log('有余量');
// const time2 = new Date().toLocaleTimeString(); // const time2 = new Date().toLocaleTimeString();
// bot.sendMessage(chatID, `当前时间:${time2},以下是有余量的接种站:`); // bot.sendMessage(chatID, `当前时间:${time2},以下是有余量的接种站:`);
// for (const l of quotalistfilter) { // for (const l of quotalistfilter) {
// bot.sendMessage(chatID, `${l.name_c} : ${l.sum}`); // bot.sendMessage(chatID, `${l.name_c} : ${l.sum}`);
// } // }
// } else console.log('无余量'); // } else console.log('无余量');
// 遍历每个接种站尚有余额之时段 // 遍历每个接种站尚有余额之时段
// for (const item of quotalist) { // for (const item of quotalist) {
// const { location } = await getlocationbyidtype(item.idtype) // const { location } = await getlocationbyidtype(item.idtype)
// const { periodlist } = await GetLocationPeriodByIdtype(item.idtype) // const { periodlist } = await GetLocationPeriodByIdtype(item.idtype)
// if (periodlist.length != 0) { // if (periodlist.length != 0) {
// const name = location[0].name_c; // const name = location[0].name_c;
// const periodlisttext = periodlist.map(l => `${l.booktime} 餘額 : ${l.ava_quota}`) // const periodlisttext = periodlist.map(l => `${l.booktime} 餘額 : ${l.ava_quota}`)
// bot.sendMessage(chatID, name + '\n' + periodlisttext.join('\n')); // bot.sendMessage(chatID, name + '\n' + periodlisttext.join('\n'));
// } // }
// } // }
} }
module.exports = main; module.exports = main;
if (!module.parent) { if (!module.parent) {
// 引入环境变量 // 引入环境变量
require("dotenv").config('../.env'); require("dotenv")
main() .config("../.env");
sendMessage('测试') main();
} sendMessage("测试");
}