refactor: 项目架构重构 - 目录标准化 + 清理冗余

- 目录重命名: backend/wdkj-server/ → server/, frontend/ai-dimension/ → client/
- 删除 20+ 冗余文件(WDKJ 旧脚本、Windows 脚本、过时文档、设计稿)
- 更新 package.json 元数据(移除 wdkj 命名)
- 完善三级 .gitignore(根 + server + client)
- 重写 README.md 和 CHANGELOG.md
- 工具脚本移至 scripts/
This commit is contained in:
Yuzhiran Dev
2026-07-11 12:45:37 +08:00
parent 66ac576082
commit c1d6dd3b29
106 changed files with 17436 additions and 2311 deletions
+492
View File
@@ -0,0 +1,492 @@
const express = require('express')
const router = express.Router()
const jwt = require('jsonwebtoken')
const { User, Admin, AdminLog, LoginLog } = require('../models')
const ApiResponse = require('../utils/response')
const weChatService = require('../utils/weixin')
const logger = require('../utils/logger')
const axios = require('axios')
/**
* 获取客户端IP
*/
const getClientIp = (req) => {
return req.headers['x-forwarded-for'] ||
req.headers['x-real-ip'] ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
'0.0.0.0'
}
/**
* 解析User-Agent
*/
const parseUserAgent = (userAgent) => {
if (!userAgent) return { device: '', os: '', browser: '' }
const device = /Mobile|Android|iPhone|iPad|iPod/i.test(userAgent) ? 'mobile' : 'desktop'
let os = ''
if (/Windows/i.test(userAgent)) os = 'Windows'
else if (/Mac OS X/i.test(userAgent)) os = 'MacOS'
else if (/Android/i.test(userAgent)) os = 'Android'
else if (/iOS|iPhone|iPad|iPod/i.test(userAgent)) os = 'iOS'
else if (/Linux/i.test(userAgent)) os = 'Linux'
else os = 'Unknown'
let browser = ''
if (/MicroMessenger/i.test(userAgent)) browser = 'WeChat'
else if (/Chrome/i.test(userAgent)) browser = 'Chrome'
else if (/Safari/i.test(userAgent)) browser = 'Safari'
else if (/Firefox/i.test(userAgent)) browser = 'Firefox'
else if (/Edge/i.test(userAgent)) browser = 'Edge'
else browser = 'Unknown'
return { device, os, browser }
}
/**
* 记录登录日志
*/
const recordLoginLog = async (userId, openid, type, method, source, req, success = true, failReason = '') => {
try {
const ip = getClientIp(req)
const userAgent = req.headers['user-agent'] || ''
const { device, os, browser } = parseUserAgent(userAgent)
await LoginLog.create({
userId,
openid,
type,
method,
source,
ip,
userAgent,
device,
os,
browser,
success,
failReason
})
} catch (error) {
logger.error('记录登录日志失败:', error)
}
}
/**
* 小程序登录
* POST /api/auth/login
*/
router.post('/login', async (req, res) => {
try {
const { code, userInfo, source = 'miniapp' } = req.body
// 方式1: 通过 code 登录(推荐)
let openid = null
if (code) {
const sessionData = await weChatService.code2Session(code)
openid = sessionData.openid
}
// 方式2: 开发环境直接使用 openid (已废弃,仅用于本地调试)
if (!openid && process.env.NODE_ENV === 'development' && req.body.openid) {
openid = req.body.openid
}
if (!openid) {
return ApiResponse.error(res, '无法获取用户标识', 400)
}
// 查找或创建用户
let user = await User.findOne({ openid })
let isNewUser = false
if (!user) {
// 新用户
user = new User({
openid,
nickName: userInfo?.nickName || '星辰旅行者',
avatarUrl: userInfo?.avatarUrl || '',
unlockedDims: [1]
})
isNewUser = true
logger.info(`新用户注册: ${openid}`)
} else {
// 老用户,更新登录时间
user.lastLoginAt = new Date()
if (userInfo?.nickName) user.nickName = userInfo.nickName
if (userInfo?.avatarUrl) user.avatarUrl = userInfo.avatarUrl
}
await user.save()
// 记录登录日志
await recordLoginLog(
user._id,
openid,
isNewUser ? 'register' : 'login',
'wechat_miniapp',
source,
req,
true
)
// 生成 JWT token(可选,小程序通常不需要)
const token = jwt.sign(
{ openid, userId: user._id },
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRE }
)
return ApiResponse.success(res, {
token,
openid,
isNewUser,
userInfo: {
nickName: user.nickName,
avatarUrl: user.avatarUrl
},
firstPlayDate: user.createdAt
}, isNewUser ? '注册成功' : '登录成功')
} catch (error) {
logger.error('登录失败:', error)
return ApiResponse.serverError(res, error.message)
}
})
/**
* WebView 静默登录
* POST /api/auth/webview-login
* 用于微信小程序 webview 内嵌 H5 的自动登录
*/
router.post('/webview-login', async (req, res) => {
try {
const { code, userInfo } = req.body
if (!code) {
return ApiResponse.error(res, '缺少登录凭证', 400)
}
// 通过 code 获取 openid
let openid = null
try {
const sessionData = await weChatService.code2Session(code)
openid = sessionData.openid
} catch (error) {
logger.error('WebView登录获取session失败:', error)
return ApiResponse.error(res, '登录凭证无效', 401)
}
if (!openid) {
return ApiResponse.error(res, '无法获取用户标识', 400)
}
// 查找或创建用户
let user = await User.findOne({ openid })
let isNewUser = false
if (!user) {
// 新用户 - 使用微信提供的信息创建账户
user = new User({
openid,
nickName: userInfo?.nickName || '星辰旅行者',
avatarUrl: userInfo?.avatarUrl || '',
unlockedDims: [1]
})
isNewUser = true
logger.info(`WebView新用户注册: ${openid}`)
} else {
// 老用户,更新登录时间和信息
user.lastLoginAt = new Date()
if (userInfo?.nickName) user.nickName = userInfo.nickName
if (userInfo?.avatarUrl) user.avatarUrl = userInfo.avatarUrl
logger.info(`WebView老用户登录: ${openid}`)
}
await user.save()
// 记录登录日志
await recordLoginLog(
user._id,
openid,
isNewUser ? 'register' : 'auto_login',
'wechat_webview',
'webview',
req,
true
)
// 生成 JWT token
const token = jwt.sign(
{ openid, userId: user._id },
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRE }
)
return ApiResponse.success(res, {
token,
openid,
isNewUser,
userInfo: {
nickName: user.nickName,
avatarUrl: user.avatarUrl
},
firstPlayDate: user.createdAt
}, isNewUser ? '注册成功' : '自动登录成功')
} catch (error) {
logger.error('WebView登录失败:', error)
return ApiResponse.serverError(res, error.message)
}
})
/**
* 管理员登录
* POST /api/auth/admin/login
*/
router.post('/admin/login', async (req, res) => {
try {
const { username, password } = req.body
// 查找管理员
const admin = await Admin.findOne({ username })
if (!admin) {
return ApiResponse.error(res, '用户名或密码错误', 401)
}
// 验证密码
const isMatch = await admin.validatePassword(password)
if (!isMatch) {
return ApiResponse.error(res, '用户名或密码错误', 401)
}
// 检查状态
if (admin.status !== 'active') {
return ApiResponse.error(res, '账户已禁用', 403)
}
// 更新登录信息
admin.lastLogin = new Date()
admin.lastLoginIp = req.ip
admin.loginCount += 1
await admin.save()
// 记录日志
await AdminLog.create({
adminId: admin._id,
adminName: admin.username,
action: 'login',
resource: 'system',
ip: req.ip,
userAgent: req.get('User-Agent')
})
// 生成 JWT token
const token = jwt.sign(
{ adminId: admin._id, role: admin.role },
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRE }
)
// 获取权限
const permissions = Admin.getPermissionsByRole(admin.role)
return ApiResponse.success(res, {
token,
adminId: admin._id,
username: admin.username,
role: admin.role,
permissions
}, '登录成功')
} catch (error) {
logger.error('管理员登录失败:', error)
return ApiResponse.serverError(res, error.message)
}
})
/**
* H5环境用户名密码登录
* POST /api/auth/user/login
*/
router.post('/user/login', async (req, res) => {
try {
const { username, password } = req.body
// 查找用户(显式包含密码字段)
const user = await User.findOne({ username }).select('+password')
if (!user) {
return ApiResponse.error(res, '用户名或密码错误', 401)
}
// 验证密码
const isMatch = await user.validatePassword(password)
if (!isMatch) {
return ApiResponse.error(res, '用户名或密码错误', 401)
}
// 更新登录时间
user.lastLoginAt = new Date()
await user.save()
// 生成 JWT token
const token = jwt.sign(
{ userId: user._id, openid: user.openid },
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRE }
)
return ApiResponse.success(res, {
token,
userId: user._id,
openid: user.openid,
userInfo: {
nickName: user.nickName,
avatarUrl: user.avatarUrl
},
firstPlayDate: user.createdAt
}, '登录成功')
} catch (error) {
logger.error('用户登录失败:', error)
return ApiResponse.serverError(res, error.message)
}
})
/**
* H5环境用户注册
* POST /api/auth/user/register
*/
router.post('/user/register', async (req, res) => {
try {
const { username, password, nickName, avatarUrl } = req.body
// 检查用户名是否已存在
const existingUser = await User.findOne({ username })
if (existingUser) {
return ApiResponse.error(res, '用户名已存在', 400)
}
// 创建新用户
const user = new User({
username,
password,
nickName: nickName || '星辰旅行者',
avatarUrl: avatarUrl || '',
unlockedDims: [1]
})
await user.save()
logger.info(`新用户注册: ${username}`)
// 生成 JWT token
const token = jwt.sign(
{ userId: user._id },
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRE }
)
return ApiResponse.success(res, {
token,
userId: user._id,
userInfo: {
nickName: user.nickName,
avatarUrl: user.avatarUrl
}
}, '注册成功')
} catch (error) {
logger.error('用户注册失败:', error)
return ApiResponse.serverError(res, error.message)
}
})
/**
* 获取当前用户信息
* GET /api/auth/me
*/
router.get('/me', async (req, res) => {
try {
const token = req.header('Authorization')?.replace('Bearer ', '')
if (!token) {
return ApiResponse.unauthorized(res)
}
const decoded = jwt.verify(token, process.env.JWT_SECRET)
const user = await User.findById(decoded.userId)
if (!user) {
return ApiResponse.notFound(res, '用户不存在')
}
return ApiResponse.success(res, user)
} catch (error) {
logger.error('获取用户信息失败:', error)
return ApiResponse.serverError(res, error.message)
}
})
/**
* WebView Token 交换接口
* POST /api/auth/webview-token
* 用于小程序 WebView 内嵌 H5 时,通过 openid 获取登录 token
* 小程序在 URL 中传递 openid,H5 用这个接口换取 token
*/
router.post('/webview-token', async (req, res) => {
try {
const { openid } = req.body
if (!openid) {
return ApiResponse.error(res, '缺少用户标识', 400)
}
// 查找用户
const user = await User.findOne({ openid })
if (!user) {
return ApiResponse.error(res, '用户不存在', 404)
}
// 生成新的 JWT token
const token = jwt.sign(
{ openid, userId: user._id },
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRE }
)
// 更新最后登录时间
user.lastLoginAt = new Date()
await user.save()
// 记录登录日志
await recordLoginLog(
user._id,
openid,
'webview_auto_login',
'wechat_webview',
'webview_h5',
req,
true
)
logger.info(`WebView Token交换成功: ${openid}`)
return ApiResponse.success(res, {
token,
openid,
userInfo: {
nickName: user.nickName,
avatarUrl: user.avatarUrl
}
}, '登录成功')
} catch (error) {
logger.error('WebView Token交换失败:', error)
return ApiResponse.serverError(res, error.message)
}
})
module.exports = router