Browse Source

feat: 更新页面渲染和用户登录功能,添加样式支持

alpha
谢亚昕 2 months ago
parent
commit
fddb11d84f
  1. 19
      public/styles.css
  2. 2
      src/controllers/Page/PageController.js
  3. 2
      src/controllers/userController.js
  4. 4
      src/db/migrations/20250616065041_create_users_table.mjs
  5. 5
      src/db/models/UserModel.js
  6. 4
      src/db/seeds/20250616071157_users_seed.mjs
  7. 37
      src/middlewares/Auth/auth.js
  8. 23
      src/middlewares/ResponseTime/index.js
  9. 2
      src/middlewares/Views/index.js
  10. 3
      src/middlewares/install.js
  11. 13
      src/views/htmx/login.pug
  12. 2
      src/views/htmx/navbar.pug
  13. 38
      src/views/index.pug
  14. 9
      src/views/layouts/base.pug
  15. 11
      src/views/layouts/page.pug
  16. 8
      src/views/page/index/index.pug

19
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;
}

2
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 => {

2
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)

4
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()) // 更新时间

5
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) {

4
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" },
])
}

37
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()
}

23
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")
}

2
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 静态文件

3
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)

13
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

2
src/views/htmx/navbar.pug

@ -0,0 +1,2 @@
nav.navbar
.title 首页

38
src/views/index.pug

@ -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") 登录

9
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

11
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

8
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
Loading…
Cancel
Save