Compare commits

..

No commits in common. "adc9f2259ee1de1baeb64cf7503c98262bd0a8cc" and "e6adea5f5243a089d9312890a5e6a2f7a324287b" have entirely different histories.

13 changed files with 183 additions and 140 deletions

36
app.js
View File

@ -1,20 +1,35 @@
import createError from 'http-errors' import createError from 'http-errors'
import express from 'express' import express from 'express'
import path from 'path' import path from 'path'
import logger from 'morgan' import logger from 'morgan'
import cors from 'cors' import cors from 'cors'
import session from 'express-session' import session from 'express-session'
import { RedisStore } from 'connect-redis' import { RedisStore } from 'connect-redis'
import Redis from 'ioredis' import Redis from 'ioredis'
import { fileURLToPath } from 'url'
import cookieParser from 'cookie-parser' import cookieParser from 'cookie-parser'
import indexRouter from './routes/index.js'
import userRouter from './routes/userRouter.js' import indexRouter from './routes/index'
import accountRouter from './routes/accountRouter.js'
import userRouter from './routes/userRouter'
import accountRouter from './routes/accountRouter'
import { config } from 'dotenv' import { config } from 'dotenv'
import { connectMongoDB } from './config/mongodbConfig.js'
import { HTTP_STATUS } from './common/constant/httpStatus.js' // mongodb数据库连接
import { authenticateSession } from './utils/loginUtil.js' import { connectMongoDB } from './config/mongodbConfig'
import { HTTP_STATUS } from './common/constant/httpStatus'
import loginUtil from './utils/loginUtil'
config() config()
// 初始化 Redis 客户端 // 初始化 Redis 客户端
@ -27,9 +42,6 @@ const redisClient = new Redis({
const app = express() const app = express()
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
app.use(logger('dev')) app.use(logger('dev'))
app.use(express.json()) app.use(express.json())
app.use(express.urlencoded({ extended: false })) app.use(express.urlencoded({ extended: false }))
@ -69,7 +81,7 @@ async function startServer() {
startServer().then((r) => logger(r)) startServer().then((r) => logger(r))
app.use('/', indexRouter) app.use('/', indexRouter)
app.use('/user', authenticateSession, userRouter) app.use('/user', loginUtil.authenticateSession, userRouter)
app.use('/account', accountRouter) app.use('/account', accountRouter)
// catch 404 and forward to error handler // catch 404 and forward to error handler
@ -93,4 +105,4 @@ app.use(function (err, req, res) {
}) })
}) })
export default app module.exports = app

View File

@ -3,9 +3,9 @@
/** /**
* Module dependencies. * Module dependencies.
*/ */
import app from '../app.js' const app = require('../app')
import debug from 'debug' const debug = require('debug')('express:server')
import http from 'http' const http = require('http')
/** /**
* Get port from environment and store in Express. * Get port from environment and store in Express.

View File

@ -66,3 +66,5 @@ export const HTTP_STATUS = {
/** @type {number} 网关超时 */ /** @type {number} 网关超时 */
GATEWAY_TIMEOUT: 504 GATEWAY_TIMEOUT: 504
} }
export default { HTTP_STATUS }

View File

@ -1,4 +1,5 @@
import mongoose from 'mongoose' import mongoose from 'mongoose'
import logger from 'morgan' import logger from 'morgan'
import { config } from 'dotenv' import { config } from 'dotenv'
@ -15,7 +16,7 @@ let isConnected = false
export async function connectMongoDB() { export async function connectMongoDB() {
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
logger({ account, password, host, port }) logger(account, password, host, port)
} }
// 如果已经连接,不再重复连接 // 如果已经连接,不再重复连接

View File

@ -1,12 +1,12 @@
import { body, validationResult } from 'express-validator' import { body, validationResult } from 'express-validator'
import logger from 'morgan' import logger from 'morgan'
import userService from '../services/userService.js' import userService from '../services/userService'
import FetchResult from '../common/web/fetchResult.js' import FetchResult from '../common/web/fetchResult'
import messages from '../config/messages.js' import messages from '../config/messages'
import { HTTP_STATUS } from '../common/constant/httpStatus.js' import { HTTP_STATUS } from '../common/constant/httpStatus'
import { SearchQuery } from '../models/search.js' import { SearchQuery } from '../models/search'
const UserController = { export default UserController = {
async getAllUsers(res) { async getAllUsers(res) {
try { try {
const users = await userService.getUserList() const users = await userService.getUserList()
@ -15,6 +15,7 @@ const UserController = {
return FetchResult.formatResult(res, HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Internal server error') return FetchResult.formatResult(res, HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Internal server error')
} }
}, },
async findUserList(req, res) { async findUserList(req, res) {
try { try {
const { page, size, sort } = req.query const { page, size, sort } = req.query
@ -27,6 +28,7 @@ const UserController = {
return FetchResult.formatResult(res, HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Internal server error') return FetchResult.formatResult(res, HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Internal server error')
} }
}, },
createUser: [ createUser: [
body('account').isLength({ min: 3 }).withMessage('Account must be at least 3 characters long'), body('account').isLength({ min: 3 }).withMessage('Account must be at least 3 characters long'),
body('account').isEmpty().withMessage('Account is required'), body('account').isEmpty().withMessage('Account is required'),
@ -61,6 +63,7 @@ const UserController = {
} }
} }
], ],
login: [ login: [
body('account').notEmpty().withMessage('Account is required'), body('account').notEmpty().withMessage('Account is required'),
body('password').notEmpty().withMessage('Password is required'), body('password').notEmpty().withMessage('Password is required'),
@ -96,6 +99,7 @@ const UserController = {
} }
} }
], ],
logout: [ logout: [
async (req, res) => { async (req, res) => {
try { try {
@ -107,6 +111,7 @@ const UserController = {
} }
} }
], ],
async getUserExists(req, res) { async getUserExists(req, res) {
try { try {
const { account } = req.query const { account } = req.query
@ -121,7 +126,5 @@ const UserController = {
logger('Error checking user existence: ', err) logger('Error checking user existence: ', err)
return FetchResult.formatResult(res, HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Internal server error') return FetchResult.formatResult(res, HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Internal server error')
} }
}, }
} }
export default UserController

View File

@ -7,7 +7,7 @@ export class SearchQuery {
} }
} }
export class SearchResult { class SearchResult {
constructor({ list = [], num = 0, size = 0, total = 0 } = {}) { constructor({ list = [], num = 0, size = 0, total = 0 } = {}) {
if (typeof num !== 'number') { if (typeof num !== 'number') {
throw new Error('Invalid num parameter') throw new Error('Invalid num parameter')
@ -25,3 +25,5 @@ export class SearchResult {
this.total = total this.total = total
} }
} }
export default { SearchQuery, SearchResult }

View File

@ -1,5 +1,5 @@
import { Schema as _Schema, model } from 'mongoose' import { Schema as _Schema, model } from 'mongoose'
import { hashPassword } from '../utils/hashUtils.js' import { hashPassword } from '../utils/hashUtils'
const Schema = _Schema const Schema = _Schema
const UserSchema = new Schema({ const UserSchema = new Schema({
@ -7,12 +7,12 @@ const UserSchema = new Schema({
gender: { type: String, enum: ['male', 'female', 'other'], maxlength: 20 }, gender: { type: String, enum: ['male', 'female', 'other'], maxlength: 20 },
birth: { type: Date }, birth: { type: Date },
avatar: { type: String, maxlength: 100 }, avatar: { type: String, maxlength: 100 },
account: { type: String, required: true, unique: true, maxlength: 100 }, account: { type: String, required: true, unique: true, maxlength: 100, index: true },
password: { type: String, required: true, maxlength: 100 }, password: { type: String, required: true, maxlength: 100 },
email: { type: String, match: /^\S+@\S+\.\S+$/, maxlength: 255, index: true }, email: { type: String, maxlength: 255, index: true },
phone: { type: String, match: /^1[3-9]\d{9}$/, maxlength: 11 }, phone: { type: String, maxlength: 11 },
registerDate: { type: Date, default: Date.now }, registerDate: { type: Date, default: Date.now },
lastLoginDate: { type: Date }, lastLoginDate: { type: Date, default: Date.now },
status: { type: String, enum: ['active', 'inactive', 'pending'], default: 'pending' } status: { type: String, enum: ['active', 'inactive', 'pending'], default: 'pending' }
}) })
@ -20,18 +20,13 @@ UserSchema.pre('save', async function (next) {
const user = this const user = this
if (!user.isModified('password')) return next() if (!user.isModified('password')) return next()
try { try {
console.log(user.password)
user.password = await hashPassword(user.password) user.password = await hashPassword(user.password)
next() next()
} catch (error) { } catch (error) {
// 记录错误日志,避免泄露敏感信息 next(error)
console.error('Error hashing password:', error.message)
next(new Error('Failed to hash password'))
} }
}) })
// 更新 lastLoginDate 在用户登录时
UserSchema.methods.updateLastLoginDate = function () {
this.lastLoginDate = new Date()
}
export default model('User', UserSchema) export default model('User', UserSchema)

View File

@ -1,93 +1,114 @@
import User from '../models/userModel.js' import {
import { SearchResult } from '../models/search.js' startSession,
find,
aggregate,
findById,
findOne,
exists,
create,
findByIdAndUpdate,
findByIdAndDelete,
deleteMany
} from '../models/userModel'
import { SearchResult } from '../models/search'
const userRepository = { export async function startTransaction() {
async startTransaction() { const session = await startSession()
const session = await User.startSession() session.startTransaction()
session.startTransaction() return session
return session }
},
async commitTransaction(session) {
return session.commitTransaction()
},
async rollbackTransaction(session) {
return session.abortTransaction()
},
async selectAllUser() {
return User.find()
},
async selectUserList(search = {}) {
try {
const { size = 20, page = 1, sort, filters } = search
const skip = (page - 1) * size export async function commitTransaction(session) {
const searchResult = new SearchResult() return session.commitTransaction()
searchResult.num = page }
searchResult.size = size
// 检查 filters确保只有在 filters 存在时才应用 export async function rollbackTransaction(session) {
const matchFilters = filters ? { ...filters } : {} return session.abortTransaction()
}
const sortObj = sort && typeof sort === 'object' ? sort : { _id: 1 } export async function selectAllUser() {
return find()
}
const result = await User.aggregate([ export async function selectUserList(search = {}) {
// 应用过滤条件确保filters存在时才传入 try {
{ $match: matchFilters }, const { size = 20, page = 1, sort, filters } = search
{
$facet: { const skip = (page - 1) * size
metadata: [{ $count: 'total' }, { $addFields: { total: '$total' } }], const searchResult = new SearchResult()
data: [{ $sort: sortObj }, { $skip: skip }, { $limit: size }] searchResult.num = page
} searchResult.size = size
// 检查 filters确保只有在 filters 存在时才应用
const matchFilters = filters ? { ...filters } : {}
const sortObj = sort && typeof sort === 'object' ? sort : { _id: 1 } // 默认按 _id 升序排序
const result = await aggregate([
// 应用过滤条件确保filters存在时才传入
{ $match: matchFilters },
{
$facet: {
metadata: [{ $count: 'total' }, { $addFields: { total: '$total' } }],
data: [{ $sort: sortObj }, { $skip: skip }, { $limit: size }]
} }
]) }
])
// 解构 result 并进行必要的空值检查 // 解构 result 并进行必要的空值检查
const { metadata = [], data = [] } = result[0] || {} const { metadata = [], data = [] } = result[0] || {}
// 提取 metadata 中的 total若不存在则为 0 // 提取 metadata 中的 total若不存在则为 0
searchResult.total = metadata.length > 0 ? metadata[0].total : 0 searchResult.total = metadata.length > 0 ? metadata[0].total : 0
searchResult.list = data searchResult.list = data
return searchResult return searchResult
} catch (error) { } catch (error) {
console.log(error) console.log(error)
throw error throw error
}
},
async selectUserById(id) {
return User.findById(id)
},
async selectUserByAccount(account) {
return User.findOne({ account: account })
},
async selectUserByEmail(email) {
return User.findOne({ email: email })
},
async selectUserByUsername(username) {
return User.findOne({ username: username })
},
async selectUserByPhone(phone) {
return User.findOne({ phone: phone })
},
async selectUserByAccountExist(account) {
const exist = await User.exists({ account: account })
return exist !== null
},
async createUser(user) {
return User.create(user)
},
async updateUserById(id, user) {
return User.findByIdAndUpdate(id, user)
},
async updateUserByLoginDate(id, loginDate) {
return User.findByIdAndUpdate(id, { lastLoginDate: loginDate })
},
async deleteUserById(id) {
return User.findByIdAndDelete(id)
},
async deleteAllUser() {
return User.deleteMany()
} }
} }
export default userRepository export async function selectUserById(id) {
return findById(id)
}
export async function selectUserByAccount(account) {
return findOne({ account: account })
}
export async function selectUserByEmail(email) {
return findOne({ email: email })
}
export async function selectUserByUsername(username) {
return findOne({ username: username })
}
export async function selectUserByPhone(phone) {
return findOne({ phone: phone })
}
export async function selectUserByAccountExist(account) {
const exist = await exists({ account: account })
return exist !== null
}
export async function createUser(user) {
return create(user)
}
export async function updateUserById(id, user) {
return findByIdAndUpdate(id, user)
}
export async function updateUserByLoginDate(id, loginDate) {
return findByIdAndUpdate(id, { last_login_date: loginDate })
}
export async function deleteUserById(id) {
return findByIdAndDelete(id)
}
export async function deleteAllUser() {
return deleteMany()
}

View File

@ -1,8 +1,9 @@
// routes/userRoutes.js
import express from 'express' import express from 'express'
import userController from '../controllers/userController.js' const userController = import('../controllers/userController')
const router = express.Router() const router = express.Router()
router.get('/', userController.getUserExists) router.get('/', getUserExists)
export default router export default router

View File

@ -1,6 +1,6 @@
import express from 'express' import express from 'express'
import userController from '../controllers/userController.js' import userController from '../controllers/userController'
const router = express.Router() const router = express.Router()
router.get('/', function (req, res) { router.get('/', function (req, res) {

View File

@ -1,5 +1,7 @@
// routes/userRoutes.js
import userController from '../controllers/userController'
import express from 'express' import express from 'express'
import userController from '../controllers/userController.js'
const router = express.Router() const router = express.Router()

View File

@ -1,9 +1,9 @@
import userRepository from '../repositories/userRepository.js' import userMapper from '../repositories/userRepository'
import logger from 'morgan' import logger from 'morgan'
import messages from '../config/messages.js' import messages from '../config/messages'
import { comparePassword } from '../utils/hashUtils.js' import { comparePassword } from '../utils/hashUtils'
const userService = { export default userServeice = {
/** /**
* 用户登录 * 用户登录
* @param {string} account - 用户账号 * @param {string} account - 用户账号
@ -12,7 +12,7 @@ const userService = {
* @throws {Error} - 如果用户不存在或密码不正确 * @throws {Error} - 如果用户不存在或密码不正确
*/ */
async login(account, password) { async login(account, password) {
const user = await userRepository.selectUserByAccount(account) const user = await userMapper.selectUserByAccount(account)
// 用户不存在 // 用户不存在
if (!user) { if (!user) {
@ -25,6 +25,10 @@ const userService = {
throw new Error(messages.user.passwordIncorrect) throw new Error(messages.user.passwordIncorrect)
} }
// 更新用户的最后登录时间
user.lastLoginDate = new Date()
await userMapper.updateUserByLoginDate(user.id, user.lastLoginDate)
return user return user
}, },
@ -34,16 +38,16 @@ const userService = {
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
async getUserExists(account) { async getUserExists(account) {
return userRepository.selectUserByAccountExist(account) return userMapper.selectUserByAccountExist(account)
}, },
// Removed the unnecessary try-catch block and simplified the function // Removed the unnecessary try-catch block and simplified the function
async getAllUser() { async getAllUser() {
return await userRepository.selectAllUser() return await userMapper.selectAllUser()
}, },
async getUserList(searchQuery) { async getUserList(searchQuery) {
return userRepository.selectUserList(searchQuery) return userMapper.selectUserList(searchQuery)
}, },
/** /**
@ -54,29 +58,27 @@ const userService = {
*/ */
async createUser(user) { async createUser(user) {
const { account } = user const { account } = user
const session = await userRepository.startTransaction() const session = await userMapper.startTransaction()
try { try {
// 检查用户是否已存在 // 检查用户是否已存在
const existingUser = await userRepository.selectUserByAccount(account) const existingUser = await userMapper.selectUserByAccount(account)
if (existingUser) { if (existingUser) {
return new Error(messages.user.alreadyExists) return new Error(messages.user.alreadyExists)
} }
// 创建新用户 // 创建新用户
const result = await userRepository.createUser(user) const result = await userMapper.createUser(user)
// 提交事务 // 提交事务
await userRepository.commitTransaction(session) await userMapper.commitTransaction(session)
return result return result
} catch (err) { } catch (err) {
// 回滚事务 // 回滚事务
await userRepository.rollbackTransaction(session).catch((rollbackErr) => { await userMapper.rollbackTransaction(session).catch((rollbackErr) => {
logger('Error rolling back transaction: ', rollbackErr) logger('Error rolling back transaction: ', rollbackErr)
}) })
throw err // 将错误抛给调用方Controller 层) throw err // 将错误抛给调用方Controller 层)
} }
} }
} }
export default userService

View File

@ -5,7 +5,7 @@ import bcrypt from 'bcrypt'
* @param {string} password 密码 * @param {string} password 密码
* @returns password 加密后的密码 * @returns password 加密后的密码
*/ */
export async function hashPassword(password) { async function hashPassword(password) {
return bcrypt.hash(password, 10) return bcrypt.hash(password, 10)
} }
@ -18,3 +18,5 @@ export async function hashPassword(password) {
export async function comparePassword(password, hashedPassword) { export async function comparePassword(password, hashedPassword) {
return bcrypt.compare(password, hashedPassword) return bcrypt.compare(password, hashedPassword)
} }
export default { hashPassword, comparePassword }