Files
chuanqi-qycq-web/module/server/utils/utils.js
艾贤凌 d676faa704 docs(migration): 添加清渊传奇PHP到Vue+Node.js移植计划文档
- 新增 MIGRATION.md 详细记录PHP到Node.js架构迁移方案
- 包含项目背景、现有资产盘点、架构设计、任务清单等完整规划
- 记录后端补全、前端补全、PHP停用、部署运维四个阶段实施计划
- 提供技术决策、数据库说明、进度总览等关键信息
- 更新 .gitignore 添加 *_out.txt build_output.txt 构建输出文件过滤
- 修复 utils.js 路径引用问题确保代码正常运行
2026-04-24 17:57:06 +08:00

118 lines
3.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import dayjs from 'dayjs'
import crypto from 'crypto'
// 密码加密 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 }
}