start
This commit is contained in:
138
utils/authToken.js
Normal file
138
utils/authToken.js
Normal file
@@ -0,0 +1,138 @@
|
||||
"use strict";
|
||||
|
||||
import crypto from "crypto"
|
||||
import logger from './logger.js'
|
||||
|
||||
function AuthToken() {
|
||||
}
|
||||
|
||||
AuthToken.prototype.init = function (redisdb, defaultExpiresSeconds) {
|
||||
this.tokenDB = redisdb;
|
||||
this.defaultExpiresSeconds = defaultExpiresSeconds;
|
||||
}
|
||||
|
||||
AuthToken.prototype.gen = async function (uid, tokenOld) {
|
||||
let userToken = tokenOld;
|
||||
|
||||
try {
|
||||
let tokenData = await this.tokenDB.get(userToken).then(function (data) {
|
||||
return JSON.parse(data);
|
||||
});
|
||||
if (!tokenData) {
|
||||
if (!userToken || userToken.length <= 16) {
|
||||
let hash = crypto.createHash("md5");
|
||||
hash.update(uid + Date() + Math.random());
|
||||
userToken = hash.digest("hex");
|
||||
}
|
||||
|
||||
tokenData = {
|
||||
uid,
|
||||
token: userToken,
|
||||
ts: Math.floor(Date.now() / 1000),
|
||||
ttl: this.defaultExpiresSeconds,
|
||||
};
|
||||
}
|
||||
|
||||
this.tokenDB.set(userToken, JSON.stringify(tokenData), "EX", this.defaultExpiresSeconds);
|
||||
} catch (err) {
|
||||
logger.error('生成Token失败:', { error: err.message, uid, stack: err.stack });
|
||||
}
|
||||
|
||||
return userToken;
|
||||
};
|
||||
|
||||
AuthToken.prototype.update = async function (userToken) {
|
||||
try {
|
||||
let tokenData = await this.tokenDB.get(userToken).then(function (data) {
|
||||
return JSON.parse(data);
|
||||
});
|
||||
|
||||
if (tokenData) {
|
||||
this.tokenDB.set(userToken, JSON.stringify(tokenData), "EX", this.defaultExpiresSeconds);
|
||||
return tokenData;
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('更新Token失败:', { error: err.message, stack: err.stack });
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
AuthToken.prototype.del = async function (userToken) {
|
||||
let tokenData = await this.tokenDB.get(userToken).then(function (data) {
|
||||
return JSON.parse(data);
|
||||
});
|
||||
|
||||
if (tokenData) {
|
||||
this.tokenDB.del(userToken);
|
||||
}
|
||||
|
||||
return tokenData;
|
||||
};
|
||||
|
||||
AuthToken.prototype.check = async function (userToken, updateExpire = true) {
|
||||
try {
|
||||
if (updateExpire) {
|
||||
if (await this.tokenDB.expire(userToken, this.defaultExpiresSeconds) >= 1) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (await this.tokenDB.exists([userToken]) >= 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('检查Token失败:', { error: err.message, userToken, stack: err.stack });
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
AuthToken.prototype.get = async function (token) {
|
||||
let tokenData = await this.tokenDB.get(token).then(function (data) {
|
||||
return JSON.parse(data);
|
||||
});
|
||||
|
||||
if (!tokenData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return tokenData;
|
||||
};
|
||||
|
||||
AuthToken.prototype.koaRequest = async function (ctx, next) {
|
||||
try {
|
||||
let token = ctx.request.body.token;
|
||||
|
||||
if (!token) token = ctx.request.query.token;
|
||||
if (!token) token = ctx.header["authorization"];
|
||||
if (!token) token = ctx.header["token"];
|
||||
|
||||
if (!token) {
|
||||
throw new Error("Need token param.");
|
||||
}
|
||||
|
||||
let ret = await this.check(token);
|
||||
if (!ret) {
|
||||
throw new Error("User not login in.");
|
||||
}
|
||||
|
||||
let tokenData = await this.get(token);
|
||||
if (tokenData && tokenData.uid) {
|
||||
const { DBModel } = await import("../models/index.js");
|
||||
const user = await DBModel.User.findOne({ _id: tokenData.uid });
|
||||
if (user) {
|
||||
ctx.userInfo = user;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.authToken = this;
|
||||
|
||||
return next();
|
||||
} catch (err) {
|
||||
logger.error('身份验证失败:', { error: err.message, token: ctx.request.body.token || ctx.request.query.token || ctx.header["authorization"] || ctx.header["token"], stack: err.stack });
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const Token = new AuthToken()
|
||||
export { Token }
|
||||
48
utils/logger.js
Normal file
48
utils/logger.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const logDir = path.join(process.cwd(), 'logs');
|
||||
if (!fs.existsSync(logDir)) {
|
||||
fs.mkdirSync(logDir, { recursive: true });
|
||||
}
|
||||
|
||||
const LEVELS = {
|
||||
ERROR: 'ERROR',
|
||||
WARN: 'WARN',
|
||||
INFO: 'INFO',
|
||||
DEBUG: 'DEBUG'
|
||||
};
|
||||
|
||||
const getCurrentTimestamp = () => {
|
||||
return new Date().toISOString();
|
||||
};
|
||||
|
||||
const writeLog = (level, message, meta = {}) => {
|
||||
const timestamp = getCurrentTimestamp();
|
||||
const logEntry = {
|
||||
timestamp,
|
||||
level,
|
||||
message,
|
||||
meta
|
||||
};
|
||||
|
||||
console.log(`[${timestamp}] ${level}: ${message}`, meta);
|
||||
|
||||
const logFileName = `${new Date().toISOString().slice(0, 10)}.log`;
|
||||
const logFilePath = path.join(logDir, logFileName);
|
||||
|
||||
fs.appendFile(logFilePath, JSON.stringify(logEntry) + '\n', (err) => {
|
||||
if (err) {
|
||||
console.error('Failed to write log to file:', err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const logger = {
|
||||
error: (message, meta) => writeLog(LEVELS.ERROR, message, meta),
|
||||
warn: (message, meta) => writeLog(LEVELS.WARN, message, meta),
|
||||
info: (message, meta) => writeLog(LEVELS.INFO, message, meta),
|
||||
debug: (message, meta) => writeLog(LEVELS.DEBUG, message, meta)
|
||||
};
|
||||
|
||||
export default logger;
|
||||
13
utils/passwdCrypto.js
Normal file
13
utils/passwdCrypto.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import bcrypt from 'bcrypt';
|
||||
|
||||
const SALT_ROUNDS = 10;
|
||||
|
||||
function hash(text) {
|
||||
return bcrypt.hash(text, SALT_ROUNDS);
|
||||
}
|
||||
|
||||
function compare(text, hash) {
|
||||
return bcrypt.compare(text, hash);
|
||||
}
|
||||
|
||||
export default { hash, compare }
|
||||
85
utils/responseUtil.js
Normal file
85
utils/responseUtil.js
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 响应工具类
|
||||
* 提供统一的响应格式
|
||||
*/
|
||||
class ResponseUtil {
|
||||
/**
|
||||
* 成功响应
|
||||
* @param {Object} ctx - Koa上下文对象
|
||||
* @param {Object} data - 响应数据
|
||||
* @param {string} message - 响应消息
|
||||
*/
|
||||
static success(ctx, data, message = '操作成功') {
|
||||
ctx.status = 200;
|
||||
ctx.body = {
|
||||
code: 0,
|
||||
msg: message,
|
||||
data,
|
||||
ts: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误响应
|
||||
* @param {Object} ctx - Koa上下文对象
|
||||
* @param {string} message - 错误消息
|
||||
* @param {number} errorCode - 应用错误码
|
||||
*/
|
||||
static error(ctx, message, data = null, errorCode = 500) {
|
||||
// 根据规则,只要进入controller,http状态码都是200
|
||||
ctx.status = 200;
|
||||
ctx.body = {
|
||||
code: errorCode,
|
||||
msg: message,
|
||||
data,
|
||||
ts: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求参数错误响应
|
||||
* @param {Object} ctx - Koa上下文对象
|
||||
* @param {string} message - 错误消息
|
||||
*/
|
||||
static badRequest(ctx, message = '请求参数错误', data = null) {
|
||||
return ResponseUtil.error(ctx, message, data, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* 未授权响应
|
||||
* @param {Object} ctx - Koa上下文对象
|
||||
* @param {string} message - 错误消息
|
||||
*/
|
||||
static unauthorized(ctx, message = '未授权', data = null) {
|
||||
return ResponseUtil.error(ctx, message, data, 401);
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁止访问响应
|
||||
* @param {Object} ctx - Koa上下文对象
|
||||
* @param {string} message - 错误消息
|
||||
*/
|
||||
static forbidden(ctx, message = '禁止访问', data = null) {
|
||||
return ResponseUtil.error(ctx, message, data, 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源不存在响应
|
||||
* @param {Object} ctx - Koa上下文对象
|
||||
* @param {string} message - 错误消息
|
||||
*/
|
||||
static notFound(ctx, message = '资源不存在', data = null) {
|
||||
return ResponseUtil.error(ctx, message, data, 404);
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务器内部错误响应
|
||||
* @param {Object} ctx - Koa上下文对象
|
||||
* @param {string} message - 错误消息
|
||||
*/
|
||||
static internalError(ctx, message = '服务器内部错误', data = null) {
|
||||
return ResponseUtil.error(ctx, message, data, 500);
|
||||
}
|
||||
}
|
||||
|
||||
export default ResponseUtil;
|
||||
Reference in New Issue
Block a user