Files
pdf-tools/server/models/User.js

249 lines
5.5 KiB
JavaScript

const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const userSchema = new mongoose.Schema({
userId: {
type: String,
required: true,
unique: true,
index: true
},
email: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true,
match: [/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/, '请输入有效的邮箱地址']
},
username: {
type: String,
required: true,
trim: true,
minlength: 2,
maxlength: 50
},
password: {
type: String,
required: true,
minlength: 6
},
settings: {
defaultOutputFormat: {
type: String,
enum: ['docx', 'html', 'txt', 'png', 'jpg'],
default: 'docx'
},
imageQuality: {
type: String,
enum: ['low', 'medium', 'high'],
default: 'medium'
},
autoDownload: {
type: Boolean,
default: true
},
language: {
type: String,
enum: ['zh-CN', 'en-US'],
default: 'zh-CN'
},
autoDelete: {
type: Boolean,
default: true
},
deleteDelay: {
type: Number,
default: 24, // 小时
min: 1,
max: 168 // 7天
},
maxConcurrentTasks: {
type: Number,
default: 3,
min: 1,
max: 10
},
conversionTimeout: {
type: Number,
default: 10, // 分钟
min: 5,
max: 60
}
},
profile: {
avatar: String,
bio: String,
company: String,
website: String
},
statistics: {
totalConversions: {
type: Number,
default: 0
},
successfulConversions: {
type: Number,
default: 0
},
failedConversions: {
type: Number,
default: 0
},
totalFileSize: {
type: Number,
default: 0 // 字节
}
},
lastLoginAt: {
type: Date,
default: null
},
lastActiveAt: {
type: Date,
default: Date.now
},
isActive: {
type: Boolean,
default: true
},
isEmailVerified: {
type: Boolean,
default: false
},
emailVerificationToken: String,
passwordResetToken: String,
passwordResetExpires: Date
}, {
timestamps: true,
toJSON: {
transform: function(doc, ret) {
delete ret.password;
delete ret.emailVerificationToken;
delete ret.passwordResetToken;
delete ret.passwordResetExpires;
delete ret.__v;
return ret;
}
}
});
// 索引
userSchema.index({ email: 1 });
userSchema.index({ username: 1 });
userSchema.index({ createdAt: -1 });
userSchema.index({ lastActiveAt: -1 });
// 密码加密中间件
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
try {
const salt = await bcrypt.genSalt(12);
this.password = await bcrypt.hash(this.password, salt);
next();
} catch (error) {
next(error);
}
});
// 实例方法:验证密码
userSchema.methods.comparePassword = async function(candidatePassword) {
try {
return await bcrypt.compare(candidatePassword, this.password);
} catch (error) {
throw error;
}
};
// 实例方法:更新最后活动时间
userSchema.methods.updateLastActive = function() {
this.lastActiveAt = new Date();
return this.save();
};
// 实例方法:更新统计信息
userSchema.methods.updateStatistics = function(update) {
Object.assign(this.statistics, update);
return this.save();
};
// 静态方法:根据邮箱查找用户
userSchema.statics.findByEmail = function(email) {
return this.findOne({ email: email.toLowerCase() });
};
// 静态方法:获取活跃用户
userSchema.statics.getActiveUsers = function(days = 30) {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - days);
return this.find({
lastActiveAt: { $gte: cutoffDate },
isActive: true
});
};
// 静态方法:获取用户统计
userSchema.statics.getUserStats = async function() {
const pipeline = [
{
$group: {
_id: null,
totalUsers: { $sum: 1 },
activeUsers: {
$sum: {
$cond: [{ $eq: ['$isActive', true] }, 1, 0]
}
},
verifiedUsers: {
$sum: {
$cond: [{ $eq: ['$isEmailVerified', true] }, 1, 0]
}
},
totalConversions: { $sum: '$statistics.totalConversions' },
totalFileSize: { $sum: '$statistics.totalFileSize' }
}
}
];
const result = await this.aggregate(pipeline);
return result[0] || {
totalUsers: 0,
activeUsers: 0,
verifiedUsers: 0,
totalConversions: 0,
totalFileSize: 0
};
};
// 虚拟字段:转换成功率
userSchema.virtual('conversionSuccessRate').get(function() {
if (this.statistics.totalConversions === 0) return 0;
return (this.statistics.successfulConversions / this.statistics.totalConversions * 100).toFixed(1);
});
// 虚拟字段:格式化文件大小
userSchema.virtual('formattedFileSize').get(function() {
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
if (this.statistics.totalFileSize === 0) return '0 Bytes';
const i = Math.floor(Math.log(this.statistics.totalFileSize) / Math.log(1024));
return (this.statistics.totalFileSize / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
});
// 中间件:删除前清理关联数据
userSchema.pre('remove', async function(next) {
try {
// 这里可以添加删除用户相关文件和转换记录的逻辑
// await FileModel.deleteMany({ userId: this.userId });
// await ConversionTaskModel.deleteMany({ userId: this.userId });
next();
} catch (error) {
next(error);
}
});
const User = mongoose.model('User', userSchema);
module.exports = User;