后端开发与集成

💡 核心结论

  1. 小程序后端需要处理用户登录、数据存储、业务逻辑等

  2. RESTful API设计要遵循REST规范,返回统一格式

  3. JWT Token是常用的身份认证方案

  4. 数据库设计要考虑性能、扩展性和数据一致性

  5. 安全措施包括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:

  1. 使用Redis缓存热点数据

  2. 数据库读写分离

  3. 使用消息队列处理异步任务

  4. 负载均衡

Q2: 如何保证数据一致性?

A:

  1. 使用数据库事务

  2. 分布式锁

  3. 最终一致性方案

Q3: 如何优化API性能?

A:

  1. 数据库索引优化

  2. 查询结果缓存

  3. 分页查询

  4. 减少数据库查询次数


参考资源

  • Node.js最佳实践

  • RESTful API设计指南

  • MongoDB官方文档

  • 小程序后端开发规范