mirror of
https://github.com/patdelphi/suanming.git
synced 2026-02-28 05:33:11 +08:00
- 完全按照logic/yijing.txt补全所有64卦的完整数据结构 - 包含每卦的卦辞、象传、六爻详解和人生指导 - 重建八字、易经、紫微斗数三个核心分析器 - 实现完整的本地SQLite数据库替代Supabase - 添加本地Express.js后端服务器 - 更新前端API调用为本地接口 - 实现JWT本地认证系统 - 完善历史记录和用户管理功能
220 lines
5.4 KiB
JavaScript
220 lines
5.4 KiB
JavaScript
const express = require('express');
|
|
const bcrypt = require('bcryptjs');
|
|
const { getDB } = require('../database/index.cjs');
|
|
const {
|
|
generateToken,
|
|
authenticate,
|
|
createSession,
|
|
deleteSession,
|
|
deleteAllSessions
|
|
} = require('../middleware/auth.cjs');
|
|
const { AppError, asyncHandler } = require('../middleware/errorHandler.cjs');
|
|
|
|
const router = express.Router();
|
|
|
|
// 用户注册
|
|
router.post('/register', asyncHandler(async (req, res) => {
|
|
const { email, password, full_name } = req.body;
|
|
|
|
// 输入验证
|
|
if (!email || !password) {
|
|
throw new AppError('邮箱和密码不能为空', 400, 'MISSING_FIELDS');
|
|
}
|
|
|
|
if (password.length < 6) {
|
|
throw new AppError('密码长度至少6位', 400, 'PASSWORD_TOO_SHORT');
|
|
}
|
|
|
|
// 验证邮箱格式
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
if (!emailRegex.test(email)) {
|
|
throw new AppError('邮箱格式不正确', 400, 'INVALID_EMAIL');
|
|
}
|
|
|
|
const db = getDB();
|
|
|
|
// 检查邮箱是否已存在
|
|
const existingUser = db.prepare('SELECT id FROM users WHERE email = ?').get(email);
|
|
if (existingUser) {
|
|
throw new AppError('该邮箱已被注册', 400, 'EMAIL_EXISTS');
|
|
}
|
|
|
|
// 加密密码
|
|
const saltRounds = 12;
|
|
const passwordHash = await bcrypt.hash(password, saltRounds);
|
|
|
|
// 创建用户
|
|
const insertUser = db.prepare(
|
|
'INSERT INTO users (email, password_hash) VALUES (?, ?)'
|
|
);
|
|
|
|
const result = insertUser.run(email, passwordHash);
|
|
const userId = result.lastInsertRowid;
|
|
|
|
// 创建用户档案
|
|
if (full_name) {
|
|
const insertProfile = db.prepare(
|
|
'INSERT INTO user_profiles (user_id, full_name) VALUES (?, ?)'
|
|
);
|
|
insertProfile.run(userId, full_name);
|
|
}
|
|
|
|
// 生成JWT token
|
|
const token = generateToken(userId);
|
|
|
|
// 创建会话
|
|
createSession(userId, token);
|
|
|
|
res.status(201).json({
|
|
data: {
|
|
user: {
|
|
id: userId,
|
|
email: email
|
|
},
|
|
token: token
|
|
}
|
|
});
|
|
}));
|
|
|
|
// 用户登录
|
|
router.post('/login', asyncHandler(async (req, res) => {
|
|
const { email, password } = req.body;
|
|
|
|
// 输入验证
|
|
if (!email || !password) {
|
|
throw new AppError('邮箱和密码不能为空', 400, 'MISSING_FIELDS');
|
|
}
|
|
|
|
const db = getDB();
|
|
|
|
// 查找用户
|
|
const user = db.prepare('SELECT id, email, password_hash FROM users WHERE email = ?').get(email);
|
|
if (!user) {
|
|
throw new AppError('邮箱或密码错误', 401, 'INVALID_CREDENTIALS');
|
|
}
|
|
|
|
// 验证密码
|
|
const isPasswordValid = await bcrypt.compare(password, user.password_hash);
|
|
if (!isPasswordValid) {
|
|
throw new AppError('邮箱或密码错误', 401, 'INVALID_CREDENTIALS');
|
|
}
|
|
|
|
// 生成JWT token
|
|
const token = generateToken(user.id);
|
|
|
|
// 创建会话
|
|
createSession(user.id, token);
|
|
|
|
res.json({
|
|
data: {
|
|
user: {
|
|
id: user.id,
|
|
email: user.email
|
|
},
|
|
token: token
|
|
}
|
|
});
|
|
}));
|
|
|
|
// 获取当前用户信息
|
|
router.get('/me', authenticate, asyncHandler(async (req, res) => {
|
|
const db = getDB();
|
|
|
|
// 获取用户基本信息
|
|
const user = db.prepare('SELECT id, email, created_at FROM users WHERE id = ?').get(req.user.id);
|
|
|
|
// 获取用户档案信息
|
|
const profile = db.prepare(
|
|
'SELECT username, full_name, birth_date, birth_time, birth_location, gender, avatar_url FROM user_profiles WHERE user_id = ?'
|
|
).get(req.user.id);
|
|
|
|
res.json({
|
|
data: {
|
|
user: {
|
|
...user,
|
|
profile: profile || null
|
|
}
|
|
}
|
|
});
|
|
}));
|
|
|
|
// 用户登出
|
|
router.post('/logout', authenticate, asyncHandler(async (req, res) => {
|
|
// 删除当前会话
|
|
deleteSession(req.user.id, req.token);
|
|
|
|
res.json({
|
|
data: {
|
|
message: '登出成功'
|
|
}
|
|
});
|
|
}));
|
|
|
|
// 登出所有设备
|
|
router.post('/logout-all', authenticate, asyncHandler(async (req, res) => {
|
|
// 删除用户所有会话
|
|
const result = deleteAllSessions(req.user.id);
|
|
|
|
res.json({
|
|
data: {
|
|
message: `已登出 ${result.changes} 个设备`
|
|
}
|
|
});
|
|
}));
|
|
|
|
// 修改密码
|
|
router.post('/change-password', authenticate, asyncHandler(async (req, res) => {
|
|
const { current_password, new_password } = req.body;
|
|
|
|
// 输入验证
|
|
if (!current_password || !new_password) {
|
|
throw new AppError('当前密码和新密码不能为空', 400, 'MISSING_FIELDS');
|
|
}
|
|
|
|
if (new_password.length < 6) {
|
|
throw new AppError('新密码长度至少6位', 400, 'PASSWORD_TOO_SHORT');
|
|
}
|
|
|
|
const db = getDB();
|
|
|
|
// 获取用户当前密码
|
|
const user = db.prepare('SELECT password_hash FROM users WHERE id = ?').get(req.user.id);
|
|
|
|
// 验证当前密码
|
|
const isCurrentPasswordValid = await bcrypt.compare(current_password, user.password_hash);
|
|
if (!isCurrentPasswordValid) {
|
|
throw new AppError('当前密码错误', 401, 'INVALID_CURRENT_PASSWORD');
|
|
}
|
|
|
|
// 加密新密码
|
|
const saltRounds = 12;
|
|
const newPasswordHash = await bcrypt.hash(new_password, saltRounds);
|
|
|
|
// 更新密码
|
|
const updatePassword = db.prepare('UPDATE users SET password_hash = ? WHERE id = ?');
|
|
updatePassword.run(newPasswordHash, req.user.id);
|
|
|
|
// 删除所有会话(强制重新登录)
|
|
deleteAllSessions(req.user.id);
|
|
|
|
res.json({
|
|
data: {
|
|
message: '密码修改成功,请重新登录'
|
|
}
|
|
});
|
|
}));
|
|
|
|
// 验证token有效性
|
|
router.get('/verify', authenticate, asyncHandler(async (req, res) => {
|
|
res.json({
|
|
data: {
|
|
valid: true,
|
|
user: {
|
|
id: req.user.id,
|
|
email: req.user.email
|
|
}
|
|
}
|
|
});
|
|
}));
|
|
|
|
module.exports = router; |