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:
项目实战要点
性能优化策略
- 数据库优化:使用索引、查询优化、分页加载
- 缓存策略:Redis缓存热点数据
- 图片优化:CDN加速、图片压缩、懒加载
- 代码优化:减少wx.request调用、使用Promise.all
安全考虑
- 接口安全:JWT认证、请求签名、参数校验
- 数据安全:SQL注入防护、XSS防护
- 业务安全:防刷机制、金额校验、库存校验
总结
Python + 微信小程序的组合为开发者提供了强大的全栈开发能力。通过武沛齐老师的实战课程,学员可以掌握:
- Django REST Framework构建稳健的API服务
- 微信小程序开发完整的移动端应用
- 前后端分离架构设计与实现
- 项目部署与性能优化实战经验
这种技术栈组合特别适合创业项目、电商应用、内容平台等场景,能够快速验证产品idea并实现商业化落地。随着小程序的持续普及,掌握这套技术栈的开发者将在就业市场拥有显著优势。