Python+微信小程序开发实战课,武沛齐WuSir视频

26 阅读6分钟

Python+微信小程序开发实战:全栈开发的新范式

在移动互联网时代,微信小程序以其无需下载、即用即走的特性成为流量新入口。武沛齐老师的Python+微信小程序开发实战课程,为开发者提供了一条从后端到前端的完整全栈开发路径。

技术架构概览

Python后端 + 微信小程序的组合形成了完美的技术栈搭配:

  • 后端:Django/Flask框架提供稳健的API服务
  • 数据库:MySQL/MongoDB存储业务数据
  • 缓存:Redis提升系统性能
  • 小程序前端:微信小程序原生框架
  • 通信:RESTful API + WebSocket

后端开发:Django REST Framework实战

项目初始化与模型设计

# settings.py 核心配置
INSTALLED_APPS = [
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'rest_framework',
    'corsheaders',  # 跨域支持
    'users',
    'products',
    'orders',
]

# REST Framework配置
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

# 小程序配置
WECHAT_APP = {
    'appid': 'wx1234567890abcdef',
    'secret': 'your_app_secret_here',
}
# models.py 数据模型设计
from django.db import models
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    """扩展用户模型"""
    openid = models.CharField(max_length=64, unique=True, verbose_name='微信OpenID')
    avatar_url = models.URLField(blank=True, verbose_name='头像')
    nick_name = models.CharField(max_length=50, blank=True, verbose_name='昵称')
    phone = models.CharField(max_length=11, blank=True, verbose_name='手机号')
    
    class Meta:
        db_table = 'user'
        verbose_name = '用户'
        verbose_name_plural = verbose_name

class Product(models.Model):
    """商品模型"""
    name = models.CharField(max_length=100, verbose_name='商品名称')
    description = models.TextField(verbose_name='商品描述')
    price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='价格')
    stock = models.IntegerField(default=0, verbose_name='库存')
    image_url = models.URLField(verbose_name='图片链接')
    is_active = models.BooleanField(default=True, verbose_name='是否上架')
    created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    
    class Meta:
        db_table = 'product'
        verbose_name = '商品'
        verbose_name_plural = verbose_name

class Order(models.Model):
    """订单模型"""
    ORDER_STATUS = (
        ('pending', '待支付'),
        ('paid', '已支付'),
        ('shipped', '已发货'),
        ('completed', '已完成'),
        ('cancelled', '已取消'),
    )
    
    user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='用户')
    order_number = models.CharField(max_length=32, unique=True, verbose_name='订单号')
    total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='总金额')
    status = models.CharField(max_length=20, choices=ORDER_STATUS, default='pending', verbose_name='订单状态')
    created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    
    class Meta:
        db_table = 'order'
        verbose_name = '订单'
        verbose_name_plural = verbose_name

视图与序列化器

# serializers.py 序列化器
from rest_framework import serializers
from .models import User, Product, Order

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('id', 'openid', 'nick_name', 'avatar_url', 'phone')
        read_only_fields = ('openid',)

class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = '__all__'

class OrderSerializer(serializers.ModelSerializer):
    user = UserSerializer(read_only=True)
    items = OrderItemSerializer(many=True, read_only=True)
    
    class Meta:
        model = Order
        fields = '__all__'

class WechatLoginSerializer(serializers.Serializer):
    """微信登录序列化器"""
    code = serializers.CharField(required=True)
    
    def validate_code(self, value):
        # 验证微信code
        if not value:
            raise serializers.ValidationError('code不能为空')
        return value
# views.py API视图
from rest_framework import status, permissions
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.decorators import api_view, permission_classes
from django.contrib.auth import login
import requests
from .models import User, Product, Order
from .serializers import *

class WechatLoginView(APIView):
    """微信登录接口"""
    permission_classes = [permissions.AllowAny]
    
    def post(self, request):
        serializer = WechatLoginSerializer(data=request.data)
        if serializer.is_valid():
            code = serializer.validated_data['code']
            
            # 调用微信接口获取openid
            wechat_response = requests.get(
                'https://api.weixin.qq.com/sns/jscode2session',
                params={
                    'appid': settings.WECHAT_APP['appid'],
                    'secret': settings.WECHAT_APP['secret'],
                    'js_code': code,
                    'grant_type': 'authorization_code'
                }
            )
            
            wechat_data = wechat_response.json()
            openid = wechat_data.get('openid')
            
            if not openid:
                return Response(
                    {'error': '微信登录失败'}, 
                    status=status.HTTP_400_BAD_REQUEST
                )
            
            # 查找或创建用户
            user, created = User.objects.get_or_create(
                openid=openid,
                defaults={'username': openid}
            )
            
            # 生成JWT token
            from rest_framework_simplejwt.tokens import RefreshToken
            refresh = RefreshToken.for_user(user)
            
            return Response({
                'user': UserSerializer(user).data,
                'access_token': str(refresh.access_token),
                'refresh_token': str(refresh),
            })
        
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class ProductListView(APIView):
    """商品列表接口"""
    permission_classes = [permissions.IsAuthenticated]
    
    def get(self, request):
        products = Product.objects.filter(is_active=True)
        serializer = ProductSerializer(products, many=True)
        return Response(serializer.data)

class OrderCreateView(APIView):
    """创建订单接口"""
    permission_classes = [permissions.IsAuthenticated]
    
    def post(self, request):
        user = request.user
        items_data = request.data.get('items', [])
        
        # 生成订单号
        import time
        order_number = f"ORD{int(time.time())}{user.id:06d}"
        
        # 计算总金额
        total_amount = 0
        for item in items_data:
            product = Product.objects.get(id=item['product_id'])
            total_amount += product.price * item['quantity']
        
        # 创建订单
        order = Order.objects.create(
            user=user,
            order_number=order_number,
            total_amount=total_amount
        )
        
        # 创建订单项
        for item in items_data:
            OrderItem.objects.create(
                order=order,
                product_id=item['product_id'],
                quantity=item['quantity'],
                price=Product.objects.get(id=item['product_id']).price
            )
        
        serializer = OrderSerializer(order)
        return Response(serializer.data, status=status.HTTP_201_CREATED)

微信小程序前端开发

小程序配置文件

// app.json 全局配置
{
  "pages": [
    "pages/index/index",
    "pages/products/products",
    "pages/cart/cart",
    "pages/order/order",
    "pages/profile/profile"
  ],
  "window": {
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#ff6b81",
    "navigationBarTitleText": "电商小程序",
    "navigationBarTextStyle": "white"
  },
  "tabBar": {
    "color": "#999",
    "selectedColor": "#ff6b81",
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "首页",
        "iconPath": "images/home.png",
        "selectedIconPath": "images/home-active.png"
      },
      {
        "pagePath": "pages/products/products",
        "text": "商品",
        "iconPath": "images/products.png",
        "selectedIconPath": "images/products-active.png"
      },
      {
        "pagePath": "pages/cart/cart",
        "text": "购物车",
        "iconPath": "images/cart.png",
        "selectedIconPath": "images/cart-active.png"
      },
      {
        "pagePath": "pages/profile/profile",
        "text": "我的",
        "iconPath": "images/profile.png",
        "selectedIconPath": "images/profile-active.png"
      }
    ]
  }
}
// app.js 小程序入口
App({
  onLaunch: function () {
    // 登录
    wx.login({
      success: res => {
        if (res.code) {
          // 发送 res.code 到后台换取 openId, sessionKey, unionId
          this.login(res.code);
        }
      }
    });
  },

  globalData: {
    userInfo: null,
    baseURL: 'https://your-domain.com/api',
    token: null
  },

  // 登录方法
  login: function(code) {
    const that = this;
    wx.request({
      url: `${this.globalData.baseURL}/wechat-login/`,
      method: 'POST',
      data: {
        code: code
      },
      success: function(res) {
        if (res.statusCode === 200) {
          that.globalData.token = res.data.access_token;
          that.globalData.userInfo = res.data.user;
          
          // 存储token到本地
          wx.setStorageSync('token', res.data.access_token);
          wx.setStorageSync('userInfo', res.data.user);
        }
      }
    });
  },

  // 封装请求方法
  request: function(options) {
    const token = wx.getStorageSync('token');
    const header = {
      'Content-Type': 'application/json'
    };
    
    if (token) {
      header['Authorization'] = `Bearer ${token}`;
    }

    return new Promise((resolve, reject) => {
      wx.request({
        url: `${this.globalData.baseURL}${options.url}`,
        method: options.method || 'GET',
        data: options.data || {},
        header: header,
        success: resolve,
        fail: reject
      });
    });
  }
});

页面组件开发

<!-- pages/products/products.wxml -->
<view class="container">
  <!-- 搜索栏 -->
  <view class="search-bar">
    <input class="search-input" placeholder="搜索商品" bindinput="onSearchInput" />
    <button class="search-btn" bindtap="onSearch">搜索</button>
  </view>

  <!-- 商品列表 -->
  <scroll-view class="product-list" scroll-y>
    <view class="product-item" wx:for="{{products}}" wx:key="id" bindtap="onProductTap" data-product="{{item}}">
      <image class="product-image" src="{{item.image_url}}" mode="aspectFill"></image>
      <view class="product-info">
        <text class="product-name">{{item.name}}</text>
        <text class="product-description">{{item.description}}</text>
        <view class="product-bottom">
          <text class="product-price">¥{{item.price}}</text>
          <text class="product-stock">库存: {{item.stock}}</text>
        </view>
      </view>
      <button class="add-cart-btn" bindtap="onAddToCart" data-product="{{item}}">加入购物车</button>
    </view>
  </scroll-view>

  <!-- 加载更多 -->
  <view class="load-more" wx:if="{{hasMore}}">
    <text>加载中...</text>
  </view>
</view>
// pages/products/products.js
const app = getApp();

Page({
  data: {
    products: [],
    page: 1,
    pageSize: 10,
    hasMore: true,
    searchKeyword: ''
  },

  onLoad: function (options) {
    this.loadProducts();
  },

  onPullDownRefresh: function () {
    this.setData({
      page: 1,
      products: [],
      hasMore: true
    });
    this.loadProducts().then(() => {
      wx.stopPullDownRefresh();
    });
  },

  onReachBottom: function () {
    if (this.data.hasMore) {
      this.loadProducts();
    }
  },

  // 加载商品列表
  loadProducts: function () {
    const that = this;
    const { page, pageSize, searchKeyword } = this.data;

    return app.request({
      url: '/products/',
      method: 'GET',
      data: {
        page: page,
        page_size: pageSize,
        search: searchKeyword
      }
    }).then(res => {
      if (res.statusCode === 200) {
        const newProducts = res.data;
        const allProducts = that.data.products.concat(newProducts);
        
        that.setData({
          products: allProducts,
          page: page + 1,
          hasMore: newProducts.length === pageSize
        });
      }
    }).catch(err => {
      console.error('加载商品失败:', err);
      wx.showToast({
        title: '加载失败',
        icon: 'none'
      });
    });
  },

  // 搜索输入
  onSearchInput: function (e) {
    this.setData({
      searchKeyword: e.detail.value
    });
  },

  // 执行搜索
  onSearch: function () {
    this.setData({
      page: 1,
      products: [],
      hasMore: true
    });
    this.loadProducts();
  },

  // 商品点击
  onProductTap: function (e) {
    const product = e.currentTarget.dataset.product;
    wx.navigateTo({
      url: `/pages/product-detail/product-detail?id=${product.id}`
    });
  },

  // 加入购物车
  onAddToCart: function (e) {
    const product = e.currentTarget.dataset.product;
    const cart = wx.getStorageSync('cart') || [];
    
    const existingItem = cart.find(item => item.id === product.id);
    if (existingItem) {
      existingItem.quantity += 1;
    } else {
      cart.push({
        ...product,
        quantity: 1
      });
    }
    
    wx.setStorageSync('cart', cart);
    
    wx.showToast({
      title: '添加成功',
      icon: 'success'
    });
  }
});
/* pages/products/products.wxss */
.container {
  padding: 20rpx;
}

.search-bar {
  display: flex;
  margin-bottom: 20rpx;
}

.search-input {
  flex: 1;
  height: 80rpx;
  background: #f5f5f5;
  border-radius: 40rpx;
  padding: 0 30rpx;
  margin-right: 20rpx;
}

.search-btn {
  width: 120rpx;
  height: 80rpx;
  line-height: 80rpx;
  background: #ff6b81;
  color: white;
  border-radius: 40rpx;
}

.product-list {
  height: calc(100vh - 200rpx);
}

.product-item {
  display: flex;
  background: white;
  margin-bottom: 20rpx;
  padding: 20rpx;
  border-radius: 10rpx;
  box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.1);
}

.product-image {
  width: 200rpx;
  height: 200rpx;
  border-radius: 10rpx;
  margin-right: 20rpx;
}

.product-info {
  flex: 1;
  display: flex;
  flex-direction: column;
}

.product-name {
  font-size: 32rpx;
  font-weight: bold;
  margin-bottom: 10rpx;
}

.product-description {
  font-size: 24rpx;
  color: #666;
  margin-bottom: 20rpx;
  flex: 1;
}

.product-bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.product-price {
  font-size: 36rpx;
  color: #ff6b81;
  font-weight: bold;
}

.product-stock {
  font-size: 24rpx;
  color: #999;
}

.add-cart-btn {
  width: 160rpx;
  height: 60rpx;
  line-height: 60rpx;
  background: #ff6b81;
  color: white;
  border-radius: 30rpx;
  font-size: 24rpx;
}

.load-more {
  text-align: center;
  padding: 20rpx;
  color: #999;
}

部署与优化

Docker部署配置

# Dockerfile
FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

COPY . .

EXPOSE 8000

CMD ["gunicorn", "project.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "4"]
# docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DEBUG=False
      - DATABASE_URL=mysql://user:password@db:3306/app
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - db
      - redis

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: app
      MYSQL_USER: user
      MYSQL_PASSWORD: password
    volumes:
      - db_data:/var/lib/mysql

  redis:
    image: redis:6-alpine

volumes:
  db_data:

项目实战要点

性能优化策略

  1. 数据库优化:使用索引、查询优化、分页加载
  2. 缓存策略:Redis缓存热点数据
  3. 图片优化:CDN加速、图片压缩、懒加载
  4. 代码优化:减少wx.request调用、使用Promise.all

安全考虑

  1. 接口安全:JWT认证、请求签名、参数校验
  2. 数据安全:SQL注入防护、XSS防护
  3. 业务安全:防刷机制、金额校验、库存校验

总结

Python + 微信小程序的组合为开发者提供了强大的全栈开发能力。通过武沛齐老师的实战课程,学员可以掌握:

  1. Django REST Framework构建稳健的API服务
  2. 微信小程序开发完整的移动端应用
  3. 前后端分离架构设计与实现
  4. 项目部署与性能优化实战经验

这种技术栈组合特别适合创业项目、电商应用、内容平台等场景,能够快速验证产品idea并实现商业化落地。随着小程序的持续普及,掌握这套技术栈的开发者将在就业市场拥有显著优势。