diff --git a/bun.lockb b/bun.lockb index cc49194..f49cad5 100644 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 2e71fbb..ad1e94c 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "path-to-regexp": "^8.2.0", "pug": "^3.0.3", "sqlite3": "^5.1.7", + "svg-captcha": "^1.4.0", "vite-plugin-static-copy": "^3.1.0" }, "_moduleAliases": { diff --git a/src/controllers/Page/PageController.js b/src/controllers/Page/PageController.js index 28e41b5..99c00ad 100644 --- a/src/controllers/Page/PageController.js +++ b/src/controllers/Page/PageController.js @@ -1,6 +1,8 @@ import Router from "utils/router.js" import UserService from "services/UserService.js" import SiteConfigService from "services/SiteConfigService.js" +import svgCaptcha from "svg-captcha" +import CommonError from "@/utils/error/CommonError" class PageController { constructor() { @@ -33,6 +35,26 @@ class PageController { ctx.body = { success: true, message: "登录成功" } } + async captchaGet(ctx) { + var captcha = svgCaptcha.create({ + size: 4, // 个数 + width: 100, // 宽 + height: 30, // 高 + fontSize: 38, // 字体大小 + color: true, // 字体颜色是否多变 + noise: 2, // 干扰线几条 + }) + // 记录验证码信息(文本+过期时间) + // 这里设置5分钟后过期 + const expireTime = Date.now() + 5 * 60 * 1000 + ctx.session.captcha = { + text: captcha.text.toLowerCase(), // 转小写,忽略大小写验证 + expireTime: expireTime, + } + ctx.type = "image/svg+xml" + ctx.body = captcha.data + } + async registerGet(ctx) { if (ctx.state.user) { ctx.cookies.set("toast", JSON.stringify({ type: "error", message: encodeURIComponent("用户已登录") }), { @@ -42,15 +64,48 @@ class PageController { }) return ctx.redirect("/?msg=用户已登录") } - return await ctx.render("page/register/index", { site_title: "注册" }) + // TODO 多个 + ctx.session.registerRandomStr = Math.ceil(Math.random() * 100000000000000) + return await ctx.render("page/register/index", { site_title: "注册", randomStr: ctx.session.registerRandomStr }) } async registerPost(ctx) { - const { username, password } = ctx.request.body + const { username, password, code, randomStr } = ctx.request.body + + if (!ctx.session.registerRandomStr) { + throw new CommonError("缺少随机数") + } + if (ctx.session.registerRandomStr + "" !== randomStr + "") { + throw new CommonError("随机数不匹配") + } + delete ctx.session.registerRandomStr + + // 检查Session中是否存在验证码 + if (!ctx.session.captcha) { + throw new CommonError("验证码不存在,请重新获取") + } + + const { text, expireTime } = ctx.session.captcha + + // 检查是否过期 + if (Date.now() > expireTime) { + // 过期后清除Session中的验证码 + delete ctx.session.captcha + throw new CommonError("验证码已过期,请重新获取") + } + + if (!code) { + throw new CommonError("请输入验证码") + } + + if (code.toLowerCase() !== text) { + throw new CommonError("验证码错误") + } + + delete ctx.session.captcha // try { await this.userService.register({ username, password, role: "user" }) - // ctx.cookies.set("toast", JSON.stringify({ type: "success", message: "注册成功" }), { // maxAge: 1, // httpOnly: false, @@ -97,6 +152,7 @@ class PageController { router.get("/about", controller.pageGet("page/about/index"), { auth: false }) router.get("/login", controller.loginGet.bind(controller), { auth: "try" }) router.post("/login", controller.loginPost.bind(controller), { auth: false }) + router.get("/captcha", controller.captchaGet.bind(controller), { auth: false }) router.get("/register", controller.registerGet.bind(controller), { auth: "try" }) router.post("/register", controller.registerPost.bind(controller), { auth: false }) router.post("/logout", controller.logout.bind(controller), { auth: true }) diff --git a/src/jobs/exampleJob.js b/src/jobs/exampleJob.js index c79fc8b..4e0387c 100644 --- a/src/jobs/exampleJob.js +++ b/src/jobs/exampleJob.js @@ -1,11 +1,11 @@ -import { jobLogger } from "@/logger"; +import { jobLogger } from "@/logger" export default { - id: 'example', - cronTime: '*/10 * * * * *', // 每10秒执行一次 + id: "example", + cronTime: "*/10 * * * * *", // 每10秒执行一次 task: () => { - jobLogger.info('Example Job 执行了'); + jobLogger.info("Example Job 执行了") }, options: {}, - autoStart: false -}; + autoStart: false, +} diff --git a/src/logger.js b/src/logger.js index 2312d90..8e5285e 100644 --- a/src/logger.js +++ b/src/logger.js @@ -2,18 +2,18 @@ import log4js from "log4js" log4js.configure({ appenders: { - debug: { - type: "file", - filename: "logs/debug.log", - maxLogSize: 102400, - pattern: "-yyyy-MM-dd.log", - alwaysIncludePattern: true, - backups: 3, - layout: { - type: 'pattern', - pattern: '[%d{yyyy-MM-dd hh:mm:ss}] [%p] %m', - }, - }, + // debug: { + // type: "file", + // filename: "logs/debug.log", + // maxLogSize: 102400, + // pattern: "-yyyy-MM-dd.log", + // alwaysIncludePattern: true, + // backups: 3, + // layout: { + // type: 'pattern', + // pattern: '[%d{yyyy-MM-dd hh:mm:ss}] [%p] %m', + // }, + // }, all: { type: "file", filename: "logs/all.log", @@ -26,18 +26,18 @@ log4js.configure({ pattern: '[%d{yyyy-MM-dd hh:mm:ss}] [%p] %m', }, }, - error: { - type: "file", - filename: "logs/error.log", - maxLogSize: 102400, - pattern: "-yyyy-MM-dd.log", - alwaysIncludePattern: true, - backups: 3, - layout: { - type: 'pattern', - pattern: '[%d{yyyy-MM-dd hh:mm:ss}] [%p] %m', - }, - }, + // error: { + // type: "file", + // filename: "logs/error.log", + // maxLogSize: 102400, + // pattern: "-yyyy-MM-dd.log", + // alwaysIncludePattern: true, + // backups: 3, + // layout: { + // type: 'pattern', + // pattern: '[%d{yyyy-MM-dd hh:mm:ss}] [%p] %m', + // }, + // }, jobs: { type: "file", filename: "logs/jobs.log", @@ -50,18 +50,18 @@ log4js.configure({ pattern: '[%d{yyyy-MM-dd hh:mm:ss}] [%p] %m', }, }, - site: { - type: "file", - filename: "logs/site.log", - maxLogSize: 102400, - pattern: "-yyyy-MM-dd.log", - alwaysIncludePattern: true, - backups: 3, - layout: { - type: 'pattern', - pattern: '[%d{yyyy-MM-dd hh:mm:ss}] [%p] %m', - }, - }, + // site: { + // type: "file", + // filename: "logs/site.log", + // maxLogSize: 102400, + // pattern: "-yyyy-MM-dd.log", + // alwaysIncludePattern: true, + // backups: 3, + // layout: { + // type: 'pattern', + // pattern: '[%d{yyyy-MM-dd hh:mm:ss}] [%p] %m', + // }, + // }, console: { type: "console", layout: { @@ -72,18 +72,18 @@ log4js.configure({ }, categories: { jobs: { appenders: ["console", "jobs"], level: "ALL" }, - site: { appenders: ["site"], level: "ALL" }, - console: { appenders: ["console"], level: "ALL" }, - error: { appenders: ["console", "error"], level: "error" }, + // site: { appenders: ["site"], level: "ALL" }, + // console: { appenders: ["console"], level: "ALL" }, + // error: { appenders: ["console", "error"], level: "error" }, default: { appenders: ["console", "all"], level: "ALL" }, - debug: { appenders: ["debug"], level: "debug" }, + // debug: { appenders: ["debug"], level: "debug" }, }, }) // 导出常用logger实例,便于直接引用 export const logger = log4js.getLogger(); -export const debugLogger = log4js.getLogger('debug'); +// export const debugLogger = log4js.getLogger('debug'); export const jobLogger = log4js.getLogger('jobs'); -export const errorLogger = log4js.getLogger('error'); -export const siteLogger = log4js.getLogger('site'); -export const consoleLogger = log4js.getLogger('console'); +// export const errorLogger = log4js.getLogger('error'); +// export const siteLogger = log4js.getLogger('site'); +// export const consoleLogger = log4js.getLogger('console'); diff --git a/src/main.js b/src/main.js index 7a1d876..560618d 100644 --- a/src/main.js +++ b/src/main.js @@ -1,5 +1,5 @@ // 日志、全局插件、定时任务等基础设施 -import { consoleLogger } from "./logger.js" +import { logger } from "./logger.js" import "./jobs/index.js" // 第三方依赖 @@ -31,10 +31,10 @@ const server = app.listen(PORT, () => { return "localhost" } const localIP = getLocalIP() - consoleLogger.trace(`===================【服务器地址】====================`) - consoleLogger.trace(` http://localhost:${port} (本地地址) `) - consoleLogger.trace(` http://${localIP}:${port} (本地地址) `) - consoleLogger.trace(`===================【服务器地址】====================`) + logger.trace(`===================【服务器地址】====================`) + logger.trace(` http://localhost:${port} (本地地址) `) + logger.trace(` http://${localIP}:${port} (本地地址) `) + logger.trace(`===================【服务器地址】====================`) }) export default app diff --git a/src/middlewares/ResponseTime/index.js b/src/middlewares/ResponseTime/index.js index 6712512..8312814 100644 --- a/src/middlewares/ResponseTime/index.js +++ b/src/middlewares/ResponseTime/index.js @@ -1,4 +1,4 @@ -import { siteLogger, logger } from "@/logger" +import { logger } from "@/logger" // 静态资源扩展名列表 const staticExts = [".css", ".js", ".png", ".jpg", ".jpeg", ".gif", ".ico", ".svg", ".map", ".woff", ".woff2", ".ttf", ".eot"] @@ -23,7 +23,7 @@ export default async (ctx, next) => { const ms = Date.now() - start ctx.set("X-Response-Time", `${ms}ms`) if (ms > 500) { - siteLogger.info(`${ctx.path} | ⏱️ ${ms}ms`) + logger.info(`${ctx.path} | ⏱️ ${ms}ms`) } return } diff --git a/src/middlewares/Toast/index.js b/src/middlewares/Toast/index.js new file mode 100644 index 0000000..ad7a05c --- /dev/null +++ b/src/middlewares/Toast/index.js @@ -0,0 +1,14 @@ +export default function ToastMiddlewares() { + return function toast(ctx, next) { + if (ctx.toast) return next() + // error success info + ctx.toast = function (type, message) { + ctx.cookies.set("toast", JSON.stringify({ type: type, message: encodeURIComponent(message) }), { + maxAge: 1, + httpOnly: false, + path: "/", + }) + } + return next() + } +} diff --git a/src/middlewares/errorHandler/index.js b/src/middlewares/errorHandler/index.js index 73de3d5..328f79c 100644 --- a/src/middlewares/errorHandler/index.js +++ b/src/middlewares/errorHandler/index.js @@ -1,3 +1,4 @@ +import { logger } from "@/logger" import CommonError from "utils/error/CommonError" // src/plugins/errorHandler.js // 错误处理中间件插件 @@ -32,7 +33,7 @@ export default function errorHandler() { await formatError(ctx, 404, "Resource not found") } } catch (err) { - console.error(err); + logger.error(err) const isDev = process.env.NODE_ENV === "development" if (isDev && err.stack) { console.error(err.stack) @@ -43,7 +44,7 @@ export default function errorHandler() { httpOnly: false, path: "/", }) - ctx.redirect(ctx.path) + ctx.redirect(ctx.path+"?msg="+err.message) return } await formatError(ctx, err.statusCode || 500, err.message || err || "Internal server error", isDev ? err.stack : undefined) diff --git a/src/middlewares/install.js b/src/middlewares/install.js index d54cede..99366d2 100644 --- a/src/middlewares/install.js +++ b/src/middlewares/install.js @@ -8,12 +8,14 @@ import { auth } from "./Auth" import bodyParser from "koa-bodyparser" import Views from "./Views" import Session from "./Session" +import Toast from "./Toast" import { autoRegisterControllers } from "@/utils/ForRegister.js" const __dirname = path.dirname(fileURLToPath(import.meta.url)) const publicPath = resolve(__dirname, "../../public") export default app => { + app.use(Toast()) app.use(ErrorHandler()) app.use(ResponseTime) app.use(Session(app)); diff --git a/src/views/page/register/index.pug b/src/views/page/register/index.pug index 79a5902..34871f5 100644 --- a/src/views/page/register/index.pug +++ b/src/views/page/register/index.pug @@ -77,6 +77,7 @@ block pageContent .register-container .register-title 注册账号 form(action="/register" method="post") + input(type="text" name="randomStr" value=randomStr style="display:none") .form-group label(for="username") 用户名 input(type="text" id="username" name="username" required placeholder="请输入用户名") @@ -86,5 +87,9 @@ block pageContent .form-group label(for="confirm_password") 确认密码 input(type="password" id="confirm_password" name="confirm_password" required placeholder="请再次输入密码") + img(src="/captcha", alt="") + .form-group + label(for="code") 验证码 + input(type="text" id="code" name="code" required placeholder="请输入验证码") button.register-btn(type="submit") 注册 a.login-link(href="/login") 已有账号?去登录 \ No newline at end of file