Browse Source
- Updated the users table migration to include a password field. - Enhanced UserModel with methods to find users by username and email. - Implemented user registration and login functionalities in UserService. - Added JWT authentication middleware with support for whitelisting and blacklisting routes. - Created a response time middleware for logging request durations. - Replaced the previous Send and ResponseTime plugins with new middleware implementations. - Introduced bcrypt utility functions for password hashing and comparison. - Added a base singleton utility class for managing single instances of classes. - Created a helper function for formatting API responses. - Set up a Dockerfile for containerizing the application with health checks and entrypoint script. - Added a basic HTML login and registration interface.alpha
35 changed files with 821 additions and 459 deletions
@ -0,0 +1,29 @@ |
|||
# 使用官方 Bun 运行时的轻量级镜像 |
|||
FROM oven/bun:alpine as base |
|||
|
|||
WORKDIR /app |
|||
|
|||
# 仅复制生产依赖相关文件 |
|||
COPY package.json bun.lockb knexfile.mjs .npmrc ./ |
|||
|
|||
# 安装依赖(生产环境) |
|||
RUN bun install --production |
|||
|
|||
# 复制应用代码和静态资源 |
|||
COPY src ./src |
|||
COPY public ./public |
|||
|
|||
COPY entrypoint.sh ./entrypoint.sh |
|||
RUN chmod +x ./entrypoint.sh |
|||
|
|||
# 如需数据库文件(如 SQLite),可挂载到宿主机 |
|||
VOLUME /app/database |
|||
|
|||
# 启动命令(如有端口需求可暴露端口) |
|||
EXPOSE 3000 |
|||
|
|||
# 健康检查:每30秒检查一次服务端口,3次失败则容器为unhealthy |
|||
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \ |
|||
CMD wget --spider -q http://localhost:3000/ || exit 1 |
|||
|
|||
ENTRYPOINT ["./entrypoint.sh"] |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,18 @@ |
|||
#!/bin/sh |
|||
set -e |
|||
|
|||
# 数据库文件路径(可根据实际环境调整) |
|||
DB_FILE=./database/db.sqlite3 |
|||
|
|||
# 如果数据库文件不存在,先 migrate 再 seed |
|||
if [ ! -f "$DB_FILE" ]; then |
|||
echo "Database not found, running migration and seed..." |
|||
bun run npx knex migrate:latest |
|||
bun run npx knex seed:run |
|||
else |
|||
echo "Database exists, running migration only..." |
|||
bun run npx knex migrate:latest |
|||
fi |
|||
|
|||
# 启动主服务 |
|||
exec bun src/main.js |
@ -1 +0,0 @@ |
|||
sada |
@ -0,0 +1,101 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="zh-cn"> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<title>登录 / 注册</title> |
|||
<style> |
|||
body { background: #f5f6fa; font-family: 'Segoe UI', Arial, sans-serif; } |
|||
.container { max-width: 350px; margin: 60px auto; background: #fff; border-radius: 8px; box-shadow: 0 2px 12px #0001; padding: 32px 28px; } |
|||
h2 { text-align: center; margin-bottom: 24px; color: #333; } |
|||
.tabs { display: flex; margin-bottom: 24px; } |
|||
.tab { flex: 1; text-align: center; padding: 10px 0; cursor: pointer; border-bottom: 2px solid #eee; color: #888; font-weight: 500; } |
|||
.tab.active { color: #1976d2; border-bottom: 2px solid #1976d2; } |
|||
.form-group { margin-bottom: 18px; } |
|||
label { display: block; margin-bottom: 6px; color: #555; } |
|||
input { width: 100%; padding: 8px 10px; border: 1px solid #ccc; border-radius: 4px; font-size: 15px; } |
|||
button { width: 100%; padding: 10px; background: #1976d2; color: #fff; border: none; border-radius: 4px; font-size: 16px; cursor: pointer; margin-top: 8px; } |
|||
button:active { background: #145ea8; } |
|||
.msg { text-align: center; color: #e53935; margin-bottom: 10px; min-height: 22px; } |
|||
</style> |
|||
</head> |
|||
<body> |
|||
<div class="container"> |
|||
<div class="tabs"> |
|||
<div class="tab active" id="loginTab">登录</div> |
|||
<div class="tab" id="registerTab">注册</div> |
|||
</div> |
|||
<div class="msg" id="msg"></div> |
|||
<form id="loginForm"> |
|||
<div class="form-group"> |
|||
<label for="login-username">用户名</label> |
|||
<input type="text" id="login-username" required autocomplete="username"> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label for="login-password">密码</label> |
|||
<input type="password" id="login-password" required autocomplete="current-password"> |
|||
</div> |
|||
<button type="submit">登录</button> |
|||
</form> |
|||
<form id="registerForm" style="display:none;"> |
|||
<div class="form-group"> |
|||
<label for="register-username">用户名</label> |
|||
<input type="text" id="register-username" required autocomplete="username"> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label for="register-email">邮箱</label> |
|||
<input type="email" id="register-email" required autocomplete="email"> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label for="register-password">密码</label> |
|||
<input type="password" id="register-password" required autocomplete="new-password"> |
|||
</div> |
|||
<button type="submit">注册</button> |
|||
</form> |
|||
</div> |
|||
<script> |
|||
const loginTab = document.getElementById('loginTab'); |
|||
const registerTab = document.getElementById('registerTab'); |
|||
const loginForm = document.getElementById('loginForm'); |
|||
const registerForm = document.getElementById('registerForm'); |
|||
const msg = document.getElementById('msg'); |
|||
|
|||
loginTab.onclick = () => { |
|||
loginTab.classList.add('active'); |
|||
registerTab.classList.remove('active'); |
|||
loginForm.style.display = ''; |
|||
registerForm.style.display = 'none'; |
|||
msg.textContent = ''; |
|||
}; |
|||
registerTab.onclick = () => { |
|||
registerTab.classList.add('active'); |
|||
loginTab.classList.remove('active'); |
|||
registerForm.style.display = ''; |
|||
loginForm.style.display = 'none'; |
|||
msg.textContent = ''; |
|||
}; |
|||
|
|||
loginForm.onsubmit = async e => { |
|||
e.preventDefault(); |
|||
msg.textContent = ''; |
|||
const username = document.getElementById('login-username').value.trim(); |
|||
const password = document.getElementById('login-password').value; |
|||
if (!username || !password) { msg.textContent = '请填写完整信息'; return; } |
|||
// TODO: 替换为实际API |
|||
msg.style.color = '#1976d2'; |
|||
msg.textContent = '登录成功(示例)'; |
|||
}; |
|||
registerForm.onsubmit = async e => { |
|||
e.preventDefault(); |
|||
msg.textContent = ''; |
|||
const username = document.getElementById('register-username').value.trim(); |
|||
const email = document.getElementById('register-email').value.trim(); |
|||
const password = document.getElementById('register-password').value; |
|||
if (!username || !email || !password) { msg.textContent = '请填写完整信息'; return; } |
|||
// TODO: 替换为实际API |
|||
msg.style.color = '#1976d2'; |
|||
msg.textContent = '注册成功(示例)'; |
|||
}; |
|||
</script> |
|||
</body> |
|||
</html> |
@ -1,41 +1,56 @@ |
|||
// Job Controller 示例:如何调用 service 层动态控制和查询定时任务
|
|||
import JobService from "services/JobService.js" |
|||
import Router from "utils/router.js" |
|||
import { formatResponse } from "utils/helper.js" |
|||
|
|||
class JobController { |
|||
static routes() { |
|||
const router = new Router({ prefix: "/api/jobs" }) |
|||
router.get("/", JobController.list) |
|||
router.post("/start/:id", JobController.start) |
|||
router.post("/stop/:id", JobController.stop) |
|||
router.post("/update/:id", JobController.updateCron) |
|||
return router |
|||
} |
|||
const jobService = new JobService() |
|||
|
|||
static async list(ctx) { |
|||
ctx.body = JobService.listJobs() |
|||
export const list = async (ctx) => { |
|||
try { |
|||
const data = jobService.listJobs() |
|||
ctx.body = formatResponse(true, data) |
|||
} catch (err) { |
|||
ctx.body = formatResponse(false, null, err.message || "获取任务列表失败") |
|||
} |
|||
} |
|||
|
|||
static async start(ctx) { |
|||
const { id } = ctx.params |
|||
JobService.startJob(id) |
|||
ctx.body = { success: true, message: `${id} 任务已启动` } |
|||
export const start = async (ctx) => { |
|||
const { id } = ctx.params |
|||
try { |
|||
jobService.startJob(id) |
|||
ctx.body = formatResponse(true, null, null, `${id} 任务已启动`) |
|||
} catch (err) { |
|||
ctx.body = formatResponse(false, null, err.message || "启动任务失败") |
|||
} |
|||
} |
|||
|
|||
static async stop(ctx) { |
|||
const { id } = ctx.params |
|||
JobService.stopJob(id) |
|||
ctx.body = { success: true, message: `${id} 任务已停止` } |
|||
export const stop = async (ctx) => { |
|||
const { id } = ctx.params |
|||
try { |
|||
jobService.stopJob(id) |
|||
ctx.body = formatResponse(true, null, null, `${id} 任务已停止`) |
|||
} catch (err) { |
|||
ctx.body = formatResponse(false, null, err.message || "停止任务失败") |
|||
} |
|||
} |
|||
|
|||
static async updateCron(ctx) { |
|||
const { id } = ctx.params |
|||
const { cronTime } = ctx.request.body |
|||
JobService.updateJobCron(id, cronTime) |
|||
ctx.body = { success: true, message: `${id} 任务频率已修改` } |
|||
export const updateCron = async (ctx) => { |
|||
const { id } = ctx.params |
|||
const { cronTime } = ctx.request.body |
|||
try { |
|||
jobService.updateJobCron(id, cronTime) |
|||
ctx.body = formatResponse(true, null, null, `${id} 任务频率已修改`) |
|||
} catch (err) { |
|||
ctx.body = formatResponse(false, null, err.message || "修改任务频率失败") |
|||
} |
|||
} |
|||
|
|||
export default JobController |
|||
|
|||
// 你可以在路由中引入这些 controller 方法,实现接口调用
|
|||
// 路由注册示例
|
|||
import Router from "utils/router.js" |
|||
export function createRoutes() { |
|||
const router = new Router({ prefix: "/api/jobs" }) |
|||
router.get("/", list) |
|||
router.post("/start/:id", start) |
|||
router.post("/stop/:id", stop) |
|||
router.post("/update/:id", updateCron) |
|||
return router |
|||
} |
|||
|
@ -1,22 +1,16 @@ |
|||
import Router from 'utils/router.js'; |
|||
import { formatResponse } from "utils/helper.js" |
|||
|
|||
class StatusController { |
|||
static routes() { |
|||
const v1 = new Router({ prefix: "/api/v1" }) |
|||
|
|||
// 组内中间件
|
|||
v1.use((ctx, next) => { |
|||
ctx.set("X-API-Version", "v1") |
|||
return next() |
|||
}) |
|||
|
|||
v1.get("/status", StatusController.status) |
|||
return v1 |
|||
} |
|||
|
|||
static async status(ctx) { |
|||
ctx.body = "OK" |
|||
} |
|||
export const status = async (ctx) => { |
|||
ctx.body = "OK" |
|||
} |
|||
|
|||
export default StatusController |
|||
import Router from "utils/router.js" |
|||
export function createRoutes() { |
|||
const v1 = new Router({ prefix: "/api/v1" }) |
|||
v1.use((ctx, next) => { |
|||
ctx.set("X-API-Version", "v1") |
|||
return next() |
|||
}) |
|||
v1.get("/status", status) |
|||
return v1 |
|||
} |
|||
|
@ -1,23 +1,44 @@ |
|||
import UserService from 'services/UserService.js'; |
|||
import Router from 'utils/router.js'; |
|||
import UserService from "services/UserService.js" |
|||
import { formatResponse } from "utils/helper.js" |
|||
|
|||
class UserController { |
|||
static routes() { |
|||
let router = new Router({ prefix: '/api' }); |
|||
router.get('/hello', UserController.hello); |
|||
router.get('/user/:id', UserController.getUser); |
|||
return router; |
|||
} |
|||
const userService = new UserService() |
|||
|
|||
static async hello(ctx) { |
|||
ctx.body = 'Hello World'; |
|||
} |
|||
export const hello = async (ctx) => { |
|||
ctx.body = formatResponse(true, "Hello World") |
|||
} |
|||
|
|||
export const getUser = async (ctx) => { |
|||
const user = await userService.getUserById(ctx.params.id) |
|||
ctx.body = formatResponse(true, user) |
|||
} |
|||
|
|||
static async getUser(ctx) { |
|||
// 调用 service 层获取用户
|
|||
const user = await UserService.getUserById(ctx.params.id); |
|||
ctx.body = user; |
|||
} |
|||
export const register = async (ctx) => { |
|||
try { |
|||
const { username, email, password } = ctx.request.body |
|||
const user = await userService.register({ username, email, password }) |
|||
ctx.body = formatResponse(true, user) |
|||
} catch (err) { |
|||
ctx.body = formatResponse(false, null, err.message) |
|||
} |
|||
} |
|||
|
|||
export default UserController; |
|||
export const login = async (ctx) => { |
|||
try { |
|||
const { username, email, password } = ctx.request.body |
|||
const result = await userService.login({ username, email, password }) |
|||
ctx.body = formatResponse(true, result) |
|||
} catch (err) { |
|||
ctx.body = formatResponse(false, null, err.message) |
|||
} |
|||
} |
|||
|
|||
// 路由注册示例
|
|||
import Router from "utils/router.js" |
|||
export function createRoutes() { |
|||
const router = new Router({ prefix: "/api" }) |
|||
router.get("/hello", hello) |
|||
router.get("/user/:id", getUser) |
|||
router.post("/register", register) |
|||
router.post("/login", login) |
|||
return router |
|||
} |
|||
|
@ -0,0 +1,68 @@ |
|||
// JWT 鉴权中间件,支持白名单和黑名单,白名单/黑名单支持glob语法,白名单可指定是否校验权限(auth: true/false/"try")
|
|||
import jwt from "./jwt" |
|||
import { minimatch } from "minimatch" |
|||
|
|||
export const JWT_SECRET = process.env.JWT_SECRET || "jwt-demo-secret" |
|||
|
|||
function matchList(list, path) { |
|||
for (const item of list) { |
|||
if (typeof item === "string" && minimatch(path, item)) { |
|||
return { matched: true, auth: false } |
|||
} |
|||
if (typeof item === "object" && minimatch(path, item.pattern)) { |
|||
return { matched: true, auth: item.auth } |
|||
} |
|||
} |
|||
return { matched: false } |
|||
} |
|||
|
|||
function verifyToken(ctx) { |
|||
const token = ctx.headers["authorization"]?.replace(/^Bearer\s/, "") |
|||
if (!token) return { ok: false } |
|||
try { |
|||
ctx.state.user = jwt.verify(token, JWT_SECRET) |
|||
return { ok: true } |
|||
} catch { |
|||
ctx.state.user = undefined |
|||
return { ok: false } |
|||
} |
|||
} |
|||
|
|||
export default function authMiddleware(options = { |
|||
whiteList: [], |
|||
blackList: [] |
|||
}) { |
|||
return async (ctx, next) => { |
|||
// 黑名单优先生效
|
|||
if (matchList(options.blackList, ctx.path).matched) { |
|||
ctx.status = 403 |
|||
ctx.body = { success: false, error: "禁止访问" } |
|||
return |
|||
} |
|||
// 白名单处理
|
|||
const white = matchList(options.whiteList, ctx.path) |
|||
if (white.matched) { |
|||
if (white.auth === false) { |
|||
return await next() |
|||
} |
|||
if (white.auth === "try") { |
|||
verifyToken(ctx) // token可选,校验失败不报错
|
|||
return await next() |
|||
} |
|||
// true 或其他情况,必须有token
|
|||
if (!verifyToken(ctx).ok) { |
|||
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 |
|||
} |
|||
await next() |
|||
} |
|||
} |
@ -0,0 +1,3 @@ |
|||
// 统一导出所有中间件
|
|||
import auth from "./auth.js" |
|||
export { auth } |
@ -0,0 +1,3 @@ |
|||
// 兼容性导出,便于后续扩展
|
|||
import jwt from "jsonwebtoken" |
|||
export default jwt |
@ -0,0 +1,33 @@ |
|||
import ResponseTime from "./ResponseTime" |
|||
import Send from "./Send" |
|||
import { resolve } from "path" |
|||
import { fileURLToPath } from "url" |
|||
import path from "path" |
|||
import errorHandler from "./errorHandler" |
|||
import { auth } from "./Auth" |
|||
|
|||
const __dirname = path.dirname(fileURLToPath(import.meta.url)) |
|||
const publicPath = resolve(__dirname, "../../public") |
|||
|
|||
export default app => { |
|||
app.use(errorHandler()) |
|||
app.use(ResponseTime) |
|||
app.use( |
|||
auth({ |
|||
whiteList: [ |
|||
{ pattern: "/", auth: "try" }, |
|||
"/api/login", |
|||
"/api/register" |
|||
], |
|||
blackList: [], |
|||
}) |
|||
) |
|||
app.use(async (ctx, next) => { |
|||
try { |
|||
await Send(ctx, ctx.path, { root: publicPath }) |
|||
} catch (err) { |
|||
if (err.status !== 404) throw err |
|||
} |
|||
await next() |
|||
}) |
|||
} |
@ -1,23 +0,0 @@ |
|||
import ResponseTime from "./ResponseTime"; |
|||
import Send from "./Send"; |
|||
import { resolve } from 'path'; |
|||
import { fileURLToPath } from 'url'; |
|||
import path from "path"; |
|||
import errorHandler from './errorHandler'; |
|||
|
|||
|
|||
const __dirname = path.dirname(fileURLToPath(import.meta.url)); |
|||
const publicPath = resolve(__dirname, '../../public'); |
|||
|
|||
export default (app)=>{ |
|||
app.use(errorHandler()); |
|||
app.use(ResponseTime) |
|||
app.use(async (ctx, next) => { |
|||
try { |
|||
await Send(ctx, ctx.path, { root: publicPath }); |
|||
} catch (err) { |
|||
if (err.status !== 404) throw err; |
|||
} |
|||
await next(); |
|||
}) |
|||
} |
@ -1,18 +1,18 @@ |
|||
import jobs from '../jobs'; |
|||
import jobs from "../jobs" |
|||
|
|||
class JobService { |
|||
static startJob(id) { |
|||
return jobs.start(id); |
|||
} |
|||
static stopJob(id) { |
|||
return jobs.stop(id); |
|||
} |
|||
static updateJobCron(id, cronTime) { |
|||
return jobs.updateCronTime(id, cronTime); |
|||
} |
|||
static listJobs() { |
|||
return jobs.list(); |
|||
} |
|||
startJob(id) { |
|||
return jobs.start(id) |
|||
} |
|||
stopJob(id) { |
|||
return jobs.stop(id) |
|||
} |
|||
updateJobCron(id, cronTime) { |
|||
return jobs.updateCronTime(id, cronTime) |
|||
} |
|||
listJobs() { |
|||
return jobs.list() |
|||
} |
|||
} |
|||
|
|||
export default JobService; |
|||
export default JobService |
|||
|
@ -1,39 +1,74 @@ |
|||
// src/services/userService.js
|
|||
// 用户相关业务逻辑
|
|||
|
|||
import UserModel from 'db/models/UserModel.js'; |
|||
import UserModel from "db/models/UserModel.js" |
|||
import { hashPassword, comparePassword } from "utils/bcrypt.js" |
|||
import { JWT_SECRET } from "@/middlewares/Auth/auth.js" |
|||
import jwt from "@/middlewares/Auth/jwt.js" |
|||
|
|||
class UserService { |
|||
static async getUserById(id) { |
|||
// 这里可以调用数据库模型
|
|||
// 示例返回
|
|||
return { id, name: `User_${id}` }; |
|||
} |
|||
|
|||
// 获取所有用户
|
|||
static async getAllUsers() { |
|||
return await UserModel.findAll(); |
|||
} |
|||
|
|||
// 创建新用户
|
|||
static async createUser(data) { |
|||
if (!data.name) throw new Error('用户名不能为空'); |
|||
return await UserModel.create(data); |
|||
} |
|||
|
|||
// 更新用户
|
|||
static async updateUser(id, data) { |
|||
const user = await UserModel.findById(id); |
|||
if (!user) throw new Error('用户不存在'); |
|||
return await UserModel.update(id, data); |
|||
} |
|||
|
|||
// 删除用户
|
|||
static async deleteUser(id) { |
|||
const user = await UserModel.findById(id); |
|||
if (!user) throw new Error('用户不存在'); |
|||
return await UserModel.delete(id); |
|||
} |
|||
async getUserById(id) { |
|||
// 这里可以调用数据库模型
|
|||
// 示例返回
|
|||
return { id, name: `User_${id}` } |
|||
} |
|||
|
|||
// 获取所有用户
|
|||
async getAllUsers() { |
|||
return await UserModel.findAll() |
|||
} |
|||
|
|||
// 创建新用户
|
|||
async createUser(data) { |
|||
if (!data.name) throw new Error("用户名不能为空") |
|||
return await UserModel.create(data) |
|||
} |
|||
|
|||
// 更新用户
|
|||
async updateUser(id, data) { |
|||
const user = await UserModel.findById(id) |
|||
if (!user) throw new Error("用户不存在") |
|||
return await UserModel.update(id, data) |
|||
} |
|||
|
|||
// 删除用户
|
|||
async deleteUser(id) { |
|||
const user = await UserModel.findById(id) |
|||
if (!user) throw new Error("用户不存在") |
|||
return await UserModel.delete(id) |
|||
} |
|||
|
|||
// 注册新用户
|
|||
async register(data) { |
|||
if (!data.username || !data.email || !data.password) throw new Error("用户名、邮箱和密码不能为空") |
|||
const existUser = await UserModel.findByUsername(data.username) |
|||
if (existUser) throw new Error("用户名已存在") |
|||
const existEmail = await UserModel.findByEmail(data.email) |
|||
if (existEmail) throw new Error("邮箱已被注册") |
|||
// 密码加密
|
|||
const hashed = await hashPassword(data.password) |
|||
|
|||
const user = await UserModel.create({ ...data, password: hashed }) |
|||
// 返回脱敏信息
|
|||
const { password, ...userInfo } = Array.isArray(user) ? user[0] : user |
|||
return userInfo |
|||
} |
|||
|
|||
// 登录
|
|||
async login({ username, email, password }) { |
|||
let user |
|||
if (username) { |
|||
user = await UserModel.findByUsername(username) |
|||
} else if (email) { |
|||
user = await UserModel.findByEmail(email) |
|||
} |
|||
if (!user) throw new Error("用户不存在") |
|||
// 校验密码
|
|||
const ok = await comparePassword(password, user.password) |
|||
if (!ok) throw new Error("密码错误") |
|||
// 生成token
|
|||
const token = jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, { expiresIn: "2h" }) |
|||
// 返回token和用户信息
|
|||
const { password: pwd, ...userInfo } = user |
|||
return { token, user: userInfo } |
|||
} |
|||
} |
|||
|
|||
export default UserService; |
|||
export default UserService |
|||
|
@ -0,0 +1,37 @@ |
|||
// 抽象基类,使用泛型来正确推导子类类型
|
|||
class BaseSingleton { |
|||
static _instance |
|||
|
|||
constructor() { |
|||
if (this.constructor === BaseSingleton) { |
|||
throw new Error("禁止直接实例化 BaseOne 抽象类") |
|||
} |
|||
|
|||
if (this.constructor._instance) { |
|||
throw new Error("构造函数私有化失败,禁止重复 new") |
|||
} |
|||
|
|||
// this.constructor 是子类,所以这里设为 instance
|
|||
this.constructor._instance = this |
|||
} |
|||
|
|||
static getInstance() { |
|||
const clazz = this |
|||
if (!clazz._instance) { |
|||
const self = new this() |
|||
const handler = { |
|||
get: function (target, prop) { |
|||
const value = Reflect.get(target, prop) |
|||
if (typeof value === "function") { |
|||
return value.bind(target) |
|||
} |
|||
return Reflect.get(target, prop) |
|||
}, |
|||
} |
|||
clazz._instance = new Proxy(self, handler) |
|||
} |
|||
return clazz._instance |
|||
} |
|||
} |
|||
|
|||
export { BaseSingleton } |
@ -0,0 +1,11 @@ |
|||
// 密码加密与校验工具
|
|||
import bcrypt from "bcryptjs" |
|||
|
|||
export async function hashPassword(password) { |
|||
const salt = await bcrypt.genSalt(10) |
|||
return bcrypt.hash(password, salt) |
|||
} |
|||
|
|||
export async function comparePassword(password, hash) { |
|||
return bcrypt.compare(password, hash) |
|||
} |
@ -0,0 +1,4 @@ |
|||
|
|||
export function formatResponse(success, data = null, error = null) { |
|||
return { success, error, data } |
|||
} |
Loading…
Reference in new issue