2025-08-31 23:29:26 +08:00
|
|
|
|
const express = require('express');
|
|
|
|
|
|
const bcrypt = require('bcryptjs');
|
|
|
|
|
|
const jwt = require('jsonwebtoken');
|
|
|
|
|
|
const validator = require('validator');
|
|
|
|
|
|
const dbConnector = require('../utils/dbConnector');
|
|
|
|
|
|
|
|
|
|
|
|
const router = express.Router();
|
|
|
|
|
|
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-09-02 23:41:32 +08:00
|
|
|
|
* @swagger
|
|
|
|
|
|
* /api/v1/auth/register:
|
|
|
|
|
|
* post:
|
|
|
|
|
|
* summary: 用户注册
|
|
|
|
|
|
* description: 创建一个新的用户账户
|
|
|
|
|
|
* tags:
|
|
|
|
|
|
* - 认证管理
|
|
|
|
|
|
* requestBody:
|
|
|
|
|
|
* required: true
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* required:
|
|
|
|
|
|
* - username
|
|
|
|
|
|
* - password
|
|
|
|
|
|
* - phone
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* username:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: "user123"
|
|
|
|
|
|
* description: 用户名
|
|
|
|
|
|
* password:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: "password123"
|
|
|
|
|
|
* description: 密码(至少6位)
|
|
|
|
|
|
* phone:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: "13800138000"
|
|
|
|
|
|
* description: 手机号
|
|
|
|
|
|
* email:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: "user@example.com"
|
|
|
|
|
|
* description: 邮箱地址
|
|
|
|
|
|
* user_type:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: "farmer"
|
|
|
|
|
|
* description: 用户类型
|
|
|
|
|
|
* responses:
|
|
|
|
|
|
* 201:
|
|
|
|
|
|
* description: 注册成功
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* code:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* example: 201
|
|
|
|
|
|
* message:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: 注册成功
|
|
|
|
|
|
* data:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* user_id:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* example: 1
|
|
|
|
|
|
* username:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: "user123"
|
|
|
|
|
|
* phone:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: "13800138000"
|
|
|
|
|
|
* email:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: "user@example.com"
|
|
|
|
|
|
* user_type:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: "farmer"
|
|
|
|
|
|
* token:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
|
|
|
|
* 400:
|
|
|
|
|
|
* description: 请求参数错误
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* code:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* example: 400
|
|
|
|
|
|
* message:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: 用户名、密码和手机号为必填项
|
|
|
|
|
|
* 409:
|
|
|
|
|
|
* description: 用户已存在
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* code:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* example: 409
|
|
|
|
|
|
* message:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: 用户名、手机号或邮箱已存在
|
|
|
|
|
|
*
|
2025-08-31 23:29:26 +08:00
|
|
|
|
* 用户注册
|
|
|
|
|
|
*/
|
|
|
|
|
|
router.post('/register', async (req, res, next) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { username, password, phone, email, user_type = 'farmer' } = req.body;
|
|
|
|
|
|
|
|
|
|
|
|
// 参数验证
|
|
|
|
|
|
if (!username || !password || !phone) {
|
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
|
code: 400,
|
|
|
|
|
|
message: '用户名、密码和手机号为必填项',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (password.length < 6) {
|
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
|
code: 400,
|
|
|
|
|
|
message: '密码长度不能少于6位',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!validator.isMobilePhone(phone, 'zh-CN')) {
|
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
|
code: 400,
|
|
|
|
|
|
message: '手机号格式不正确',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (email && !validator.isEmail(email)) {
|
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
|
code: 400,
|
|
|
|
|
|
message: '邮箱格式不正确',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查用户是否已存在
|
|
|
|
|
|
const existingUser = await dbConnector.query(
|
|
|
|
|
|
'SELECT id FROM users WHERE username = ? OR phone = ? OR email = ?',
|
|
|
|
|
|
[username, phone, email]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (existingUser.length > 0) {
|
|
|
|
|
|
return res.status(409).json({
|
|
|
|
|
|
code: 409,
|
|
|
|
|
|
message: '用户名、手机号或邮箱已存在',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 加密密码
|
|
|
|
|
|
const hashedPassword = await bcrypt.hash(password, 12);
|
|
|
|
|
|
|
|
|
|
|
|
// 创建用户
|
|
|
|
|
|
const result = await dbConnector.query(
|
2025-09-01 01:05:53 +08:00
|
|
|
|
'INSERT INTO users (username, password, phone, email, user_type) VALUES (?, ?, ?, ?, ?)',
|
2025-08-31 23:29:26 +08:00
|
|
|
|
[username, hashedPassword, phone, email, user_type]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 生成JWT token
|
|
|
|
|
|
const token = jwt.sign(
|
|
|
|
|
|
{ userId: result.insertId, username, user_type },
|
|
|
|
|
|
JWT_SECRET,
|
|
|
|
|
|
{ expiresIn: '7d' }
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
res.status(201).json({
|
|
|
|
|
|
code: 201,
|
|
|
|
|
|
message: '注册成功',
|
|
|
|
|
|
data: {
|
|
|
|
|
|
user_id: result.insertId,
|
|
|
|
|
|
username,
|
|
|
|
|
|
phone,
|
|
|
|
|
|
email,
|
|
|
|
|
|
user_type,
|
|
|
|
|
|
token
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
next(error);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-09-02 23:41:32 +08:00
|
|
|
|
* @swagger
|
|
|
|
|
|
* /api/v1/auth/login:
|
|
|
|
|
|
* post:
|
|
|
|
|
|
* summary: 用户登录
|
|
|
|
|
|
* description: 使用用户名和密码进行身份验证并获取访问令牌
|
|
|
|
|
|
* tags:
|
|
|
|
|
|
* - 认证管理
|
|
|
|
|
|
* requestBody:
|
|
|
|
|
|
* required: true
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* required:
|
|
|
|
|
|
* - username
|
|
|
|
|
|
* - password
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* username:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: "user123"
|
|
|
|
|
|
* description: 用户名
|
|
|
|
|
|
* password:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: "password123"
|
|
|
|
|
|
* description: 密码
|
|
|
|
|
|
* responses:
|
|
|
|
|
|
* 200:
|
|
|
|
|
|
* description: 登录成功
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* code:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* example: 200
|
|
|
|
|
|
* message:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: 登录成功
|
|
|
|
|
|
* data:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* user_id:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* example: 1
|
|
|
|
|
|
* username:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: "user123"
|
|
|
|
|
|
* user_type:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: "farmer"
|
|
|
|
|
|
* token:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
|
|
|
|
* 400:
|
|
|
|
|
|
* description: 请求参数错误
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* code:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* example: 400
|
|
|
|
|
|
* message:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: 用户名和密码为必填项
|
|
|
|
|
|
* 401:
|
|
|
|
|
|
* description: 用户名或密码错误
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* code:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* example: 401
|
|
|
|
|
|
* message:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* example: 用户名或密码错误
|
|
|
|
|
|
*
|
2025-08-31 23:29:26 +08:00
|
|
|
|
* 用户登录
|
|
|
|
|
|
*/
|
|
|
|
|
|
router.post('/login', async (req, res, next) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { login, password } = req.body;
|
|
|
|
|
|
|
|
|
|
|
|
if (!login || !password) {
|
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
|
code: 400,
|
|
|
|
|
|
message: '登录账号和密码为必填项',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查询用户(支持用户名、手机号、邮箱登录)
|
|
|
|
|
|
const user = await dbConnector.query(
|
|
|
|
|
|
'SELECT * FROM users WHERE (username = ? OR phone = ? OR email = ?) AND status = 1',
|
|
|
|
|
|
[login, login, login]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (user.length === 0) {
|
|
|
|
|
|
return res.status(401).json({
|
|
|
|
|
|
code: 401,
|
|
|
|
|
|
message: '用户不存在或已被禁用',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const userData = user[0];
|
|
|
|
|
|
|
|
|
|
|
|
// 验证密码
|
2025-09-01 03:42:32 +08:00
|
|
|
|
const isValidPassword = await bcrypt.compare(password, userData.password_hash);
|
2025-08-31 23:29:26 +08:00
|
|
|
|
if (!isValidPassword) {
|
|
|
|
|
|
return res.status(401).json({
|
|
|
|
|
|
code: 401,
|
|
|
|
|
|
message: '密码不正确',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新最后登录时间
|
|
|
|
|
|
await dbConnector.query(
|
|
|
|
|
|
'UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?',
|
|
|
|
|
|
[userData.id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 生成JWT token
|
|
|
|
|
|
const token = jwt.sign(
|
|
|
|
|
|
{ userId: userData.id, username: userData.username, user_type: userData.user_type },
|
|
|
|
|
|
JWT_SECRET,
|
|
|
|
|
|
{ expiresIn: '7d' }
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
code: 200,
|
|
|
|
|
|
message: '登录成功',
|
|
|
|
|
|
data: {
|
|
|
|
|
|
user_id: userData.id,
|
|
|
|
|
|
username: userData.username,
|
|
|
|
|
|
phone: userData.phone,
|
|
|
|
|
|
email: userData.email,
|
|
|
|
|
|
user_type: userData.user_type,
|
|
|
|
|
|
avatar_url: userData.avatar_url,
|
|
|
|
|
|
token
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
next(error);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取当前用户信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
router.get('/me', async (req, res, next) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 从token中获取用户ID
|
|
|
|
|
|
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
|
|
|
|
if (!token) {
|
|
|
|
|
|
return res.status(401).json({
|
|
|
|
|
|
code: 401,
|
|
|
|
|
|
message: '未提供认证token',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const decoded = jwt.verify(token, JWT_SECRET);
|
|
|
|
|
|
const user = await dbConnector.query(
|
|
|
|
|
|
'SELECT id, username, phone, email, user_type, avatar_url, created_at, last_login FROM users WHERE id = ? AND status = 1',
|
|
|
|
|
|
[decoded.userId]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (user.length === 0) {
|
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
|
code: 404,
|
|
|
|
|
|
message: '用户不存在',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
code: 200,
|
|
|
|
|
|
message: '获取成功',
|
|
|
|
|
|
data: user[0]
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (error.name === 'JsonWebTokenError') {
|
|
|
|
|
|
return res.status(401).json({
|
|
|
|
|
|
code: 401,
|
|
|
|
|
|
message: '无效的token',
|
|
|
|
|
|
data: null
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
next(error);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = router;
|