feat: add auto-increment userId (8-digit padded) to User model and login response
This commit is contained in:
+98
-93
@@ -1,93 +1,98 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const userSchema = new mongoose.Schema({
|
||||
openid: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
index: true
|
||||
},
|
||||
unionid: {
|
||||
type: String,
|
||||
sparse: true,
|
||||
index: true
|
||||
},
|
||||
nickname: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
avatarUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
phoneNumber: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
profile: {
|
||||
gender: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
country: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
province: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
city: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: ['active', 'inactive', 'banned'],
|
||||
default: 'active'
|
||||
},
|
||||
lastLoginAt: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
},
|
||||
isVip: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
vipExpireAt: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
ocrCount: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
ocrCountTotal: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
ocrCountResetAt: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
},
|
||||
platformLimit: {
|
||||
type: Number,
|
||||
default: 15
|
||||
},
|
||||
platformCount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
}, {
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
userSchema.pre('save', function(next) {
|
||||
if (this.isModified('lastLoginAt')) {
|
||||
this.lastLoginAt = Date.now();
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('User', userSchema);
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const userSchema = new mongoose.Schema({
|
||||
openid: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
index: true
|
||||
},
|
||||
unionid: {
|
||||
type: String,
|
||||
sparse: true,
|
||||
index: true
|
||||
},
|
||||
userId: {
|
||||
type: String,
|
||||
unique: true,
|
||||
index: true
|
||||
},
|
||||
nickname: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
avatarUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
phoneNumber: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
profile: {
|
||||
gender: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
country: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
province: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
city: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: ['active', 'inactive', 'banned'],
|
||||
default: 'active'
|
||||
},
|
||||
lastLoginAt: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
},
|
||||
isVip: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
vipExpireAt: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
ocrCount: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
ocrCountTotal: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
ocrCountResetAt: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
},
|
||||
platformLimit: {
|
||||
type: Number,
|
||||
default: 15
|
||||
},
|
||||
platformCount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
}, {
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
userSchema.pre('save', function(next) {
|
||||
if (this.isModified('lastLoginAt')) {
|
||||
this.lastLoginAt = Date.now();
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('User', userSchema);
|
||||
|
||||
+149
-129
@@ -1,129 +1,149 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const axios = require('axios');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const User = require('../models/User');
|
||||
|
||||
router.post('/wechat-login', async (req, res, next) => {
|
||||
try {
|
||||
const { code, userInfo } = req.body;
|
||||
|
||||
if (!code) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '缺少微信登录code'
|
||||
});
|
||||
}
|
||||
|
||||
const appid = process.env.WECHAT_APPID;
|
||||
const secret = process.env.WECHAT_APPSECRET;
|
||||
|
||||
const wxResponse = await axios.get('https://api.weixin.qq.com/sns/jscode2session', {
|
||||
params: {
|
||||
appid,
|
||||
secret,
|
||||
js_code: code,
|
||||
grant_type: 'authorization_code'
|
||||
}
|
||||
});
|
||||
|
||||
const { openid, session_key, unionid, errcode, errmsg } = wxResponse.data;
|
||||
|
||||
if (errcode) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: `微信登录失败: ${errmsg}`,
|
||||
wxErrorCode: errcode
|
||||
});
|
||||
}
|
||||
|
||||
let user = await User.findOne({ openid });
|
||||
|
||||
if (!user) {
|
||||
user = await User.create({
|
||||
openid,
|
||||
unionid: unionid || undefined,
|
||||
nickname: userInfo?.nickName || '',
|
||||
avatarUrl: userInfo?.avatarUrl || '',
|
||||
profile: {
|
||||
gender: userInfo?.gender || 0,
|
||||
country: userInfo?.country || '',
|
||||
province: userInfo?.province || '',
|
||||
city: userInfo?.city || ''
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (userInfo) {
|
||||
user.nickname = userInfo.nickName || user.nickname;
|
||||
user.avatarUrl = userInfo.avatarUrl || user.avatarUrl;
|
||||
user.lastLoginAt = new Date();
|
||||
await user.save();
|
||||
}
|
||||
}
|
||||
|
||||
const token = jwt.sign(
|
||||
{ id: user._id, openid: user.openid },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
token,
|
||||
user: {
|
||||
id: user._id,
|
||||
nickname: user.nickname,
|
||||
avatarUrl: user.avatarUrl,
|
||||
status: user.status,
|
||||
isVip: user.isVip,
|
||||
vipExpireAt: user.vipExpireAt,
|
||||
ocrCount: user.ocrCount,
|
||||
ocrCountTotal: user.ocrCountTotal,
|
||||
platformLimit: user.platformLimit,
|
||||
platformCount: user.platformCount
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/refresh-token', async (req, res, next) => {
|
||||
try {
|
||||
const { token } = req.body;
|
||||
|
||||
if (!token) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '缺少token'
|
||||
});
|
||||
}
|
||||
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET, { ignoreExpiration: true });
|
||||
const user = await User.findById(decoded.id);
|
||||
|
||||
if (!user || user.status !== 'active') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: '用户不存在或已被禁用'
|
||||
});
|
||||
}
|
||||
|
||||
const newToken = jwt.sign(
|
||||
{ id: user._id, openid: user.openid },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { token: newToken }
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const axios = require('axios');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const User = require('../models/User');
|
||||
|
||||
async function generateUserId() {
|
||||
const lastUser = await User.findOne({ userId: { $exists: true } })
|
||||
.sort({ userId: -1 })
|
||||
.select('userId')
|
||||
.lean();
|
||||
|
||||
let nextNumber = 1;
|
||||
if (lastUser && lastUser.userId) {
|
||||
const lastNumber = parseInt(lastUser.userId, 10);
|
||||
if (!isNaN(lastNumber)) {
|
||||
nextNumber = lastNumber + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return nextNumber.toString().padStart(8, '0');
|
||||
}
|
||||
|
||||
router.post('/wechat-login', async (req, res, next) => {
|
||||
try {
|
||||
const { code, userInfo } = req.body;
|
||||
|
||||
if (!code) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '缺少微信登录code'
|
||||
});
|
||||
}
|
||||
|
||||
const appid = process.env.WECHAT_APPID;
|
||||
const secret = process.env.WECHAT_APPSECRET;
|
||||
|
||||
const wxResponse = await axios.get('https://api.weixin.qq.com/sns/jscode2session', {
|
||||
params: {
|
||||
appid,
|
||||
secret,
|
||||
js_code: code,
|
||||
grant_type: 'authorization_code'
|
||||
}
|
||||
});
|
||||
|
||||
const { openid, session_key, unionid, errcode, errmsg } = wxResponse.data;
|
||||
|
||||
if (errcode) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: `微信登录失败: ${errmsg}`,
|
||||
wxErrorCode: errcode
|
||||
});
|
||||
}
|
||||
|
||||
let user = await User.findOne({ openid });
|
||||
|
||||
if (!user) {
|
||||
const userId = await generateUserId();
|
||||
|
||||
user = await User.create({
|
||||
openid,
|
||||
unionid: unionid || undefined,
|
||||
userId,
|
||||
nickname: userInfo?.nickName || '',
|
||||
avatarUrl: userInfo?.avatarUrl || '',
|
||||
profile: {
|
||||
gender: userInfo?.gender || 0,
|
||||
country: userInfo?.country || '',
|
||||
province: userInfo?.province || '',
|
||||
city: userInfo?.city || ''
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (userInfo) {
|
||||
user.nickname = userInfo.nickName || user.nickname;
|
||||
user.avatarUrl = userInfo.avatarUrl || user.avatarUrl;
|
||||
user.lastLoginAt = new Date();
|
||||
await user.save();
|
||||
}
|
||||
}
|
||||
|
||||
const token = jwt.sign(
|
||||
{ id: user._id, openid: user.openid },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
token,
|
||||
user: {
|
||||
userId: user.userId,
|
||||
nickname: user.nickname,
|
||||
avatarUrl: user.avatarUrl,
|
||||
status: user.status,
|
||||
isVip: user.isVip,
|
||||
vipExpireAt: user.vipExpireAt,
|
||||
ocrCount: user.ocrCount,
|
||||
ocrCountTotal: user.ocrCountTotal,
|
||||
platformLimit: user.platformLimit,
|
||||
platformCount: user.platformCount
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/refresh-token', async (req, res, next) => {
|
||||
try {
|
||||
const { token } = req.body;
|
||||
|
||||
if (!token) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '缺少token'
|
||||
});
|
||||
}
|
||||
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET, { ignoreExpiration: true });
|
||||
const user = await User.findById(decoded.id);
|
||||
|
||||
if (!user || user.status !== 'active') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: '用户不存在或已被禁用'
|
||||
});
|
||||
}
|
||||
|
||||
const newToken = jwt.sign(
|
||||
{ id: user._id, openid: user.openid },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { token: newToken }
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user