1. 项目概述:基于Python-Flask的同城二手交易小程序
这个二手交易系统是我去年为一个本地社区开发的实战项目,核心目标是解决学生和年轻上班族处理闲置物品的需求。相比闲鱼等大型平台,我们聚焦同城交易,通过地理位置匹配让买卖双方能快速面交,省去物流环节。系统采用Flask作为后端框架,搭配微信小程序前端,从需求分析到上线部署共耗时3个月,目前日均活跃用户稳定在800人左右。
为什么选择Flask?在初期技术选型时,我们对比了Django和FastAPI。Django虽然开箱即用但显得臃肿,FastAPI的异步特性在这个IO压力不大的场景下优势不明显。最终选择Flask是因为:
- 轻量级核心适合快速迭代
- 扩展机制灵活,可以按需引入组件
- 社区生态成熟,遇到问题容易找到解决方案
- 与SQLAlchemy的集成堪称完美
2. 核心功能模块设计
2.1 用户系统实现方案
用户模块采用经典的手机号+验证码登录,这里有个值得分享的细节:我们没有直接存储用户手机号,而是使用AES-256加密后存储。关键实现如下:
python复制from Crypto.Cipher import AES
import base64
class UserSecurity:
def __init__(self):
self.key = b'32位长度的密钥字符串' # 实际项目应从配置读取
self.iv = b'16位初始化向量'
def encrypt_phone(self, phone):
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
encrypted = cipher.encrypt(phone.ljust(16).encode())
return base64.b64encode(encrypted).decode()
def decrypt_phone(self, encrypted):
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
decrypted = cipher.decrypt(base64.b64decode(encrypted))
return decrypted.decode().strip()
重要提示:密钥管理一定要通过环境变量注入,绝对不能硬编码在代码中。我们使用python-dotenv管理开发环境变量,生产环境则使用Vault。
2.2 商品发布与检索
商品模块的核心挑战是地理位置检索。我们在MySQL中使用了空间索引,具体实现分三步:
- 数据库表设计:
sql复制CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(100) NOT NULL,
price DECIMAL(10,2) NOT NULL,
location POINT NOT NULL,
SPATIAL INDEX(location),
-- 其他字段...
);
- Flask模型定义:
python复制class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
location = db.Column(db.Geometry('POINT'))
def set_location(self, lat, lng):
self.location = f'POINT({lng} {lat})'
@hybrid_property
def latitude(self):
return db.session.scalar(func.ST_Y(self.location))
@hybrid_property
def longitude(self):
return db.session.scalar(func.ST_X(self.location))
- 距离查询接口:
python复制@app.route('/api/products/nearby')
def nearby_products():
lat = request.args.get('lat', type=float)
lng = request.args.get('lng', type=float)
radius = request.args.get('radius', default=5, type=float) # 默认5公里
point = f'POINT({lng} {lat})'
query = Product.query.filter(
func.ST_Distance_Sphere(
Product.location,
func.ST_GeomFromText(point, 4326)
) <= radius * 1000 # 转换为米
).order_by('created_at desc')
return jsonify([p.to_dict() for p in query])
实测下来,在10万条商品数据的情况下,这个查询能在200ms内完成,完全满足移动端需求。
3. 技术架构深度解析
3.1 后端服务分层设计
我们采用清晰的三层架构,这在Flask项目中尤为重要:
code复制app/
├── controllers/ # 路由层
├── services/ # 业务逻辑
├── models/ # 数据模型
├── utils/ # 工具类
└── extensions.py # 扩展初始化
关键技巧:使用Flask的Blueprint实现模块化路由。例如用户模块:
python复制# controllers/user_controller.py
from flask import Blueprint
user_bp = Blueprint('user', __name__, url_prefix='/api/user')
@user_bp.route('/login', methods=['POST'])
def login():
# 登录逻辑...
# app.py
from controllers.user_controller import user_bp
app.register_blueprint(user_bp)
3.2 小程序端关键技术点
微信小程序开发中,有几个特别需要注意的点:
- 图片上传优化:
- 使用wx.chooseMedia代替旧的chooseImage API
- 先压缩再上传,我们开发了智能压缩算法:
javascript复制function compressImage(file, quality = 80) {
return new Promise((resolve) => {
wx.compressImage({
src: file.tempFilePath,
quality,
success: res => resolve(res.tempFilePath)
})
})
}
- 地理位置缓存策略:
javascript复制// 获取位置并缓存24小时
function getLocation() {
const cache = wx.getStorageSync('last_location')
if (cache && Date.now() - cache.time < 86400000) {
return Promise.resolve(cache.data)
}
return new Promise((resolve, reject) => {
wx.getLocation({
type: 'gcj02',
success: res => {
const data = { lat: res.latitude, lng: res.longitude }
wx.setStorageSync('last_location', {
time: Date.now(),
data
})
resolve(data)
},
fail: reject
})
})
}
4. 部署与性能优化实战
4.1 生产环境部署方案
我们最终选择了Docker + Nginx的方案,目录结构如下:
code复制deploy/
├── docker-compose.yml
├── nginx/
│ ├── nginx.conf
│ └── sites-enabled/
├── mysql/
│ └── my.cnf
└── redis/
└── redis.conf
关键配置项:
yaml复制# docker-compose.yml
version: '3'
services:
app:
build: .
ports:
- "5000:5000"
environment:
- FLASK_ENV=production
depends_on:
- redis
- mysql
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx:/etc/nginx
depends_on:
- app
避坑指南:Alpine镜像的musl libc与某些Python包不兼容,我们最终改用python:3.9-slim作为基础镜像。
4.2 性能优化技巧
- 数据库连接池配置:
python复制from sqlalchemy.pool import QueuePool
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
'poolclass': QueuePool,
'pool_size': 20,
'max_overflow': 10,
'pool_recycle': 3600
}
- Redis缓存策略:
- 商品详情缓存30分钟
- 热门列表缓存5分钟
- 使用Hash类型存储用户会话
python复制from flask_redis import FlaskRedis
redis = FlaskRedis()
@app.route('/product/<int:id>')
def get_product(id):
cache_key = f'product:{id}'
data = redis.hgetall(cache_key)
if data:
return jsonify(data)
product = Product.query.get_or_404(id)
data = product.to_dict()
redis.hmset(cache_key, data)
redis.expire(cache_key, 1800) # 30分钟
return jsonify(data)
5. 踩坑实录与解决方案
5.1 微信支付集成陷阱
微信小程序支付需要特别注意:
- 统一下单接口的notify_url必须是HTTPS
- 支付签名严格按照字典序排序
- 金额单位是分(整数)
我们封装了安全支付方法:
python复制def create_wxpay_order(openid, amount, desc):
nonce_str = generate_nonce_str()
params = {
'appid': app.config['WX_APPID'],
'mch_id': app.config['WX_MCHID'],
'nonce_str': nonce_str,
'body': desc,
'out_trade_no': generate_trade_no(),
'total_fee': int(amount * 100),
'spbill_create_ip': request.remote_addr,
'notify_url': app.config['WX_NOTIFY_URL'],
'trade_type': 'JSAPI',
'openid': openid
}
# 签名处理
params['sign'] = generate_sign(params)
# 转换XML并请求
xml = dict_to_xml(params)
response = requests.post(
'https://api.mch.weixin.qq.com/pay/unifiedorder',
data=xml,
headers={'Content-Type': 'application/xml'}
)
# 解析响应
result = xml_to_dict(response.content)
if result['return_code'] != 'SUCCESS':
raise PaymentError(result['return_msg'])
# 返回小程序支付参数
return {
'timeStamp': str(int(time.time())),
'nonceStr': nonce_str,
'package': f'prepay_id={result["prepay_id"]}',
'signType': 'MD5',
'paySign': generate_pay_sign(result["prepay_id"])
}
5.2 消息实时性保障
最初使用轮询方案导致服务器压力大,后来改造为WebSocket方案:
python复制from flask_socketio import SocketIO
socketio = SocketIO(app, cors_allowed_origins="*")
@socketio.on('join_room')
def handle_join(data):
join_room(data['room'])
emit('system_msg', {'text': f'{data["user"]}进入聊天'}, room=data['room'])
@socketio.on('send_msg')
def handle_message(data):
# 保存到数据库
message = Message(
room=data['room'],
sender=data['sender'],
content=data['content']
)
db.session.add(message)
db.session.commit()
# 广播消息
emit('new_msg', message.to_dict(), room=data['room'])
前端实现:
javascript复制const socket = wx.connectSocket({
url: 'wss://yourdomain.com/socket.io'
})
socket.onMessage(msg => {
const data = JSON.parse(msg.data)
if (data.event === 'new_msg') {
// 更新UI
}
})
6. 项目演进方向
目前正在开发中的功能:
- 智能推荐系统:
- 基于用户浏览历史的协同过滤
- 结合地理位置的热门商品推荐
- 使用Faiss加速向量检索
python复制def recommend_products(user_id, location, limit=10):
# 获取用户历史
history = get_user_history(user_id)
# 获取附近商品
nearby = get_nearby_products(location)
# 使用预训练模型生成推荐
model = load_recommend_model()
scores = model.predict(user_id, [p.id for p in nearby])
# 合并结果
results = sorted(zip(nearby, scores), key=lambda x: -x[1])
return [r[0] for r in results[:limit]]
- 交易安全升级:
- 引入平台担保交易
- 增加实名认证环节
- 开发线下交易提醒功能
这个项目给我的最大启示是:技术方案必须紧密贴合业务场景。比如最初我们想用Elasticsearch实现搜索,后来发现对于同城二手交易,简单的MySQL空间查询+关键词匹配就完全够用,节省了大量运维成本。