From 9f2f9ba7f65492d69e640d18148e0ce52b8c62a8 Mon Sep 17 00:00:00 2001 From: Developer Date: Wed, 13 May 2026 20:41:58 +0800 Subject: [PATCH] feat: add auto-increment userId (8-digit padded) to User model and login response --- src/models/User.js | 191 ++++++++++++++++--------------- src/routes/auth.js | 278 ++++++++++++++++++++++++--------------------- 2 files changed, 247 insertions(+), 222 deletions(-) diff --git a/src/models/User.js b/src/models/User.js index 0129761..0f322e8 100644 --- a/src/models/User.js +++ b/src/models/User.js @@ -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); diff --git a/src/routes/auth.js b/src/routes/auth.js index 13fcf05..3b8e1a1 100644 --- a/src/routes/auth.js +++ b/src/routes/auth.js @@ -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;