feat: add auto-increment userId (8-digit padded) to User model and login response

This commit is contained in:
Developer
2026-05-13 20:41:58 +08:00
parent 67e7c251a6
commit 9f2f9ba7f6
2 changed files with 247 additions and 222 deletions
+98 -93
View File
@@ -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
View File
@@ -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;