后端开发与集成
💡 核心结论
小程序后端需要处理用户登录、数据存储、业务逻辑等
RESTful API设计要遵循REST规范,返回统一格式
JWT Token是常用的身份认证方案
数据库设计要考虑性能、扩展性和数据一致性
安全措施包括HTTPS、参数校验、SQL注入防护等
1. 后端架构设计
1.1 技术选型
Node.js + Express:
// 适合快速开发,生态丰富
const express = require('express')
const app = express()
app.use(express.json())
app.listen(3000)
Node.js + Koa:
// 更轻量,支持async/await
const Koa = require('koa')
const app = new Koa()
app.use(async (ctx) => {
ctx.body = 'Hello World'
})
app.listen(3000)
Python + Flask:
# 简洁易用
from flask import Flask
app = Flask(__name__)
@app.route('/api/products')
def get_products():
return {'list': []}
Go + Gin:
// 高性能,适合高并发
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/api/products", getProducts)
r.Run(":3000")
}
1.2 项目结构
backend/
├── src/
│ ├── controllers/ # 控制器
│ │ ├── user.js
│ │ └── product.js
│ ├── models/ # 数据模型
│ │ ├── user.js
│ │ └── product.js
│ ├── services/ # 业务逻辑
│ │ ├── auth.js
│ │ └── order.js
│ ├── middleware/ # 中间件
│ │ ├── auth.js
│ │ └── error.js
│ ├── utils/ # 工具函数
│ │ ├── response.js
│ │ └── validator.js
│ ├── routes/ # 路由
│ │ ├── index.js
│ │ └── api.js
│ └── app.js # 入口文件
├── config/ # 配置文件
│ └── database.js
├── tests/ # 测试
└── package.json
2. 用户登录与认证
2.1 微信登录流程
// controllers/auth.js
const axios = require('axios')
const jwt = require('jsonwebtoken')
const User = require('../models/user')
class AuthController {
// 微信登录
async wechatLogin(req, res) {
const { code } = req.body
if (!code) {
return res.json({
code: 400,
message: 'code不能为空'
})
}
try {
// 1. 用code换取openid和session_key
const wechatRes = await axios.get('https://api.weixin.qq.com/sns/jscode2session', {
params: {
appid: process.env.WECHAT_APPID,
secret: process.env.WECHAT_SECRET,
js_code: code,
grant_type: 'authorization_code'
}
})
const { openid, session_key, errcode, errmsg } = wechatRes.data
if (errcode) {
return res.json({
code: 400,
message: errmsg || '登录失败'
})
}
// 2. 查询或创建用户
let user = await User.findOne({ openid })
if (!user) {
user = await User.create({
openid,
sessionKey: session_key,
createdAt: new Date()
})
} else {
// 更新session_key
user.sessionKey = session_key
await user.save()
}
// 3. 生成JWT token
const token = jwt.sign(
{ userId: user._id, openid },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
)
res.json({
code: 0,
data: {
token,
userId: user._id,
userInfo: {
nickname: user.nickname,
avatar: user.avatar
}
}
})
} catch (error) {
console.error('登录失败', error)
res.json({
code: 500,
message: '登录失败,请重试'
})
}
}
// 获取用户信息
async getUserInfo(req, res) {
const { userId } = req.user
try {
const user = await User.findById(userId)
if (!user) {
return res.json({
code: 404,
message: '用户不存在'
})
}
res.json({
code: 0,
data: {
id: user._id,
nickname: user.nickname,
avatar: user.avatar,
phone: user.phone
}
})
} catch (error) {
res.json({
code: 500,
message: '获取用户信息失败'
})
}
}
// 更新用户信息
async updateUserInfo(req, res) {
const { userId } = req.user
const { nickname, avatar, phone } = req.body
try {
const user = await User.findById(userId)
if (nickname) user.nickname = nickname
if (avatar) user.avatar = avatar
if (phone) user.phone = phone
await user.save()
res.json({
code: 0,
message: '更新成功'
})
} catch (error) {
res.json({
code: 500,
message: '更新失败'
})
}
}
}
module.exports = new AuthController()
2.2 JWT认证中间件
// middleware/auth.js
const jwt = require('jsonwebtoken')
const User = require('../models/user')
async function authMiddleware(req, res, next) {
try {
// 从header获取token
const token = req.headers.authorization?.replace('Bearer ', '')
if (!token) {
return res.json({
code: 401,
message: '未登录'
})
}
// 验证token
const decoded = jwt.verify(token, process.env.JWT_SECRET)
// 查询用户
const user = await User.findById(decoded.userId)
if (!user) {
return res.json({
code: 401,
message: '用户不存在'
})
}
// 将用户信息挂载到request
req.user = {
userId: user._id,
openid: user.openid
}
next()
} catch (error) {
if (error.name === 'TokenExpiredError') {
return res.json({
code: 401,
message: 'Token已过期'
})
}
res.json({
code: 401,
message: 'Token无效'
})
}
}
module.exports = authMiddleware
2.3 路由配置
// routes/api.js
const express = require('express')
const router = express.Router()
const authMiddleware = require('../middleware/auth')
const authController = require('../controllers/auth')
// 登录(不需要认证)
router.post('/auth/login', authController.wechatLogin)
// 需要认证的路由
router.get('/auth/user', authMiddleware, authController.getUserInfo)
router.put('/auth/user', authMiddleware, authController.updateUserInfo)
module.exports = router
3. API设计
3.1 RESTful规范
// GET /api/products # 获取商品列表
// GET /api/products/:id # 获取商品详情
// POST /api/products # 创建商品
// PUT /api/products/:id # 更新商品
// DELETE /api/products/:id # 删除商品
3.2 统一响应格式
// utils/response.js
class Response {
static success(data, message = '成功') {
return {
code: 0,
message,
data
}
}
static error(code = 500, message = '失败', data = null) {
return {
code,
message,
data
}
}
static pagination(list, total, page, size) {
return {
code: 0,
message: '成功',
data: {
list,
pagination: {
total,
page,
size,
totalPages: Math.ceil(total / size)
}
}
}
}
}
module.exports = Response
3.3 商品API示例
// controllers/product.js
const Product = require('../models/product')
const Response = require('../utils/response')
class ProductController {
// 获取商品列表
async getList(req, res) {
try {
const { page = 1, size = 10, category, keyword } = req.query
const query = {}
if (category) query.category = category
if (keyword) {
query.$or = [
{ name: new RegExp(keyword, 'i') },
{ description: new RegExp(keyword, 'i') }
]
}
const skip = (page - 1) * size
const [list, total] = await Promise.all([
Product.find(query)
.skip(skip)
.limit(parseInt(size))
.sort({ createdAt: -1 }),
Product.countDocuments(query)
])
res.json(Response.pagination(list, total, parseInt(page), parseInt(size)))
} catch (error) {
res.json(Response.error(500, '获取商品列表失败'))
}
}
// 获取商品详情
async getDetail(req, res) {
try {
const { id } = req.params
const product = await Product.findById(id)
if (!product) {
return res.json(Response.error(404, '商品不存在'))
}
res.json(Response.success(product))
} catch (error) {
res.json(Response.error(500, '获取商品详情失败'))
}
}
// 创建商品
async create(req, res) {
try {
const product = await Product.create(req.body)
res.json(Response.success(product, '创建成功'))
} catch (error) {
res.json(Response.error(500, '创建失败'))
}
}
// 更新商品
async update(req, res) {
try {
const { id } = req.params
const product = await Product.findByIdAndUpdate(
id,
req.body,
{ new: true }
)
if (!product) {
return res.json(Response.error(404, '商品不存在'))
}
res.json(Response.success(product, '更新成功'))
} catch (error) {
res.json(Response.error(500, '更新失败'))
}
}
// 删除商品
async delete(req, res) {
try {
const { id } = req.params
const product = await Product.findByIdAndDelete(id)
if (!product) {
return res.json(Response.error(404, '商品不存在'))
}
res.json(Response.success(null, '删除成功'))
} catch (error) {
res.json(Response.error(500, '删除失败'))
}
}
}
module.exports = new ProductController()
4. 数据库设计
4.1 MongoDB模型设计
// models/user.js
const mongoose = require('mongoose')
const userSchema = new mongoose.Schema({
openid: {
type: String,
required: true,
unique: true,
index: true
},
sessionKey: {
type: String
},
nickname: {
type: String
},
avatar: {
type: String
},
phone: {
type: String
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
})
userSchema.pre('save', function(next) {
this.updatedAt = Date.now()
next()
})
module.exports = mongoose.model('User', userSchema)
// models/product.js
const mongoose = require('mongoose')
const productSchema = new mongoose.Schema({
name: {
type: String,
required: true,
index: true
},
description: {
type: String
},
price: {
type: Number,
required: true
},
originalPrice: {
type: Number
},
images: [{
type: String
}],
category: {
type: String,
index: true
},
stock: {
type: Number,
default: 0
},
sales: {
type: Number,
default: 0
},
status: {
type: String,
enum: ['on_sale', 'off_sale', 'deleted'],
default: 'on_sale'
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
})
// 索引
productSchema.index({ name: 'text', description: 'text' })
productSchema.index({ category: 1, createdAt: -1 })
module.exports = mongoose.model('Product', productSchema)
// models/order.js
const mongoose = require('mongoose')
const orderSchema = new mongoose.Schema({
orderNo: {
type: String,
required: true,
unique: true,
index: true
},
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
index: true
},
items: [{
productId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product',
required: true
},
name: String,
price: Number,
quantity: Number,
image: String
}],
totalAmount: {
type: Number,
required: true
},
status: {
type: String,
enum: ['pending', 'paid', 'shipped', 'completed', 'cancelled'],
default: 'pending',
index: true
},
address: {
name: String,
phone: String,
province: String,
city: String,
district: String,
detail: String
},
createdAt: {
type: Date,
default: Date.now,
index: true
},
updatedAt: {
type: Date,
default: Date.now
}
})
module.exports = mongoose.model('Order', orderSchema)
4.2 数据库连接
// config/database.js
const mongoose = require('mongoose')
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
})
console.log('MongoDB连接成功')
} catch (error) {
console.error('MongoDB连接失败', error)
process.exit(1)
}
}
module.exports = connectDB
5. 业务逻辑
5.1 订单服务
// services/order.js
const Order = require('../models/order')
const Product = require('../models/product')
const Response = require('../utils/response')
class OrderService {
// 创建订单
async createOrder(userId, items, address) {
// 1. 验证商品库存
for (const item of items) {
const product = await Product.findById(item.productId)
if (!product || product.stock < item.quantity) {
throw new Error(`商品${product?.name || item.productId}库存不足`)
}
}
// 2. 计算总价
let totalAmount = 0
const orderItems = []
for (const item of items) {
const product = await Product.findById(item.productId)
const itemTotal = product.price * item.quantity
totalAmount += itemTotal
orderItems.push({
productId: product._id,
name: product.name,
price: product.price,
quantity: item.quantity,
image: product.images[0]
})
}
// 3. 生成订单号
const orderNo = this.generateOrderNo()
// 4. 创建订单
const order = await Order.create({
orderNo,
userId,
items: orderItems,
totalAmount,
address
})
// 5. 扣减库存
for (const item of items) {
await Product.findByIdAndUpdate(
item.productId,
{ $inc: { stock: -item.quantity } }
)
}
return order
}
// 生成订单号
generateOrderNo() {
const timestamp = Date.now()
const random = Math.floor(Math.random() * 10000)
return `ORDER${timestamp}${random}`
}
// 支付订单
async payOrder(orderId) {
const order = await Order.findById(orderId)
if (!order) {
throw new Error('订单不存在')
}
if (order.status !== 'pending') {
throw new Error('订单状态不正确')
}
// 更新订单状态
order.status = 'paid'
await order.save()
// 增加商品销量
for (const item of order.items) {
await Product.findByIdAndUpdate(
item.productId,
{ $inc: { sales: item.quantity } }
)
}
return order
}
// 取消订单
async cancelOrder(orderId) {
const order = await Order.findById(orderId)
if (!order) {
throw new Error('订单不存在')
}
if (order.status === 'completed' || order.status === 'shipped') {
throw new Error('订单无法取消')
}
// 恢复库存
if (order.status === 'paid') {
for (const item of order.items) {
await Product.findByIdAndUpdate(
item.productId,
{ $inc: { stock: item.quantity } }
)
}
}
order.status = 'cancelled'
await order.save()
return order
}
}
module.exports = new OrderService()
6. 安全措施
6.1 参数校验
// utils/validator.js
class Validator {
static validateRequired(value, field) {
if (!value || (typeof value === 'string' && value.trim() === '')) {
throw new Error(`${field}不能为空`)
}
}
static validateNumber(value, field, min, max) {
const num = Number(value)
if (isNaN(num)) {
throw new Error(`${field}必须是数字`)
}
if (min !== undefined && num < min) {
throw new Error(`${field}不能小于${min}`)
}
if (max !== undefined && num > max) {
throw new Error(`${field}不能大于${max}`)
}
return num
}
static validateEmail(email) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!regex.test(email)) {
throw new Error('邮箱格式不正确')
}
}
static validatePhone(phone) {
const regex = /^1[3-9]\d{9}$/
if (!regex.test(phone)) {
throw new Error('手机号格式不正确')
}
}
}
module.exports = Validator
// middleware/validator.js
const Validator = require('../utils/validator')
function validate(schema) {
return (req, res, next) => {
try {
for (const [field, rules] of Object.entries(schema)) {
const value = req.body[field] || req.query[field]
if (rules.required) {
Validator.validateRequired(value, field)
}
if (rules.type === 'number') {
req.body[field] = Validator.validateNumber(
value,
field,
rules.min,
rules.max
)
}
if (rules.type === 'email') {
Validator.validateEmail(value)
}
if (rules.type === 'phone') {
Validator.validatePhone(value)
}
}
next()
} catch (error) {
res.json({
code: 400,
message: error.message
})
}
}
}
module.exports = validate
6.2 SQL注入防护
// 使用参数化查询(MongoDB天然防护)
// ❌ 错误:字符串拼接
const query = `SELECT * FROM users WHERE name = '${name}'`
// ✅ 正确:参数化查询
const user = await User.findOne({ name: name })
// 对于MySQL,使用参数化查询
const query = 'SELECT * FROM users WHERE name = ?'
db.query(query, [name])
6.3 XSS防护
// 使用xss库过滤用户输入
const xss = require('xss')
function sanitizeInput(input) {
return xss(input, {
whiteList: {}, // 不允许任何HTML标签
stripIgnoreTag: true
})
}
// 使用
const safeContent = sanitizeInput(req.body.content)
6.4 限流
// middleware/rateLimit.js
const rateLimit = require('express-rate-limit')
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100, // 最多100次请求
message: {
code: 429,
message: '请求过于频繁,请稍后再试'
}
})
module.exports = limiter
6.5 HTTPS
// 生产环境必须使用HTTPS
// 小程序要求所有请求必须使用HTTPS
// 使用Nginx反向代理
// server {
// listen 443 ssl;
// ssl_certificate /path/to/cert.pem;
// ssl_certificate_key /path/to/key.pem;
//
// location / {
// proxy_pass http://localhost:3000;
// }
// }
7. 错误处理
7.1 全局错误处理
// middleware/error.js
function errorHandler(err, req, res, next) {
console.error('错误:', err)
// 开发环境返回详细错误
if (process.env.NODE_ENV === 'development') {
return res.json({
code: 500,
message: err.message,
stack: err.stack
})
}
// 生产环境返回通用错误
res.json({
code: 500,
message: '服务器错误,请稍后重试'
})
}
module.exports = errorHandler
// app.js
const errorHandler = require('./middleware/error')
app.use(errorHandler)
7.2 异步错误处理
// 使用async/await
app.get('/api/products', async (req, res, next) => {
try {
const products = await Product.find()
res.json(Response.success(products))
} catch (error) {
next(error) // 传递给错误处理中间件
}
})
// 或使用express-async-errors
require('express-async-errors')
app.get('/api/products', async (req, res) => {
const products = await Product.find()
res.json(Response.success(products))
// 错误会自动传递给错误处理中间件
})
8. 部署
8.1 Docker部署
# Dockerfile
FROM node:16-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
EXPOSE 3000
CMD ["node", "src/app.js"]
# docker-compose.yml
version: '3'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- MONGODB_URI=mongodb://mongo:27017/miniprogram
depends_on:
- mongo
mongo:
image: mongo:5
volumes:
- mongo-data:/data/db
volumes:
mongo-data:
8.2 PM2部署
// ecosystem.config.js
module.exports = {
apps: [{
name: 'miniprogram-api',
script: './src/app.js',
instances: 2,
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss'
}]
}
# 启动
pm2 start ecosystem.config.js
# 查看状态
pm2 status
# 查看日志
pm2 logs
# 重启
pm2 restart miniprogram-api
9. 常见问题
Q1: 如何处理高并发?
A:
使用Redis缓存热点数据
数据库读写分离
使用消息队列处理异步任务
负载均衡
Q2: 如何保证数据一致性?
A:
使用数据库事务
分布式锁
最终一致性方案
Q3: 如何优化API性能?
A:
数据库索引优化
查询结果缓存
分页查询
减少数据库查询次数
参考资源
Node.js最佳实践
RESTful API设计指南
MongoDB官方文档
小程序后端开发规范