This commit is contained in:
艾贤凌
2026-03-16 12:05:55 +08:00
parent af3a7c83e8
commit 6d4a72161f
33 changed files with 5671 additions and 178 deletions

View File

@@ -1,5 +1,117 @@
import dayjs from "dayjs";
import dayjs from 'dayjs'
import crypto from 'crypto'
export function time(date){
// 密码加密 key与原 PHP 保持一致)
const PASSWORD_KEY = process.env.PASSWORD_KEY || 'WVImV8mIMnpY9Lrmh3yoaJ2yRLNACBfg'
/**
* 格式化时间
*/
export function time(date) {
return dayjs(date).format('YYYY-MM-DD HH:mm:ss')
}
/**
* 当前 Unix 时间戳(秒)
*/
export function unixTime() {
return Math.floor(Date.now() / 1000)
}
/**
* md5 加密(兼容 PHP md5()
*/
export function md5(str) {
return crypto.createHash('md5').update(str).digest('hex')
}
/**
* 对密码做 md5+key 加密(与原 PHP PASSWORD_KEY 逻辑一致)
*/
export function encryptPassword(password) {
return md5(password + PASSWORD_KEY)
}
/**
* 生成随机验证码
* @param {number} length 长度
* @param {'NUMBER'|'CHAR'|'ALL'} type 类型
*/
export function generateCode(length = 6, type = 'NUMBER') {
const chars = {
NUMBER: '0123456789',
CHAR: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
ALL: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
}
const pool = chars[type] || chars.NUMBER
let code = ''
for (let i = 0; i < length; i++) {
code += pool[Math.floor(Math.random() * pool.length)]
}
return code
}
/**
* 获取客户端真实 IP支持代理
*/
export function getClientIp(ctx) {
return (
ctx.request.headers['x-forwarded-for']?.split(',')[0]?.trim() ||
ctx.request.headers['x-real-ip'] ||
ctx.ip
)
}
/**
* 校验账号格式6-16位字母数字下划线
*/
export function isValidAccount(account) {
return /^[a-zA-Z0-9_]{6,16}$/.test(account)
}
/**
* 校验邮箱格式
*/
export function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}
/**
* 从 User-Agent 解析设备/系统/浏览器信息(与 PHP function.php 保持一致)
* @param {string} ua
* @returns {{ device: string, os: string, browse: string }}
*/
export function getDeviceInfo(ua = '') {
const uaLower = ua.toLowerCase()
// 设备类型
let device = 'pc'
if (/mobile|android|iphone|ipad|ipod|windows phone/i.test(ua)) device = 'mobile'
// 操作系统
let os = 'Other'
if (/windows nt 10/i.test(ua)) os = 'Windows 10'
else if (/windows nt 6\.3/i.test(ua)) os = 'Windows 8.1'
else if (/windows nt 6\.2/i.test(ua)) os = 'Windows 8'
else if (/windows nt 6\.1/i.test(ua)) os = 'Windows 7'
else if (/windows nt 6\.0/i.test(ua)) os = 'Windows Vista'
else if (/windows nt 5\.1/i.test(ua)) os = 'Windows XP'
else if (/windows/i.test(ua)) os = 'Windows'
else if (/android (\d+\.\d+)/i.test(ua)) os = 'Android ' + ua.match(/android (\d+\.\d+)/i)[1]
else if (/iphone os (\d+_\d+)/i.test(ua)) os = 'iOS ' + ua.match(/iphone os (\d+_\d+)/i)[1].replace('_', '.')
else if (/ipad.*os (\d+_\d+)/i.test(ua)) os = 'iPadOS ' + ua.match(/ipad.*os (\d+_\d+)/i)[1].replace('_', '.')
else if (/mac os x/i.test(ua)) os = 'macOS'
else if (/linux/i.test(ua)) os = 'Linux'
// 浏览器
let browse = 'Other'
if (/edg\//i.test(ua)) browse = 'Edge'
else if (/opr\//i.test(ua) || /opera/i.test(ua)) browse = 'Opera'
else if (/chrome\/(\d+)/i.test(ua)) browse = 'Chrome ' + ua.match(/chrome\/(\d+)/i)[1]
else if (/firefox\/(\d+)/i.test(ua)) browse = 'Firefox ' + ua.match(/firefox\/(\d+)/i)[1]
else if (/safari\/(\d+)/i.test(ua) && !/chrome/i.test(ua)) browse = 'Safari'
else if (/micromessenger/i.test(ua)) browse = '微信'
else if (/mqqbrowser/i.test(ua)) browse = 'QQ浏览器'
return { device, os, browse }
}