From fddb11d84f96c22bd5ec1964cba296bcff1e2ed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=A2=E4=BA=9A=E6=98=95?= <1549469775@qq.com> Date: Thu, 19 Jun 2025 15:23:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=E5=92=8C=E7=94=A8=E6=88=B7=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/styles.css | 19 +++++++++++ src/controllers/Page/PageController.js | 2 +- src/controllers/userController.js | 2 +- .../20250616065041_create_users_table.mjs | 4 ++- src/db/models/UserModel.js | 5 ++- src/db/seeds/20250616071157_users_seed.mjs | 4 +-- src/middlewares/Auth/auth.js | 37 ++++++++++++++------- src/middlewares/ResponseTime/index.js | 23 ++++++------- src/middlewares/Views/index.js | 2 +- src/middlewares/install.js | 3 ++ src/views/htmx/login.pug | 13 ++++++++ src/views/htmx/navbar.pug | 2 ++ src/views/index.pug | 38 ---------------------- src/views/layouts/base.pug | 9 +++-- src/views/layouts/page.pug | 11 +++---- src/views/page/index/index.pug | 8 +++++ 16 files changed, 104 insertions(+), 78 deletions(-) create mode 100644 public/styles.css create mode 100644 src/views/htmx/login.pug create mode 100644 src/views/htmx/navbar.pug delete mode 100644 src/views/index.pug create mode 100644 src/views/page/index/index.pug diff --git a/public/styles.css b/public/styles.css new file mode 100644 index 0000000..2e9b915 --- /dev/null +++ b/public/styles.css @@ -0,0 +1,19 @@ +html, +body { + margin: 0; + padding: 0; + height: 100%; +} + +.navbar { + height: 49px; + display: flex; + align-items: center; + box-shadow: 1px 1px 3px #e4e4e4; +} + +.title{ + font-size: 1.5em; + margin-left: 10px; + color: #333; +} \ No newline at end of file diff --git a/src/controllers/Page/PageController.js b/src/controllers/Page/PageController.js index 733d9b4..b461917 100644 --- a/src/controllers/Page/PageController.js +++ b/src/controllers/Page/PageController.js @@ -1,5 +1,5 @@ export const Index = async ctx => { - return await ctx.render("index", { name: "bluescurry" }) + return await ctx.render("page/index/index", { title: "沧源一场" }) } export const Page = (name, data) => async ctx => { diff --git a/src/controllers/userController.js b/src/controllers/userController.js index 913f79a..9a9a537 100644 --- a/src/controllers/userController.js +++ b/src/controllers/userController.js @@ -33,7 +33,7 @@ export const login = async (ctx) => { // This enables browsers to automatically include the cookie in requests sameSite: "lax", secure: process.env.NODE_ENV === "production", // Use secure cookies in production - // maxAge: 86400000, // Optional: cookie expiration in milliseconds (e.g., 24 hours) + maxAge: 2 * 60 * 60 * 1000, // 2 hours }) } ctx.body = formatResponse(true, result) diff --git a/src/db/migrations/20250616065041_create_users_table.mjs b/src/db/migrations/20250616065041_create_users_table.mjs index f73fcae..0e401b2 100644 --- a/src/db/migrations/20250616065041_create_users_table.mjs +++ b/src/db/migrations/20250616065041_create_users_table.mjs @@ -7,7 +7,9 @@ export const up = async knex => { table.increments("id").primary() // 自增主键 table.string("username", 100).notNullable() // 字符串字段(最大长度100) table.string("email", 100).unique().notNullable() // 唯一邮箱 - table.string("password", 100).unique() // 密码 + table.string("password", 100).notNullable() // 密码 + table.string("role", 100).notNullable() + table.string("phone", 100) table.integer("age").unsigned() // 无符号整数 table.timestamp("created_at").defaultTo(knex.fn.now()) // 创建时间 table.timestamp("updated_at").defaultTo(knex.fn.now()) // 更新时间 diff --git a/src/db/models/UserModel.js b/src/db/models/UserModel.js index 19de0ac..bf9fc03 100644 --- a/src/db/models/UserModel.js +++ b/src/db/models/UserModel.js @@ -10,7 +10,10 @@ class UserModel { } static async create(data) { - return db("users").insert(data).returning("*") + return db("users").insert({ + ...data, + updated_at: db.fn.now(), + }).returning("*") } static async update(id, data) { diff --git a/src/db/seeds/20250616071157_users_seed.mjs b/src/db/seeds/20250616071157_users_seed.mjs index 9cb6728..aefb791 100644 --- a/src/db/seeds/20250616071157_users_seed.mjs +++ b/src/db/seeds/20250616071157_users_seed.mjs @@ -13,7 +13,7 @@ export const seed = async knex => { // Inserts seed entries await knex("users").insert([ - { username: "Alice", email: "alice@example.com" }, - { username: "Bob", email: "bob@example.com" }, + // { username: "Alice", email: "alice@example.com" }, + // { username: "Bob", email: "bob@example.com" }, ]) } diff --git a/src/middlewares/Auth/auth.js b/src/middlewares/Auth/auth.js index ea58c1f..76362ce 100644 --- a/src/middlewares/Auth/auth.js +++ b/src/middlewares/Auth/auth.js @@ -24,17 +24,13 @@ function verifyToken(ctx) { token = ctx.cookies.get("authorization") } if (!token) { - logger.trace("[user:anonymous] Operation"); - return { ok: false } + return { ok: false, status: -1 } } try { ctx.state.user = jwt.verify(token, JWT_SECRET) - const user = ctx.state.user - logger.trace(`[user:${user.username || user.id}] Operation`); return { ok: true } } catch { ctx.state.user = undefined - logger.trace("[user:anonymous] Operation"); return { ok: false } } } @@ -57,22 +53,39 @@ export default function authMiddleware(options = { return await next() } if (white.auth === "try") { - verifyToken(ctx) // token可选,校验失败不报错 + const data = verifyToken(ctx) + if (!data.ok && data.status !== -1) { + ctx.cookies.set("authorization", null, { httpOnly: true }); + if (ctx.accepts('html')) { + ctx.redirect(ctx.path+ '?redirectType=1'); + return; + } + } return await next() } // true 或其他情况,必须有token if (!verifyToken(ctx).ok) { - ctx.status = 401 - ctx.body = { success: false, error: "未登录或token缺失或无效" } - return + if (ctx.accepts('html')) { + ctx.cookies.set("authorization", null, { httpOnly: true }); + return ctx.redirect('/login?redirectType=1'); + } else { + ctx.status = 401 + ctx.body = { success: false, error: "未登录或token缺失或无效" } + return + } } return await next() } // 非白名单,必须有token if (!verifyToken(ctx).ok) { - ctx.status = 401 - ctx.body = { success: false, error: "未登录或token缺失或无效" } - return + if (ctx.accepts('html')) { + ctx.cookies.set("authorization", null, { httpOnly: true }); + return ctx.redirect('/login?redirectType=1'); + } else { + ctx.status = 401 + ctx.body = { success: false, error: "未登录或token缺失或无效" } + return + } } await next() } diff --git a/src/middlewares/ResponseTime/index.js b/src/middlewares/ResponseTime/index.js index 2f01a63..cb5851b 100644 --- a/src/middlewares/ResponseTime/index.js +++ b/src/middlewares/ResponseTime/index.js @@ -1,6 +1,4 @@ -import log4js from "log4js"; - -const logger = log4js.getLogger(); +import { logger } from "@/logger" /** * 响应时间记录中间件 @@ -8,12 +6,15 @@ const logger = log4js.getLogger(); * @param {Function} next - Koa中间件链函数 */ export default async (ctx, next) => { - logger.info("====================[REQ]===================="); - logger.info(`➡️ ${ctx.method} ${ctx.path}`); - const start = Date.now(); - await next(); - const ms = Date.now() - start; - ctx.set("X-Response-Time", `${ms}ms`); - logger.info(`⬅️ ${ctx.method} ${ctx.url} | ⏱️ ${ms}ms`); - logger.info("====================[END]====================\n"); + if(!ctx.path.includes('/api')) { + return await next() + } + logger.info("====================[REQ]====================") + logger.info(`➡️ ${ctx.method} ${ctx.path}`) + const start = Date.now() + await next() + const ms = Date.now() - start + ctx.set("X-Response-Time", `${ms}ms`) + logger.info(`⬅️ ${ctx.method} ${ctx.url} | ⏱️ ${ms}ms`) + logger.info("====================[END]====================\n") } diff --git a/src/middlewares/Views/index.js b/src/middlewares/Views/index.js index 709eccf..0436b0b 100644 --- a/src/middlewares/Views/index.js +++ b/src/middlewares/Views/index.js @@ -18,7 +18,7 @@ function viewsMiddleware(path, { engineSource = consolidate, extension = "html", const state = Object.assign(locals, options, ctx.state || {}) // deep copy partials state.partials = Object.assign({}, options.partials || {}) - logger.debug("render `%s` with %j", paths.rel, state) + // logger.debug("render `%s` with %j", paths.rel, state) ctx.type = "text/html" // 如果是 html 文件,不编译直接 send 静态文件 diff --git a/src/middlewares/install.js b/src/middlewares/install.js index 5027bfa..da1a44b 100644 --- a/src/middlewares/install.js +++ b/src/middlewares/install.js @@ -34,6 +34,9 @@ export default app => { app.use( Views(resolve(__dirname, "../views"), { extension: "pug", + options: { + basedir: resolve(__dirname, "../views"), + } }) ) autoRegisterControllers(app) diff --git a/src/views/htmx/login.pug b/src/views/htmx/login.pug new file mode 100644 index 0000000..510ec17 --- /dev/null +++ b/src/views/htmx/login.pug @@ -0,0 +1,13 @@ +if edit + .row.justify-content-center.mt-5 + .col-md-6 + form#loginForm(method="post" action="/api/login" hx-post="/api/login" hx-trigger="submit" hx-target="body" hx-swap="none" hx-on:htmx:afterRequest="if(event.detail.xhr.status===200){window.location='/';}") + .mb-3 + label.form-label(for="username") 用户名 + input.form-control(type="text" id="username" name="username" required) + .mb-3 + label.form-label(for="password") 密码 + input.form-control(type="password" id="password" name="password" required) + button.btn.btn-primary(type="submit") 登录 +else + div sad 404 \ No newline at end of file diff --git a/src/views/htmx/navbar.pug b/src/views/htmx/navbar.pug new file mode 100644 index 0000000..5e83f76 --- /dev/null +++ b/src/views/htmx/navbar.pug @@ -0,0 +1,2 @@ +nav.navbar + .title 首页 \ No newline at end of file diff --git a/src/views/index.pug b/src/views/index.pug deleted file mode 100644 index b83b665..0000000 --- a/src/views/index.pug +++ /dev/null @@ -1,38 +0,0 @@ -extends ./layouts/page.pug - - -block pageRoot - - var title = '示例页面标题' - -block pageContent - .container.mt-5 - .row.justify-content-center - .col-md-8.text-center - img.rounded-circle.shadow.mb-4(src='https://avatars.githubusercontent.com/u/9919?s=200&v=4', alt='Avatar', width='120', height='120') - h1.mt-3.mb-1 你的姓名 - h4.text-muted 你的职位 / 头衔 - p.lead.mt-3 这里是一段简短的自我介绍,突出你的专业技能、兴趣或座右铭。 - button(hx-post="/clicked" hx-swap="outerHTML") Click Me - hr.my-4 - .d-flex.justify-content-center.gap-4 - a(href='mailto:your@email.com', target='_blank') - i.fas.fa-envelope.me-2 - | 邮箱 - a(href='https://github.com/your-github', target='_blank') - i.fab.fa-github.me-2 - | GitHub - a(href='https://your-website.com', target='_blank') - i.fas.fa-globe.me-2 - | 个人网站 - - // 登录表单区域 - .row.justify-content-center.mt-5 - .col-md-6 - form#loginForm(method="post" action="/api/login" hx-post="/api/login" hx-trigger="submit" hx-target="body" hx-swap="none" hx-on:htmx:afterRequest="if(event.detail.xhr.status===200){window.location='/';}") - .mb-3 - label.form-label(for="username") 用户名 - input.form-control(type="text" id="username" name="username" required) - .mb-3 - label.form-label(for="password") 密码 - input.form-control(type="password" id="password" name="password" required) - button.btn.btn-primary(type="submit") 登录 diff --git a/src/views/layouts/base.pug b/src/views/layouts/base.pug index 1471c2b..79b3d1f 100644 --- a/src/views/layouts/base.pug +++ b/src/views/layouts/base.pug @@ -1,12 +1,15 @@ -block root +mixin include() + if block + block + doctype html html(lang="zh-CN") head - title #{title || '默认标题'} + block head + title #{title || ''} meta(charset="utf-8") meta(name="viewport" content="width=device-width, initial-scale=1") script(src="https://unpkg.com/htmx.org@2.0.4") - block head body block content block scripts diff --git a/src/views/layouts/page.pug b/src/views/layouts/page.pug index dfeedeb..f9fd7f1 100644 --- a/src/views/layouts/page.pug +++ b/src/views/layouts/page.pug @@ -1,16 +1,13 @@ -extends ./base.pug - -block root - block pageRoot +extends /layouts/base.pug block head - link(href='https://cdn.bootcss.com/twitter-bootstrap/4.1.3/css/bootstrap.min.css' rel='stylesheet') - script(src='https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js') - script(src='https://cdn.bootcss.com/twitter-bootstrap/4.1.3/js/bootstrap.bundle.min.js') + link(rel='stylesheet', href='styles.css') block pageHead block content + +include() + include /htmx/navbar.pug block pageContent block scripts diff --git a/src/views/page/index/index.pug b/src/views/page/index/index.pug new file mode 100644 index 0000000..30fe7d6 --- /dev/null +++ b/src/views/page/index/index.pug @@ -0,0 +1,8 @@ +extends /layouts/page.pug + +//- +include() +//- - var edit = false +//- include /htmx/login.pug + +block pageContent + div sad