校园二手物品交易平台(Python+Flask)设计与实现
一、系统文档
1. 需求分析
1.1 功能需求
| 角色 | 核心功能 | 具体描述 |
|---|---|---|
| 学生用户 | 账号管理 | 注册(需校园邮箱验证)、登录、个人信息维护、密码修改 |
| 学生用户 | 商品管理 | 发布二手商品(含分类、描述、价格、图片)、编辑商品、下架商品 |
| 学生用户 | 交易功能 | 浏览/搜索商品、加入收藏、私信沟通、下单购买、交易评价 |
| 学生用户 | 订单管理 | 查看我的订单(待付款/待发货/已完成)、取消订单、确认收货 |
| 管理员 | 用户管理 | 查看用户列表、禁用违规账号、处理用户举报 |
| 管理员 | 商品管理 | 审核商品(违规商品下架)、管理商品分类、清理过期商品 |
| 管理员 | 数据统计 | 交易成交量、活跃用户数、热门商品分类等数据可视化统计 |
1.2 非功能需求
- 性能:页面响应时间≤3秒,支持1000用户同时在线
- 安全性:用户密码加密存储,防止XSS和CSRF攻击,敏感操作需二次验证
- 可用性:支持PC端和移动端访问,界面简洁易用
- 可靠性:数据定期备份,保证交易记录不丢失
2. 系统设计
2.1 技术栈
- 后端:Python 3.9 + Flask 2.0(轻量级Web框架)
- 前端:HTML5 + CSS3(Tailwind CSS) + JavaScript(Vue.js)
- 数据库:SQLite(开发环境)/ PostgreSQL(生产环境)
- 其他工具:Redis(缓存热门商品)、JWT(身份认证)、Pillow(图片处理)
2.2 数据库设计
- User(用户表):id(主键)、username(用户名)、email(校园邮箱)、password(加密密码)、avatar(头像)、is_active(是否激活)、role(角色)、created_at(注册时间)
- Category(商品分类表):id(主键)、name(分类名称)、description(分类描述)、parent_id(父分类,用于多级分类)
- Product(商品表):id(主键)、title(标题)、description(描述)、price(价格)、images(图片路径)、status(状态)、category_id(分类)、user_id(发布者)、created_at(发布时间)
- Order(订单表):id(主键)、product_id(商品)、buyer_id(买家)、seller_id(卖家)、status(状态)、price(交易价格)、created_at(创建时间)
- Message(消息表):id(主键)、sender_id(发送者)、receiver_id(接收者)、content(内容)、is_read(是否已读)、created_at(发送时间)
- Favorite(收藏表):id(主键)、user_id(用户)、product_id(商品)、created_at(收藏时间)
- Review(评价表):id(主键)、order_id(订单)、user_id(评价者)、rating(评分)、content(内容)、created_at(评价时间)
2.3 系统架构
校园二手物品交易平台
├── 表现层(Web界面)
│ ├── 用户前台(商品浏览、交易)
│ └── 管理后台(用户/商品管理)
├── 应用层(Flask核心)
│ ├── 蓝图模块(用户/商品/订单等)
│ ├── 视图函数(处理HTTP请求)
│ ├── 表单验证(数据合法性校验)
│ └── 权限控制(JWT认证)
├── 业务逻辑层
│ ├── 用户服务(注册/登录/信息管理)
│ ├── 商品服务(发布/编辑/搜索)
│ ├── 订单服务(创建/支付/完成)
│ └── 消息服务(私信/通知)
└── 数据层
├── 数据库(存储业务数据)
└── 缓存(Redis缓存热门数据)
3. 系统测试与部署
3.1 测试用例(核心功能)
| 测试模块 | 测试步骤 | 预期结果 |
|---|---|---|
| 商品发布 | 1. 用户登录 2. 点击"发布商品" 3. 填写信息并上传图片 |
商品发布成功,状态为"待审核" |
| 商品购买 | 1. 浏览商品详情 2. 点击"立即购买" 3. 确认订单信息 |
订单创建成功,商品状态变为"已售出" |
| 私信沟通 | 1. 进入商品详情页 2. 点击"联系卖家" 3. 发送咨询消息 |
卖家收到消息提醒,消息内容正确 |
3.2 部署步骤
- 安装依赖:
pip install flask flask-sqlalchemy flask-jwt-extended redis pillow - 配置环境变量:
export FLASK_APP=app.py FLASK_ENV=production - 初始化数据库:
flask db init && flask db migrate && flask db upgrade - 启动Redis服务:
redis-server --daemonize yes - 启动应用:
gunicorn -w 4 -b 0.0.0.0:8000 app:app - 访问地址:
http://localhost:8000
二、核心代码实现
1. 项目结构
campus_trading/
├── app.py # 应用入口
├── config.py # 配置文件
├── models/ # 数据模型
│ ├── __init__.py
│ ├── user.py
│ ├── product.py
│ └── order.py
├── routes/ # 路由蓝图
│ ├── __init__.py
│ ├── auth.py # 认证相关
│ ├── products.py # 商品相关
│ └── orders.py # 订单相关
├── services/ # 业务逻辑
│ ├── __init__.py
│ ├── product_service.py
│ └── order_service.py
├── static/ # 静态文件
│ ├── images/
│ ├── css/
│ └── js/
└── templates/ # 模板文件
├── index.html
├── product_detail.html
└── user/
2. 数据模型(models/product.py)
from datetime import datetime
from extensions import db
class Product(db.Model):
"""商品模型"""
__tablename__ = 'products'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text, nullable=False)
price = db.Column(db.Float, nullable=False)
images = db.Column(db.JSON) # 存储图片路径列表
status = db.Column(db.String(20), default='pending') # pending/rejected/active/sold
views = db.Column(db.Integer, default=0) # 浏览量
# 外键关联
category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
# 关系
category = db.relationship('Category', backref='products')
user = db.relationship('User', backref='products')
orders = db.relationship('Order', backref='product', lazy='dynamic')
favorites = db.relationship('Favorite', backref='product', lazy='dynamic')
created_at = db.Column(db.DateTime, default=datetime.ut***ow)
updated_at = db.Column(db.DateTime, default=datetime.ut***ow, onupdate=datetime.ut***ow)
def __repr__(self):
return f'<Product {self.title}>'
def to_dict(self):
"""转换为字典,用于API返回"""
return {
'id': self.id,
'title': self.title,
'description': self.description,
'price': self.price,
'images': self.images,
'status': self.status,
'views': self.views,
'category': self.category.name if self.category else None,
'user': {
'id': self.user.id,
'username': self.user.username,
'avatar': self.user.avatar
},
'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S')
}
3. 商品相关路由(routes/products.py)
from flask import Blueprint, request, jsonify, render_template, redirect, url_for
from flask_jwt_extended import jwt_required, get_jwt_identity
from models.product import Product
from models.category import Category
from models.favorite import Favorite
from services.product_service import create_product, get_product_detail, search_products
from extensions import db
from forms import ProductForm
import os
from utils import save_images
products_bp = Blueprint('products', __name__, url_prefix='/products')
@products_bp.route('/')
def product_list():
"""商品列表页,支持分页和筛选"""
page = request.args.get('page', 1, type=int)
per_page = 12
category_id = request.args.get('category', type=int)
keyword = request.args.get('keyword', '')
min_price = request.args.get('min_price', type=float)
max_price = request.args.get('max_price', type=float)
# 搜索和筛选商品
pagination = search_products(
page=page,
per_page=per_page,
category_id=category_id,
keyword=keyword,
min_price=min_price,
max_price=max_price
)
categories = Category.query.all()
return render_template('products/list.html',
products=pagination.items,
pagination=pagination,
categories=categories,
keyword=keyword,
category_id=category_id,
min_price=min_price,
max_price=max_price)
@products_bp.route('/<int:product_id>')
def product_detail(product_id):
"""商品详情页"""
product = get_product_detail(product_id)
if not product:
return render_template('404.html'), 404
# 增加浏览量
product.views += 1
db.session.***mit()
# 相关推荐(同分类商品)
related_products = Product.query.filter(
Product.category_id == product.category_id,
Product.id != product_id,
Product.status == 'active'
).limit(4).all()
return render_template('products/detail.html',
product=product,
related_products=related_products)
@products_bp.route('/create', methods=['GET', 'POST'])
@jwt_required()
def create_product_page():
"""发布商品页面"""
form = ProductForm()
form.category_id.choices = [(c.id, c.name) for c in Category.query.all()]
if form.validate_on_submit():
user_id = get_jwt_identity()
# 处理上传的图片
images = request.files.getlist('images')
image_paths = save_images(images)
# 创建商品
product = create_product(
title=form.title.data,
description=form.description.data,
price=form.price.data,
category_id=form.category_id.data,
user_id=user_id,
images=image_paths
)
return redirect(url_for('products.product_detail', product_id=product.id))
return render_template('products/create.html', form=form)
@products_bp.route('/<int:product_id>/favorite', methods=['POST'])
@jwt_required()
def toggle_favorite(product_id):
"""收藏/取消收藏商品"""
user_id = get_jwt_identity()
product = Product.query.get_or_404(product_id)
favorite = Favorite.query.filter_by(user_id=user_id, product_id=product_id).first()
if favorite:
# 取消收藏
db.session.delete(favorite)
db.session.***mit()
return jsonify({'status': 'su***ess', 'action': 'unfavorite'})
else:
# 收藏商品
favorite = Favorite(user_id=user_id, product_id=product_id)
db.session.add(favorite)
db.session.***mit()
return jsonify({'status': 'su***ess', 'action': 'favorite'})
4. 订单服务(services/order_service.py)
from models.order import Order
from models.product import Product
from models.user import User
from models.message import Message
from extensions import db
from datetime import datetime
from sqlalchemy.exc import SQLAlchemyError
def create_order(product_id, buyer_id):
"""创建订单"""
try:
# 获取商品和卖家信息
product = Product.query.get(product_id)
if not product:
return {'status': 'error', 'message': '商品不存在'}
# 检查商品状态
if product.status != 'active':
return {'status': 'error', 'message': '商品无法购买'}
# 检查买家是否为商品发布者
if product.user_id == buyer_id:
return {'status': 'error', 'message': '不能购买自己发布的商品'}
# 创建订单
order = Order(
product_id=product_id,
buyer_id=buyer_id,
seller_id=product.user_id,
price=product.price,
status='pending' # 待付款
)
# 更新商品状态为已售出
product.status = 'sold'
# 创建通知消息给卖家
message = Message(
sender_id=buyer_id,
receiver_id=product.user_id,
content=f'有用户购买了你的商品《{product.title}》,请及时处理订单'
)
db.session.add(order)
db.session.add(message)
db.session.***mit()
return {'status': 'su***ess', 'order_id': order.id}
except SQLAlchemyError as e:
db.session.rollback()
return {'status': 'error', 'message': str(e)}
def update_order_status(order_id, user_id, new_status):
"""更新订单状态"""
order = Order.query.get(order_id)
if not order:
return {'status': 'error', 'message': '订单不存在'}
# 验证权限(只能是买家或卖家操作)
if order.buyer_id != user_id and order.seller_id != user_id:
return {'status': 'error', 'message': '没有操作权限'}
# 检查状态流转是否合法
valid_transitions = {
'pending': ['paid', 'cancelled'],
'paid': ['shipped', 'refunded'],
'shipped': ['***pleted', 'returned'],
'***pleted': [],
'cancelled': [],
'refunded': [],
'returned': []
}
if new_status not in valid_transitions[order.status]:
return {'status': 'error', 'message': '不合法的状态转换'}
# 更新订单状态
order.status = new_status
order.updated_at = datetime.ut***ow()
# 如果订单完成,创建评价提醒消息
if new_status == '***pleted':
message = Message(
sender_id=order.seller_id,
receiver_id=order.buyer_id,
content=f'订单《{order.product.title}》已完成,欢迎对商品进行评价'
)
db.session.add(message)
db.session.***mit()
return {'status': 'su***ess', 'order': order.to_dict()}
5. 前端页面(商品详情页 templates/products/detail.html)
{% extends "base.html" %}
{% block title %}{{ product.title }} - 校园二手交易平台{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<div class="flex flex-col md:flex-row gap-8">
<!-- 商品图片 -->
<div class="md:w-1/2">
<div class="bg-white rounded-lg shadow-md overflow-hidden">
<img src="{{ product.images[0] if product.images else '/static/images/default-product.jpg' }}"
alt="{{ product.title }}"
class="w-full h-80 object-cover">
{% if product.images|length > 1 %}
<div class="flex gap-2 p-4 overflow-x-auto">
{% for img in product.images[1:] %}
<img src="{{ img }}" alt="商品图片" class="w-20 h-20 object-cover rounded cursor-pointer">
{% endfor %}
</div>
{% endif %}
</div>
</div>
<!-- 商品信息 -->
<div class="md:w-1/2">
<h1 class="text-2xl font-bold text-gray-800">{{ product.title }}</h1>
<div class="flex items-center mt-2 text-yellow-500">
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star"></i>
<i class="fas fa-star-half-alt"></i>
<span class="ml-2 text-gray-600">(0 评价)</span>
</div>
<div class="mt-4 mb-6">
<span class="text-3xl font-bold text-red-600">¥{{ product.price }}</span>
<span class="ml-2 text-gray-500">发布于 {{ product.created_at }}</span>
</div>
<div class="bg-gray-50 p-4 rounded-lg mb-6">
<h3 class="font-semibold mb-2">商品描述</h3>
<p class="text-gray-700 whitespace-pre-line">{{ product.description }}</p>
</div>
<div class="flex flex-wrap gap-4 mb-6">
<div class="flex items-center">
<span class="text-gray-500 mr-2">分类:</span>
<span>{{ product.category }}</span>
</div>
<div class="flex items-center">
<span class="text-gray-500 mr-2">浏览:</span>
<span>{{ product.views }}</span>
</div>
</div>
<!-- 操作按钮 -->
<div class="flex flex-wrap gap-4">
{% if current_user.is_authenticated %}
{% if product.user_id != current_user.id and product.status == 'active' %}
<button id="buyBtn" class="px-6 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition">
立即购买
</button>
<button id="favoriteBtn" class="px-6 py-2 border border-gray-300 rounded-lg hover:bg-gray-100 transition">
<i class="far fa-heart mr-1"></i> 收藏
</button>
{% endif %}
{% if product.user_id == current_user.id %}
<a href="{{ url_for('products.edit_product', product_id=product.id) }}"
class="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition">
编辑商品
</a>
{% endif %}
{% else %}
<a href="{{ url_for('auth.login', next=request.path) }}"
class="px-6 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition">
登录后购买
</a>
{% endif %}
<button id="contactBtn" class="px-6 py-2 border border-gray-300 rounded-lg hover:bg-gray-100 transition">
<i class="far fa-***ment mr-1"></i> 联系卖家
</button>
</div>
<!-- 卖家信息 -->
<div class="mt-8 flex items-center">
<img src="{{ product.user.avatar or '/static/images/default-avatar.png' }}"
alt="{{ product.user.username }}"
class="w-12 h-12 rounded-full object-cover">
<div class="ml-4">
<div class="font-medium">{{ product.user.username }}</div>
<div class="text-sm text-gray-500">已认证学生</div>
</div>
</div>
</div>
</div>
<!-- 商品详情 -->
<div class="mt-12 bg-white p-6 rounded-lg shadow-md">
<h2 class="text-xl font-bold mb-4">商品详情</h2>
<div class="prose max-w-none">
<!-- 这里会显示富文本编辑的商品详情 -->
<p>商品详细描述将在这里显示,包括商品的新旧程度、使用情况等信息。</p>
</div>
</div>
<!-- 相关推荐 -->
{% if related_products %}
<div class="mt-12">
<h2 class="text-xl font-bold mb-4">相关推荐</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4">
{% for p in related_products %}
<a href="{{ url_for('products.product_detail', product_id=p.id) }}" class="block">
<div class="bg-white rounded-lg shadow hover:shadow-md transition">
<img src="{{ p.images[0] if p.images else '/static/images/default-product.jpg' }}"
alt="{{ p.title }}"
class="w-full h-48 object-cover rounded-t-lg">
<div class="p-4">
<h3 class="font-medium line-clamp-1">{{ p.title }}</h3>
<p class="text-red-600 font-bold mt-2">¥{{ p.price }}</p>
</div>
</div>
</a>
{% endfor %}
</div>
</div>
{% endif %}
</div>
<!-- 购买确认模态框 -->
<div id="buyModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg w-full max-w-md p-6">
<h3 class="text-xl font-bold mb-4">确认购买</h3>
<p class="mb-4">你确定要购买 <span class="font-medium">{{ product.title }}</span> 吗?</p>
<p class="text-red-600 font-bold mb-6">价格:¥{{ product.price }}</p>
<div class="flex gap-4">
<button id="cancelBuy" class="flex-1 py-2 border border-gray-300 rounded-lg">取消</button>
<button id="confirmBuy" class="flex-1 py-2 bg-red-600 text-white rounded-lg">确认购买</button>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
// 购买功能
document.getElementById('buyBtn').addEventListener('click', function() {
document.getElementById('buyModal').classList.remove('hidden');
});
document.getElementById('cancelBuy').addEventListener('click', function() {
document.getElementById('buyModal').classList.add('hidden');
});
document.getElementById('confirmBuy').addEventListener('click', function() {
// 发送创建订单请求
fetch('{{ url_for('orders.create_order', product_id=product.id) }}', {
method: 'POST',
headers: {
'X-CSRFToken': '{{ csrf_token() }}',
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.status === 'su***ess') {
window.location.href = '{{ url_for('orders.order_detail', order_id=0) }}'.replace('0', data.order_id);
} else {
alert(data.message);
}
});
});
// 收藏功能
document.getElementById('favoriteBtn').addEventListener('click', function() {
fetch('{{ url_for('products.toggle_favorite', product_id=product.id) }}', {
method: 'POST',
headers: {
'X-CSRFToken': '{{ csrf_token() }}',
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
const btn = document.getElementById('favoriteBtn');
if (data.action === 'favorite') {
btn.innerHTML = '<i class="fas fa-heart mr-1 text-red-500"></i> 已收藏';
} else {
btn.innerHTML = '<i class="far fa-heart mr-1"></i> 收藏';
}
});
});
// 联系卖家
document.getElementById('contactBtn').addEventListener('click', function() {
window.location.href = '{{ url_for('messages.chat', user_id=product.user_id) }}';
});
</script>
{% endblock %}
该校园二手物品交易平台设计充分考虑了高校学生的实际需求,采用Python+Flask技术栈实现了完整的二手交易功能。系统具有以下特点:
- 技术适用性:使用轻量级Flask框架,开发效率高,适合毕业设计的时间周期
- 功能完整性:包含商品发布、浏览、购买、订单管理等核心交易功能
- 可扩展性:模块化设计便于后续添加新功能,如在线支付、物流跟踪等
- 安全性:实现了用户认证、权限控制等安全机制
文档部分详细阐述了系统的需求分析、设计方案和测试部署流程,代码部分实现了核心功能模块,可作为毕业论文设计的完整素材。