Python项目-校园二手物品交易平台的设计与实现(1)

1. 项目概述

1.1 项目背景

随着高校学生消费意识的增强和环保理念的普及,校园二手物品交易需求日益增长。传统的线下交易方式存在信息不对称、交易范围有限、安全性难以保障等问题。本项目旨在设计并实现一个基于Python的校园二手物品交易平台,为高校师生提供一个便捷、安全、高效的二手物品交易环境。

1.2 项目目标

  1. 构建一个功能完善的校园二手物品交易平台
  2. 实现用户注册、登录、身份验证等基础功能
  3. 支持物品发布、搜索、收藏、交易等核心功能
  4. 提供即时通讯功能,方便买卖双方沟通
  5. 实现交易评价和信用体系,保障交易安全
  6. 提供数据分析功能,了解平台运营情况

1.3 技术栈选择

  • 后端框架:Flask/Django
  • 前端技术:HTML5、CSS3、JavaScript、Bootstrap
  • 数据库:MySQL/PostgreSQL
  • 缓存系统:Redis
  • 搜索引擎:Elasticsearch
  • 消息队列:RabbitMQ
  • 即时通讯:WebSocket
  • 图片存储:阿里云OSS/七牛云
  • 部署环境:Docker + Nginx

2. 系统设计

2.1 系统架构

本项目采用前后端分离的架构设计,主要分为以下几个部分:

  1. 表示层:负责与用户交互的Web界面
  2. 应用层:处理业务逻辑的后端服务
  3. 数据层:负责数据存储和管理
  4. 基础设施层:提供系统运行所需的基础服务

系统架构图如下:

+------------------+     +------------------+     +------------------+
|    表示层        |     |    应用层        |     |    数据层        |
|                  |     |                  |     |                  |
|  - Web界面       |     |  - 用户服务      |     |  - MySQL数据库   |
|  - 移动端界面    |<--->|  - 商品服务      |<--->|  - Redis缓存     |
|  - 管理后台      |     |  - 交易服务      |     |  - Elasticsearch |
|                  |     |  - 消息服务      |     |  - 文件存储      |
+------------------+     +------------------+     +------------------+^|v+------------------+|   基础设施层     ||                  ||  - 日志系统      ||  - 监控系统      ||  - 消息队列      ||  - 定时任务      |+------------------+

2.2 数据库设计

2.2.1 E-R图

系统的核心实体包括:用户(User)、商品(Item)、订单(Order)、评价(Review)、消息(Message)等。

2.2.2 主要数据表
  1. 用户表(users)

    • id: 用户ID
    • username: 用户名
    • password: 密码(加密存储)
    • email: 邮箱
    • phone: 手机号
    • avatar: 头像URL
    • school_id: 学校ID
    • credit_score: 信用分
    • create_time: 创建时间
    • update_time: 更新时间
  2. 商品表(items)

    • id: 商品ID
    • title: 标题
    • description: 描述
    • price: 价格
    • original_price: 原价
    • category_id: 分类ID
    • user_id: 发布者ID
    • status: 状态(在售/已售/下架)
    • view_count: 浏览次数
    • favorite_count: 收藏次数
    • create_time: 创建时间
    • update_time: 更新时间
  3. 商品图片表(item_images)

    • id: 图片ID
    • item_id: 商品ID
    • image_url: 图片URL
    • is_cover: 是否为封面
    • create_time: 创建时间
  4. 订单表(orders)

    • id: 订单ID
    • item_id: 商品ID
    • seller_id: 卖家ID
    • buyer_id: 买家ID
    • price: 成交价格
    • status: 状态(待付款/待发货/待收货/已完成/已取消)
    • create_time: 创建时间
    • update_time: 更新时间
  5. 评价表(reviews)

    • id: 评价ID
    • order_id: 订单ID
    • user_id: 评价用户ID
    • target_user_id: 被评价用户ID
    • content: 评价内容
    • rating: 评分(1-5)
    • create_time: 创建时间
  6. 消息表(messages)

    • id: 消息ID
    • sender_id: 发送者ID
    • receiver_id: 接收者ID
    • content: 消息内容
    • is_read: 是否已读
    • create_time: 创建时间

2.3 功能模块设计

系统主要包含以下功能模块:

  1. 用户模块:负责用户注册、登录、个人信息管理等功能
  2. 商品模块:负责商品发布、编辑、下架、搜索等功能
  3. 交易模块:负责订单创建、支付、确认收货等功能
  4. 消息模块:负责用户间即时通讯、系统通知等功能
  5. 评价模块:负责交易评价、信用评分等功能
  6. 管理模块:负责平台运营、内容审核等功能

3. 系统实现

3.1 开发环境搭建

3.1.1 Python环境配置
# 创建虚拟环境
python -m venv venv# 激活虚拟环境
# Windows
venv\Scripts\activate
# Linux/Mac
source venv/bin/activate# 安装依赖
pip install -r requirements.txt
3.1.2 项目依赖
# requirements.txt
Flask==2.0.1
Flask-SQLAlchemy==2.5.1
Flask-Login==0.5.0
Flask-WTF==0.15.1
Flask-Mail==0.9.1
Flask-Migrate==3.1.0
Flask-SocketIO==5.1.1
Pillow==8.3.1
PyMySQL==1.0.2
redis==3.5.3
elasticsearch==7.14.0
pika==1.2.0
gunicorn==20.1.0

3.2 核心功能实现

3.2.1 用户认证模块
# app/models/user.py
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin
from app import db, login_managerclass User(UserMixin, db.Model):__tablename__ = 'users'id = db.Column(db.Integer, primary_key=True)username = db.Column(db.String(64), unique=True, index=True)email = db.Column(db.String(120), unique=True, index=True)password_hash = db.Column(db.String(128))phone = db.Column(db.String(20))avatar = db.Column(db.String(200))school_id = db.Column(db.Integer, db.ForeignKey('schools.id'))credit_score = db.Column(db.Integer, default=100)create_time = db.Column(db.DateTime, default=datetime.utcnow)update_time = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)items = db.relationship('Item', backref='seller', lazy='dynamic')@propertydef password(self):raise AttributeError('password is not a readable attribute')@password.setterdef password(self, password):self.password_hash = generate_password_hash(password)def verify_password(self, password):return check_password_hash(self.password_hash, password)@login_manager.user_loader
def load_user(user_id):return User.query.get(int(user_id))
3.2.2 商品管理模块
# app/models/item.py
from app import db
from datetime import datetimeclass Item(db.Model):__tablename__ = 'items'id = db.Column(db.Integer, primary_key=True)title = db.Column(db.String(100), index=True)description = db.Column(db.Text)price = db.Column(db.Float)original_price = db.Column(db.Float)category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))user_id = db.Column(db.Integer, db.ForeignKey('users.id'))status = db.Column(db.Integer, default=0)  # 0: 在售, 1: 已售, 2: 下架view_count = db.Column(db.Integer, default=0)favorite_count = db.Column(db.Integer, default=0)create_time = db.Column(db.DateTime, default=datetime.utcnow)update_time = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)images = db.relationship('ItemImage', backref='item', lazy='dynamic')orders = db.relationship('Order', backref='item', lazy='dynamic')def to_dict(self):return {'id': self.id,'title': self.title,'description': self.description,'price': self.price,'original_price': self.original_price,'category_id': self.category_id,'user_id': self.user_id,'status': self.status,'view_count': self.view_count,'favorite_count': self.favorite_count,'create_time': self.create_time.strftime('%Y-%m-%d %H:%M:%S'),'cover_image': self.get_cover_image()}def get_cover_image(self):cover = self.images.filter_by(is_cover=True).first()if cover:return cover.image_urlimage = self.images.first()return image.image_url if image else ''
3.2.3 交易流程实现
# app/models/order.py
from app import db
from datetime import datetimeclass Order(db.Model):__tablename__ = 'orders'id = db.Column(db.Integer, primary_key=True)item_id = db.Column(db.Integer, db.ForeignKey('items.id'))seller_id = db.Column(db.Integer, db.ForeignKey('users.id'))buyer_id = db.Column(db.Integer, db.ForeignKey('users.id'))price = db.Column(db.Float)status = db.Column(db.Integer, default=0)  # 0: 待付款, 1: 待发货, 2: 待收货, 3: 已完成, 4: 已取消create_time = db.Column(db.DateTime, default=datetime.utcnow)update_time = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)reviews = db.relationship('Review', backref='order', lazy='dynamic')def to_dict(self):return {'id': self.id,'item_id': self.item_id,'seller_id': self.seller_id,'buyer_id': self.buyer_id,'price': self.price,'status': self.status,'create_time': self.create_time.strftime('%Y-%m-%d %H:%M:%S'),'update_time': self.update_time.strftime('%Y-%m-%d %H:%M:%S')}
3.2.4 即时通讯实现
# app/socket.py
from flask_socketio import SocketIO, emit, join_room, leave_room
from flask_login import current_user
from app import socketio, db
from app.models import Message, User@socketio.on('connect')
def handle_connect():if current_user.is_authenticated:join_room(current_user.id)emit('response', {'data': '连接成功'})@socketio.on('send_message')
def handle_send_message(data):if current_user.is_authenticated:receiver_id = data.get('receiver_id')content = data.get('content')# 保存消息到数据库message = Message(sender_id=current_user.id,receiver_id=receiver_id,content=content)db.session.add(message)db.session.commit()# 发送消息给接收者emit('new_message', {'id': message.id,'sender_id': current_user.id,'sender_name': current_user.username,'content': content,'create_time': message.create_time.strftime('%Y-%m-%d %H:%M:%S')}, room=receiver_id)

3.3 系统安全性实现

3.3.1 用户密码加密

使用Werkzeug提供的安全哈希函数对用户密码进行加密存储,确保即使数据库泄露,用户密码也不会被直接获取。

3.3.2 CSRF防护

使用Flask-WTF提供的CSRF保护功能,防止跨站请求伪造攻击。

# app/__init__.py
from flask_wtf.csrf import CSRFProtectcsrf = CSRFProtect()def create_app(config_name):app = Flask(__name__)# ...csrf.init_app(app)# ...return app
3.3.3 XSS防护

使用Jinja2模板引擎的自动转义功能,防止跨站脚本攻击。

<!-- 在模板中安全地显示用户输入内容 -->
<div class="item-description">{{ item.description|safe }}</div>
3.3.4 SQL注入防护

使用SQLAlchemy ORM,避免直接拼接SQL语句,防止SQL注入攻击。

# 安全的查询方式
items = Item.query.filter(Item.title.like(f'%{keyword}%')).all()# 不安全的查询方式(避免使用)
# items = db.session.execute(f"SELECT * FROM items WHERE title LIKE '%{keyword}%'").fetchall()

3.4 性能优化

3.4.1 数据库优化
  1. 为常用查询字段添加索引
  2. 使用连接池管理数据库连接
  3. 对大表进行分区
# 为常用查询字段添加索引
class Item(db.Model):__tablename__ = 'items'id = db.Column(db.Integer, primary_key=True)title = db.Column(db.String(100), index=True)  # 添加索引category_id = db.Column(db.Integer, db.ForeignKey('categories.id'), index=True)  # 添加索引user_id = db.Column(db.Integer, db.ForeignKey('users.id'), index=True)  # 添加索引status = db.Column(db.Integer, default=0, index=True)  # 添加索引
3.4.2 缓存优化

使用Redis缓存热门商品、首页数据等,减轻数据库压力。

# app/utils/cache.py
import redis
import jsonredis_client = redis.Redis(host='localhost', port=6379, db=0)def set_cache(key, value, expire=3600):"""设置缓存"""if isinstance(value, (dict, list)):value = json.dumps(value)redis_client.set(key, value, ex=expire)def get_cache(key):"""获取缓存"""value = redis_client.get(key)if value:try:return json.loads(value)except:return value.decode('utf-8')return Nonedef clear_cache(pattern):"""清除匹配模式的缓存"""keys = redis_client.keys(pattern)if keys:redis_client.delete(*keys)
3.4.3 搜索优化

使用Elasticsearch实现全文搜索,提高搜索效率和准确性。

# app/utils/search.py
from elasticsearch import Elasticsearch
from app.models import Itemes = Elasticsearch(['http://localhost:9200'])def index_item(item):"""索引商品数据"""doc = {'id': item.id,'title': item.title,'description': item.description,'price': item.price,'category_id': item.category_id,'status': item.status,'create_time': item.create_time.strftime('%Y-%m-%d %H:%M:%S')}es.index(index='items', id=item.id, body=doc)def search_items(keyword, page=1, per_page=10):"""搜索商品"""query = {'query': {'multi_match': {'query': keyword,'fields': ['title^3', 'description']  # 标题权重更高}},'from': (page - 1) * per_page,'size': per_page}result = es.search(index='items', body=query)return result['hits']

4. 系统部署

4.1 Docker容器化部署

4.1.1 Dockerfile
# Dockerfile
FROM python:3.9-slimWORKDIR /appCOPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txtCOPY . .ENV FLASK_APP=run.py
ENV FLASK_ENV=productionEXPOSE 5000CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "run:app"]
4.1.2 Docker Compose配置
# docker-compose.yml
version: '3'services:web:build: .ports:- "5000:5000"depends_on:- db- redis- elasticsearchenvironment:- DATABASE_URL=mysql+pymysql://user:password@db:3306/campus_trading- REDIS_URL=redis://redis:6379/0- ELASTICSEARCH_URL=http://elasticsearch:9200volumes:- ./uploads:/app/uploadsdb:image: mysql:8.0environment:- MYSQL_ROOT_PASSWORD=rootpassword- MYSQL_DATABASE=campus_trading- MYSQL_USER=user- MYSQL_PASSWORD=passwordvolumes:- mysql_data:/var/lib/mysqlredis:image: redis:6.2volumes:- redis_data:/dataelasticsearch:image: elasticsearch:7.14.0environment:- discovery.type=single-node- "ES_JAVA_OPTS=-Xms512m -Xmx512m"volumes:- es_data:/usr/share/elasticsearch/datanginx:image: nginx:1.21ports:- "80:80"volumes:- ./nginx.conf:/etc/nginx/conf.d/default.conf- ./uploads:/usr/share/nginx/html/uploadsdepends_on:- webvolumes:mysql_data:redis_data:es_data:

4.2 Nginx配置

# nginx.conf
server {listen 80;server_name campus-trading.example.com;location /static {alias /usr/share/nginx/html/static;expires 30d;}location /uploads {alias /usr/share/nginx/html/uploads;expires 30d;}location / {proxy_pass http://web:5000;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}location /socket.io {proxy_pass http://web:5000/socket.io;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade";proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;}
}

4.3 数据备份策略

  1. 数据库定时备份:每日凌晨自动备份MySQL数据库
  2. 文件定时备份:每周备份一次上传的图片文件
  3. 备份文件存储:备份文件存储在云存储服务中,确保数据安全
# 数据库备份脚本 backup_db.sh
#!/bin/bash
DATE=$(date +%Y%m%d)
BACKUP_DIR=/backup/mysqlmkdir -p $BACKUP_DIRdocker exec -it campus-trading_db_1 mysqldump -u root -p<password> campus_trading > $BACKUP_DIR/campus_trading_$DATE.sql# 保留最近30天的备份
find $BACKUP_DIR -name "*.sql" -type f -mtime +30 -delete

5. 系统测试

5.1 单元测试

使用Python的unittest框架对各个模块进行单元测试,确保每个功能模块的正确性。

# tests/test_user_model.py
import unittest
from app import create_app, db
from app.models import Userclass UserModelTestCase(unittest.TestCase):def setUp(self):self.app = create_app('testing')self.app_context = self.app.app_context()self.app_context.push()db.create_all()def tearDown(self):db.session.remove()db.drop_all()self.app_context.pop()def test_password_setter(self):u = User(password='cat')self.assertTrue(u.password_hash is not None)def test_no_password_getter(self):u = User(password='cat')with self.assertRaises(AttributeError):u.passworddef test_password_verification(self):u = User(password='cat')self.assertTrue(u.verify_password('cat'))self.assertFalse(u.verify_password('dog'))

5.2 接口测试

使用Postman或Python的requests库对API接口进行测试,确保接口的正确性和稳定性。

# tests/test_api.py
import unittest
import json
from app import create_app, db
from app.models import User, Itemclass APITestCase(unittest.TestCase):def setUp(self):self.app = create_app('testing')self.app_context = self.app.app_context()self.app_context.push()db.create_all()self.client = self.app.test_client()def tearDown(self):db.session.remove()db.drop_all()self.app_context.pop()def test_get_items(self):# 创建测试数据user = User(username='test', email='test@example.com', password='password')db.session.add(user)item = Item(title='Test Item', description='Test Description', price=100, user_id=1)db.session.add(item)db.session.commit()# 测试获取商品列表接口response = self.client.get('/api/items')self.assertEqual(response.status_code, 200)data = json.loads(response.data)self.assertEqual(len(data['items']), 1)self.assertEqual(data['items'][0]['title'], 'Test Item')

5.3 性能测试

使用Apache JMeter或Locust对系统进行性能测试,评估系统在高并发情况下的表现。

# locustfile.py
from locust import HttpUser, task, betweenclass WebsiteUser(HttpUser):wait_time = between(1, 5)@task(2)def index(self):self.client.get("/")@task(1)def view_item(self):item_id = 1self.client.get(f"/items/{item_id}")@task(1)def search_items(self):self.client.get("/search?keyword=book")

6. 项目总结

6.1 项目成果

  1. 成功构建了一个功能完善的校园二手物品交易平台
  2. 实现了用户注册、登录、身份验证等基础功能
  3. 支持物品发布、搜索、收藏、交易等核心功能
  4. 提供了即时通讯功能,方便买卖双方沟通
  5. 实现了交易评价和信用体系,保障交易安全
  6. 提供了数据分析功能,了解平台运营情况

6.2 技术亮点

  1. 前后端分离架构:提高了开发效率和系统可维护性
  2. RESTful API设计:规范的API设计,便于前端调用和第三方集成
  3. WebSocket实时通讯:实现了用户间的即时通讯功能
  4. Elasticsearch全文搜索:提供了高效的商品搜索功能
  5. Redis缓存优化:减轻了数据库压力,提高了系统响应速度
  6. Docker容器化部署:简化了部署流程,提高了系统可移植性

6.3 项目不足与改进方向

  1. 移动端适配:当前系统主要针对PC端设计,移动端体验有待提升
  2. 支付系统集成:可以集成第三方支付系统,提供更便捷的支付方式
  3. 智能推荐系统:基于用户行为和偏好,推荐可能感兴趣的商品
  4. 社交功能增强:增加关注、点赞、分享等社交功能,提高用户粘性
  5. 安全性增强:加强对敏感操作的风控措施,防止欺诈行为

源代码

Directory Content Summary

Source Directory: ./campus_trading_platform

Directory Structure

campus_trading_platform/.gitignoreREADME.mdrequirements.txtrun.pyapp/__init__.pyforms/auth.pyitem.pytransaction.pyuser.pymodels/item.pyuser.pyroutes/auth.pyitem.pymain.pyorder.pytransaction.pyuser.pystatic/css/style.cssimages/js/templates/base.htmlauth/login.htmlregister.htmlreset_password.htmlreset_password_request.htmlemail/reset_password.htmlreset_password.txtitem/buy.htmlcategory.htmldetail.htmledit.htmlnew.htmlsearch.htmlmain/about.htmlhelp.htmlindex.htmlorder/detail.htmllist.htmlreview.htmltransaction/dispute.htmlpayment.htmlrecords.htmlstatistics.htmluser/change_password.htmldashboard.htmledit_profile.htmlprofile.htmlutils/decorators.pyemail.pyconfig/config.pydatabase/schema.sqlmigrations/tests/

File Contents

.gitignore

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg# Flask
instance/
.webassets-cache# Virtual Environment
venv/
ENV/
env/# Database
*.db
*.sqlite3# Environment Variables
.env
.flaskenv# IDE
.idea/
.vscode/
*.swp
*.swo# OS
.DS_Store
Thumbs.db# Uploaded files
app/static/uploads/# Logs
logs/
*.log# Migrations
migrations/

README.md

# 校园二手物品交易平台## 项目简介校园二手物品交易平台是一个专为大学校园设计的二手物品交易网站,旨在为校园内的学生提供一个安全、便捷、高效的二手物品交易环境。通过这个平台,学生可以发布闲置物品、浏览他人发布的商品、进行线上沟通和线下交易。## 功能特点- **用户认证系统**:支持学生注册、登录、密码重置等功能
- **个人资料管理**:用户可以编辑个人信息、上传头像、修改密码等
- **商品管理**:发布、编辑、下架商品,上传商品图片
- **商品浏览**:按分类浏览商品,搜索商品,查看商品详情
- **收藏功能**:收藏感兴趣的商品,方便后续查看
- **即时通讯**:买卖双方可以通过站内消息进行沟通
- **交易管理**:记录交易状态,确认交易完成
- **评价系统**:交易完成后可以对对方进行评价
- **信用体系**:基于用户评价建立信用评分## 技术栈- **后端**:Python + Flask
- **前端**:HTML + CSS + JavaScript + Bootstrap 5
- **数据库**:SQLite (开发) / MySQL (生产)
- **ORM**:SQLAlchemy
- **表单处理**:Flask-WTF
- **用户认证**:Flask-Login
- **邮件发送**:Flask-Mail## 安装指南### 环境要求- Python 3.8+
- pip### 安装步骤1. 克隆仓库
```bash
git clone https://github.com/yourusername/campus_trading_platform.git
cd campus_trading_platform
  1. 创建并激活虚拟环境
# Windows
python -m venv venv
venv\Scripts\activate# Linux/Mac
python -m venv venv
source venv/bin/activate
  1. 安装依赖
pip install -r requirements.txt
  1. 设置环境变量
    创建一个 .env 文件在项目根目录,并添加以下内容:
SECRET_KEY=your-secret-key
DATABASE_URL=sqlite:///app.db
MAIL_SERVER=smtp.example.com
MAIL_PORT=587
MAIL_USE_TLS=True
MAIL_USERNAME=your-email@example.com
MAIL_PASSWORD=your-email-password
  1. 初始化数据库
flask db init
flask db migrate -m "Initial migration"
flask db upgrade
  1. 运行应用
python run.py

应用将在 http://localhost:5000 运行。

项目结构

campus_trading_platform/
├── app/                    # 应用主目录
│   ├── __init__.py         # 应用初始化
│   ├── models/             # 数据模型
│   ├── routes/             # 路由处理
│   ├── forms/              # 表单定义
│   ├── utils/              # 工具函数
│   ├── static/             # 静态文件
│   └── templates/          # HTML模板
├── config/                 # 配置文件
├── migrations/             # 数据库迁移文件
├── tests/                  # 测试代码
├── .env                    # 环境变量
├── requirements.txt        # 依赖列表
└── run.py                  # 应用入口

贡献指南

  1. Fork 项目
  2. 创建特性分支 (git checkout -b feature/amazing-feature)
  3. 提交更改 (git commit -m 'Add some amazing feature')
  4. 推送到分支 (git push origin feature/amazing-feature)
  5. 创建 Pull Request

许可证

本项目采用 MIT 许可证 - 详情请参阅 LICENSE 文件。

联系方式

项目维护者 - your-email@example.com

项目链接:https://github.com/yourusername/campus_trading_platform


### requirements.txt```text/plain
Flask==2.2.3
Flask-SQLAlchemy==3.0.3
Flask-Login==0.6.2
Flask-WTF==1.1.1
Flask-Mail==0.9.1
Flask-Migrate==4.0.4
email-validator==2.0.0
Pillow==9.5.0
python-dotenv==1.0.0
itsdangerous==2.1.2
Werkzeug==2.2.3
Jinja2==3.1.2
SQLAlchemy==2.0.4
WTForms==3.0.1
alembic==1.10.2
blinker==1.5.0
click==8.1.3
MarkupSafe==2.1.2

run.py

from app import create_app
import osapp = create_app()if __name__ == '__main__':# 获取环境变量中的端口,如果没有则默认使用5000port = int(os.environ.get('PORT', 5000))app.run(host='0.0.0.0', port=port, debug=True)

app_init_.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from flask_mail import Mail
from config.config import Config# 初始化扩展
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
login_manager.login_view = 'auth.login'  # 设置登录视图的端点
login_manager.login_message = '请先登录才能访问此页面'
mail = Mail()def create_app(config_class=Config):"""创建并配置Flask应用"""app = Flask(__name__)app.config.from_object(config_class)# 初始化扩展db.init_app(app)migrate.init_app(app, db)login_manager.init_app(app)mail.init_app(app)# 注册蓝图from app.routes.auth import auth_bpfrom app.routes.main import main_bpfrom app.routes.user import user_bpapp.register_blueprint(auth_bp)app.register_blueprint(main_bp)app.register_blueprint(user_bp)return app

app\forms\auth.py

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Length, Email, Regexp, EqualTo, ValidationErrorfrom app.models.user import Userclass LoginForm(FlaskForm):"""用户登录表单"""email = StringField('邮箱', validators=[DataRequired(message='请输入邮箱'),Email(message='请输入有效的邮箱地址')])password = PasswordField('密码', validators=[DataRequired(message='请输入密码')])remember_me = BooleanField('记住我')submit = SubmitField('登录')class RegistrationForm(FlaskForm):"""用户注册表单"""username = StringField('用户名', validators=[DataRequired(message='请输入用户名'),Length(min=2, max=20, message='用户名长度必须在2-20个字符之间'),Regexp('^[A-Za-z0-9_\u4e00-\u9fa5]+$', message='用户名只能包含字母、数字、下划线和汉字')])email = StringField('邮箱', validators=[DataRequired(message='请输入邮箱'),Email(message='请输入有效的邮箱地址')])student_id = StringField('学号', validators=[DataRequired(message='请输入学号'),Length(min=8, max=12, message='学号长度必须在8-12个字符之间'),Regexp('^[0-9]+$', message='学号只能包含数字')])phone = StringField('手机号', validators=[DataRequired(message='请输入手机号'),Length(min=11, max=11, message='手机号必须是11位'),Regexp('^1[3-9]\d{9}$', message='请输入有效的手机号')])password = PasswordField('密码', validators=[DataRequired(message='请输入密码'),Length(min=6, message='密码长度不能少于6个字符')])confirm_password = PasswordField('确认密码', validators=[DataRequired(message='请确认密码'),EqualTo('password', message='两次输入的密码不一致')])submit = SubmitField('注册')def validate_username(self, field):"""验证用户名是否已存在"""user = User.query.filter_by(username=field.data).first()if user:raise ValidationError('该用户名已被使用')def validate_email(self, field):"""验证邮箱是否已存在"""user = User.query.filter_by(email=field.data).first()if user:raise ValidationError('该邮箱已被注册')def validate_student_id(self, field):"""验证学号是否已存在"""user = User.query.filter_by(student_id=field.data).first()if user:raise ValidationError('该学号已被注册')def validate_phone(self, field):"""验证手机号是否已存在"""user = User.query.filter_by(phone=field.data).first()if user:raise ValidationError('该手机号已被注册')class ResetPasswordRequestForm(FlaskForm):"""请求重置密码表单"""email = StringField('邮箱', validators=[DataRequired(message='请输入邮箱'),Email(message='请输入有效的邮箱地址')])submit = SubmitField('重置密码')class ResetPasswordForm(FlaskForm):"""重置密码表单"""password = PasswordField('新密码', validators=[DataRequired(message='请输入新密码'),Length(min=6, message='密码长度不能少于6个字符')])confirm_password = PasswordField('确认新密码', validators=[DataRequired(message='请确认新密码'),EqualTo('password', message='两次输入的密码不一致')])submit = SubmitField('重置密码')

app\forms\item.py

from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed, FileRequired
from wtforms import StringField, TextAreaField, FloatField, SelectField, SubmitField, MultipleFileField
from wtforms.validators import DataRequired, Length, NumberRange, Optionalclass ItemForm(FlaskForm):"""商品发布/编辑表单"""title = StringField('商品标题', validators=[DataRequired(message='请输入商品标题'),Length(min=2, max=100, message='标题长度必须在2-100个字符之间')])category = SelectField('商品分类', coerce=int, validators=[DataRequired(message='请选择商品分类')])description = TextAreaField('商品描述', validators=[DataRequired(message='请输入商品描述'),Length(min=10, max=2000, message='描述长度必须在10-2000个字符之间')])price = FloatField('售价(¥)', validators=[DataRequired(message='请输入售价'),NumberRange(min=0.01, message='价格必须大于0')])original_price = FloatField('原价(¥)', validators=[Optional(),NumberRange(min=0.01, message='价格必须大于0')])condition = SelectField('物品状况', choices=[('brand_new', '全新'),('like_new', '几乎全新'),('slightly_used', '轻微使用痕迹'),('used', '使用过'),('heavily_used', '重度使用')], validators=[DataRequired(message='请选择物品状况')])location = StringField('交易地点', validators=[DataRequired(message='请输入交易地点'),Length(max=100, message='地点长度不能超过100个字符')])images = MultipleFileField('商品图片', validators=[FileAllowed(['jpg', 'jpeg', 'png', 'gif'], message='只允许上传jpg, jpeg, png, gif格式的图片')])submit = SubmitField('发布商品')class ItemSearchForm(FlaskForm):"""商品搜索表单"""keyword = StringField('关键词', validators=[Optional(),Length(max=50, message='关键词长度不能超过50个字符')])category = SelectField('分类', coerce=int, choices=[(0, '所有分类')], validators=[Optional()])min_price = FloatField('最低价格', validators=[Optional(),NumberRange(min=0, message='价格不能为负数')])max_price = FloatField('最高价格', validators=[Optional(),NumberRange(min=0, message='价格不能为负数')])condition = SelectField('物品状况', choices=[('', '所有状况'),('brand_new', '全新'),('like_new', '几乎全新'),('slightly_used', '轻微使用痕迹'),('used', '使用过'),('heavily_used', '重度使用')], validators=[Optional()])sort = SelectField('排序方式', choices=[('newest', '最新发布'),('price_asc', '价格从低到高'),('price_desc', '价格从高到低'),('popular', '最受欢迎')], default='newest', validators=[Optional()])submit = SubmitField('搜索')class OrderForm(FlaskForm):"""订单创建表单"""message = TextAreaField('留言', validators=[Optional(),Length(max=500, message='留言长度不能超过500个字符')])submit = SubmitField('确认购买')class ReviewForm(FlaskForm):"""评价表单"""rating = SelectField('评分', choices=[(5, '★★★★★ 非常满意'),(4, '★★★★☆ 满意'),(3, '★★★☆☆ 一般'),(2, '★★☆☆☆ 不满意'),(1, '★☆☆☆☆ 非常不满意')], coerce=int, validators=[DataRequired(message='请选择评分')])content = TextAreaField('评价内容', validators=[DataRequired(message='请输入评价内容'),Length(min=5, max=500, message='评价内容长度必须在5-500个字符之间')])submit = SubmitField('提交评价')

app\forms\transaction.py

from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SelectField, DecimalField, SubmitField, HiddenField, RadioField
from wtforms.validators import DataRequired, Length, NumberRange, Optional
from flask_wtf.file import FileField, FileAllowedclass PaymentForm(FlaskForm):"""支付表单"""payment_method = SelectField('支付方式', choices=[('alipay', '支付宝'),('wechat', '微信支付'),('campus_card', '校园卡')],validators=[DataRequired(message='请选择支付方式')])agreement = RadioField('同意条款', choices=[('agree', '我已阅读并同意《交易协议》和《支付条款》')],validators=[DataRequired(message='请阅读并同意条款')])submit = SubmitField('确认支付')class DisputeForm(FlaskForm):"""纠纷申请表单"""reason = SelectField('纠纷原因', choices=[('item_not_as_described', '商品与描述不符'),('item_damaged', '商品损坏'),('seller_not_responding', '卖家不回应'),('buyer_not_responding', '买家不回应'),('payment_issue', '支付问题'),('other', '其他原因')],validators=[DataRequired(message='请选择纠纷原因')])description = TextAreaField('详细说明', validators=[DataRequired(message='请提供详细说明'),Length(min=10, max=500, message='详细说明应在10-500个字符之间')])evidence_images = FileField('证据图片(可选,最多3张)', validators=[Optional(),FileAllowed(['jpg', 'jpeg', 'png'], '只允许上传jpg, jpeg, png格式的图片')])preferred_solution = SelectField('期望解决方案', choices=[('refund', '退款'),('exchange', '换货'),('partial_refund', '部分退款'),('mediation', '平台调解'),('other', '其他')],validators=[DataRequired(message='请选择期望的解决方案')])contact_phone = StringField('联系电话', validators=[DataRequired(message='请提供联系电话'),Length(min=11, max=11, message='请输入11位手机号码')])submit = SubmitField('提交纠纷申请')class RefundForm(FlaskForm):"""退款申请表单"""refund_amount = DecimalField('退款金额', validators=[DataRequired(message='请输入退款金额'),NumberRange(min=0.01, message='退款金额必须大于0')])reason = SelectField('退款原因', choices=[('change_mind', '买家改变主意'),('item_not_as_described', '商品与描述不符'),('item_damaged', '商品损坏'),('wrong_item', '收到错误商品'),('not_received', '未收到商品'),('other', '其他原因')],validators=[DataRequired(message='请选择退款原因')])description = TextAreaField('详细说明', validators=[DataRequired(message='请提供详细说明'),Length(min=10, max=500, message='详细说明应在10-500个字符之间')])evidence_images = FileField('证据图片(可选,最多3张)', validators=[Optional(),FileAllowed(['jpg', 'jpeg', 'png'], '只允许上传jpg, jpeg, png格式的图片')])submit = SubmitField('申请退款')class TransactionFeedbackForm(FlaskForm):"""交易反馈表单"""satisfaction = SelectField('交易满意度', choices=[('5', '非常满意'),('4', '满意'),('3', '一般'),('2', '不满意'),('1', '非常不满意')],validators=[DataRequired(message='请选择交易满意度')])platform_feedback = TextAreaField('平台使用反馈(可选)', validators=[Optional(),Length(max=500, message='反馈内容不能超过500个字符')])improvement_suggestions = TextAreaField('改进建议(可选)', validators=[Optional(),Length(max=500, message='建议内容不能超过500个字符')])submit = SubmitField('提交反馈')

app\forms\user.py

from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed
from wtforms import StringField, TextAreaField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Length, Email, Regexp, EqualTo, ValidationError
from flask_login import current_userfrom app.models.user import Userclass EditProfileForm(FlaskForm):"""编辑个人资料表单"""username = StringField('用户名', validators=[DataRequired(message='请输入用户名'),Length(min=2, max=20, message='用户名长度必须在2-20个字符之间'),Regexp('^[A-Za-z0-9_\u4e00-\u9fa5]+$', message='用户名只能包含字母、数字、下划线和汉字')])phone = StringField('手机号', validators=[DataRequired(message='请输入手机号'),Length(min=11, max=11, message='手机号必须是11位'),Regexp('^1[3-9]\d{9}$', message='请输入有效的手机号')])bio = TextAreaField('个人简介', validators=[Length(max=200, message='个人简介不能超过200个字符')])dormitory = StringField('宿舍地址', validators=[Length(max=100, message='宿舍地址不能超过100个字符')])avatar = FileField('头像', validators=[FileAllowed(['jpg', 'jpeg', 'png', 'gif'], '只允许上传图片文件')])submit = SubmitField('保存修改')def validate_username(self, field):"""验证用户名是否已存在"""if field.data != current_user.username:user = User.query.filter_by(username=field.data).first()if user:raise ValidationError('该用户名已被使用')def validate_phone(self, field):"""验证手机号是否已存在"""if field.data != current_user.phone:user = User.query.filter_by(phone=field.data).first()if user:raise ValidationError('该手机号已被注册')class ChangePasswordForm(FlaskForm):"""修改密码表单"""current_password = PasswordField('当前密码', validators=[DataRequired(message='请输入当前密码')])new_password = PasswordField('新密码', validators=[DataRequired(message='请输入新密码'),Length(min=6, message='密码长度不能少于6个字符')])confirm_password = PasswordField('确认新密码', validators=[DataRequired(message='请确认新密码'),EqualTo('new_password', message='两次输入的密码不一致')])submit = SubmitField('修改密码')

app\models\item.py

from datetime import datetime
import os
from flask import current_app
from app import db
import json
from sqlalchemy.ext.hybrid import hybrid_propertyclass Category(db.Model):"""商品分类模型"""__tablename__ = 'categories'id = db.Column(db.Integer, primary_key=True)name = db.Column(db.String(50), unique=True, nullable=False)description = db.Column(db.String(200))icon = db.Column(db.String(50))  # 分类图标items = db.relationship('Item', backref='category', lazy='dynamic')def __repr__(self):return f'<Category {self.name}>'class Item(db.Model):"""商品模型"""__tablename__ = 'items'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)original_price = db.Column(db.Float)  # 原价condition = db.Column(db.String(20), nullable=False)  # 物品状况:全新、几成新等images = db.Column(db.Text)  # 存储图片路径的JSON字符串location = db.Column(db.String(100))  # 交易地点views = db.Column(db.Integer, default=0)  # 浏览次数is_sold = db.Column(db.Boolean, default=False)  # 是否已售出is_active = db.Column(db.Boolean, default=True)  # 是否处于活跃状态created_at = db.Column(db.DateTime, default=datetime.utcnow)updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)# 外键seller_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)category_id = db.Column(db.Integer, db.ForeignKey('categories.id'), nullable=False)# 关系orders = db.relationship('Order', backref='item', lazy='dynamic')favorites = db.relationship('Favorite', backref='item', lazy='dynamic')def __repr__(self):return f'<Item {self.title}>'@propertydef image_list(self):"""返回图片路径列表"""if not self.images:return []return json.loads(self.images)def add_image(self, image_path):"""添加图片路径"""images = self.image_listimages.append(image_path)self.images = json.dumps(images)def remove_image(self, image_path):"""移除图片路径"""images = self.image_listif image_path in images:images.remove(image_path)self.images = json.dumps(images)return Truereturn Falsedef get_main_image_url(self):"""获取主图URL"""images = self.image_listif images:return os.path.join('/static/uploads/items', images[0])return '/static/images/default_item.jpg'def increment_view(self):"""增加浏览次数"""self.views += 1db.session.commit()@hybrid_propertydef is_new(self):"""判断是否为新发布商品(7天内)"""return (datetime.utcnow() - self.created_at).days <= 7@propertydef discount_percentage(self):"""计算折扣百分比"""if self.original_price and self.original_price > 0:return int((1 - self.price / self.original_price) * 100)return 0class Favorite(db.Model):"""收藏模型"""__tablename__ = 'favorites'id = db.Column(db.Integer, primary_key=True)user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)item_id = db.Column(db.Integer, db.ForeignKey('items.id'), nullable=False)created_at = db.Column(db.DateTime, default=datetime.utcnow)__table_args__ = (db.UniqueConstraint('user_id', 'item_id', name='unique_user_item'),)def __repr__(self):return f'<Favorite {self.user_id} - {self.item_id}>'class Order(db.Model):"""订单模型"""__tablename__ = 'orders'id = db.Column(db.Integer, primary_key=True)order_number = db.Column(db.String(20), unique=True, nullable=False)price = db.Column(db.Float, nullable=False)status = db.Column(db.String(20), default='pending')  # pending, paid, completed, cancelledmessage = db.Column(db.Text)  # 买家留言created_at = db.Column(db.DateTime, default=datetime.utcnow)updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)# 外键buyer_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)seller_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)item_id = db.Column(db.Integer, db.ForeignKey('items.id'), nullable=False)# 关系reviews = db.relationship('Review', backref='order', lazy='dynamic')def __repr__(self):return f'<Order {self.order_number}>'@propertydef is_reviewed(self):"""判断订单是否已评价"""return self.reviews.count() > 0@staticmethoddef generate_order_number():"""生成订单号"""import randomimport timereturn f"O{int(time.time())}{random.randint(1000, 9999)}"class Review(db.Model):"""评价模型"""__tablename__ = 'reviews'id = db.Column(db.Integer, primary_key=True)content = db.Column(db.Text, nullable=False)rating = db.Column(db.Integer, nullable=False)  # 1-5星评价created_at = db.Column(db.DateTime, default=datetime.utcnow)# 外键order_id = db.Column(db.Integer, db.ForeignKey('orders.id'), nullable=False)reviewer_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)target_user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)def __repr__(self):return f'<Review {self.id} - {self.rating}星>'

app\models\user.py

from datetime import datetime
from flask import current_app
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
import jwt
from time import time
import uuid
import osfrom app import db, login_managerclass User(UserMixin, db.Model):"""用户模型"""__tablename__ = 'users'id = db.Column(db.Integer, primary_key=True)username = db.Column(db.String(64), unique=True, index=True, nullable=False)email = db.Column(db.String(120), unique=True, index=True, nullable=False)password_hash = db.Column(db.String(128))student_id = db.Column(db.String(20), unique=True, index=True, nullable=False)phone = db.Column(db.String(20), unique=True)avatar = db.Column(db.String(120), default='default_avatar.png')bio = db.Column(db.Text)dormitory = db.Column(db.String(100))is_active = db.Column(db.Boolean, default=True)is_admin = db.Column(db.Boolean, default=False)credit_score = db.Column(db.Integer, default=100)  # 信用评分,初始为100分last_seen = db.Column(db.DateTime, default=datetime.utcnow)created_at = db.Column(db.DateTime, default=datetime.utcnow)# 关系items = db.relationship('Item', backref='seller', lazy='dynamic')orders = db.relationship('Order', backref='buyer', lazy='dynamic', foreign_keys='Order.buyer_id')sold_orders = db.relationship('Order', backref='seller', lazy='dynamic', foreign_keys='Order.seller_id')reviews_received = db.relationship('Review', backref='reviewee', lazy='dynamic', foreign_keys='Review.reviewee_id')reviews_given = db.relationship('Review', backref='reviewer', lazy='dynamic', foreign_keys='Review.reviewer_id')messages_sent = db.relationship('Message', backref='sender', lazy='dynamic', foreign_keys='Message.sender_id')messages_received = db.relationship('Message', backref='recipient', lazy='dynamic', foreign_keys='Message.recipient_id')notifications = db.relationship('Notification', backref='user', lazy='dynamic')favorites = db.relationship('Favorite', backref='user', lazy='dynamic')def __init__(self, **kwargs):super(User, self).__init__(**kwargs)# 如果用户邮箱是管理员邮箱,则设置为管理员if self.email == current_app.config['ADMIN_EMAIL']:self.is_admin = Truedef __repr__(self):return f'<User {self.username}>'@propertydef password(self):"""密码属性不可读"""raise AttributeError('密码不是可读属性')@password.setterdef password(self, password):"""设置密码"""self.password_hash = generate_password_hash(password)def verify_password(self, password):"""验证密码"""return check_password_hash(self.password_hash, password)def ping(self):"""更新用户最后访问时间"""self.last_seen = datetime.utcnow()db.session.add(self)def get_reset_password_token(self, expires_in=3600):"""生成密码重置令牌"""return jwt.encode({'reset_password': self.id, 'exp': time() + expires_in},current_app.config['SECRET_KEY'],algorithm='HS256')@staticmethoddef verify_reset_password_token(token):"""验证密码重置令牌"""try:id = jwt.decode(token,current_app.config['SECRET_KEY'],algorithms=['HS256'])['reset_password']except:return Nonereturn User.query.get(id)def update_credit_score(self, points):"""更新用户信用评分"""self.credit_score += points# 确保信用评分在0-100之间if self.credit_score > 100:self.credit_score = 100elif self.credit_score < 0:self.credit_score = 0db.session.add(self)def add_notification(self, name, data):"""添加通知"""# 先删除同名的旧通知self.notifications.filter_by(name=name).delete()notification = Notification(name=name, payload_json=data, user=self)db.session.add(notification)return notificationdef get_avatar_url(self):"""获取用户头像URL"""if self.avatar and self.avatar != current_app.config['DEFAULT_AVATAR']:return f'/static/uploads/avatars/{self.avatar}'return f'/static/images/{current_app.config["DEFAULT_AVATAR"]}'def save_avatar(self, avatar_file):"""保存用户头像"""# 生成唯一文件名filename = str(uuid.uuid4()) + os.path.splitext(avatar_file.filename)[1]# 确保上传目录存在avatar_dir = os.path.join(current_app.config['UPLOAD_FOLDER'], 'avatars')if not os.path.exists(avatar_dir):os.makedirs(avatar_dir)# 保存文件avatar_path = os.path.join(avatar_dir, filename)avatar_file.save(avatar_path)# 更新用户头像self.avatar = filenamedb.session.add(self)return filename@login_manager.user_loader
def load_user(user_id):"""加载用户的回调函数"""return User.query.get(int(user_id))class Notification(db.Model):"""通知模型"""__tablename__ = 'notifications'id = db.Column(db.Integer, primary_key=True)name = db.Column(db.String(128), index=True)user_id = db.Column(db.Integer, db.ForeignKey('users.id'))timestamp = db.Column(db.Float, index=True, default=time)payload_json = db.Column(db.Text)def __repr__(self):return f'<Notification {self.name}>'

app\routes\auth.py

from flask import Blueprint, render_template, redirect, url_for, flash, request, current_app
from flask_login import login_user, logout_user, login_required, current_user
from werkzeug.urls import url_parse
import jwt
from time import timefrom app import db
from app.models.user import User
from app.forms.auth import LoginForm, RegistrationForm, ResetPasswordRequestForm, ResetPasswordForm
from app.utils.email import send_password_reset_email# 创建认证蓝图
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')@auth_bp.route('/login', methods=['GET', 'POST'])
def login():"""用户登录视图"""# 如果用户已登录,重定向到首页if current_user.is_authenticated:return redirect(url_for('main.index'))form = LoginForm()if form.validate_on_submit():# 查找用户user = User.query.filter_by(email=form.email.data).first()# 验证用户和密码if user is None or not user.verify_password(form.password.data):flash('邮箱或密码无效', 'danger')return redirect(url_for('auth.login'))# 登录用户login_user(user, remember=form.remember_me.data)# 更新最后访问时间user.ping()db.session.commit()# 重定向到下一页或首页next_page = request.args.get('next')if not next_page or url_parse(next_page).netloc != '':next_page = url_for('main.index')flash('登录成功!', 'success')return redirect(next_page)return render_template('auth/login.html', title='登录', form=form)@auth_bp.route('/logout')
@login_required
def logout():"""用户登出视图"""logout_user()flash('您已成功登出', 'info')return redirect(url_for('main.index'))@auth_bp.route('/register', methods=['GET', 'POST'])
def register():"""用户注册视图"""# 如果用户已登录,重定向到首页if current_user.is_authenticated:return redirect(url_for('main.index'))form = RegistrationForm()if form.validate_on_submit():# 创建新用户user = User(username=form.username.data,email=form.email.data,student_id=form.student_id.data,phone=form.phone.data)user.password = form.password.data# 保存到数据库db.session.add(user)db.session.commit()flash('注册成功!现在您可以登录了。', 'success')return redirect(url_for('auth.login'))return render_template('auth/register.html', title='注册', form=form)@auth_bp.route('/reset_password_request', methods=['GET', 'POST'])
def reset_password_request():"""请求重置密码视图"""# 如果用户已登录,重定向到首页if current_user.is_authenticated:return redirect(url_for('main.index'))form = ResetPasswordRequestForm()if form.validate_on_submit():user = User.query.filter_by(email=form.email.data).first()if user:send_password_reset_email(user)# 无论用户是否存在,都显示相同的消息,避免泄露用户信息flash('重置密码的邮件已发送,请检查您的邮箱', 'info')return redirect(url_for('auth.login'))return render_template('auth/reset_password_request.html', title='重置密码', form=form)@auth_bp.route('/reset_password/<token>', methods=['GET', 'POST'])
def reset_password(token):"""重置密码视图"""# 如果用户已登录,重定向到首页if current_user.is_authenticated:return redirect(url_for('main.index'))# 验证令牌user = User.verify_reset_password_token(token)if not user:flash('令牌无效或已过期', 'danger')return redirect(url_for('main.index'))form = ResetPasswordForm()if form.validate_on_submit():# 设置新密码user.password = form.password.datadb.session.commit()flash('您的密码已重置', 'success')return redirect(url_for('auth.login'))return render_template('auth/reset_password.html', title='重置密码', form=form)

app\routes\item.py

import os
import json
import uuid
from datetime import datetime
from flask import Blueprint, render_template, redirect, url_for, flash, request, current_app, jsonify, abort
from flask_login import login_required, current_user
from werkzeug.utils import secure_filename
from app import db
from app.models.item import Item, Category, Favorite, Order, Review
from app.forms.item import ItemForm, ItemSearchForm, OrderForm, ReviewForm
from app.utils.decorators import confirmed_requireditem_bp = Blueprint('item', __name__)def allowed_file(filename):"""检查文件是否为允许的扩展名"""return '.' in filename and \filename.rsplit('.', 1)[1].lower() in ['jpg', 'jpeg', 'png', 'gif']def save_image(file):"""保存图片并返回文件名"""if not file or not allowed_file(file.filename):return None# 生成唯一文件名filename = secure_filename(file.filename)ext = filename.rsplit('.', 1)[1].lower()new_filename = f"{uuid.uuid4().hex}.{ext}"# 确保目录存在upload_folder = os.path.join(current_app.root_path, 'static/uploads/items')os.makedirs(upload_folder, exist_ok=True)# 保存文件file_path = os.path.join(upload_folder, new_filename)file.save(file_path)return new_filename@item_bp.route('/new', methods=['GET', 'POST'])
@login_required
@confirmed_required
def new_item():"""发布新商品"""form = ItemForm()# 获取所有分类并填充到表单选择框form.category.choices = [(c.id, c.name) for c in Category.query.all()]if form.validate_on_submit():# 创建新商品item = Item(title=form.title.data,description=form.description.data,price=form.price.data,original_price=form.original_price.data,condition=form.condition.data,location=form.location.data,seller_id=current_user.id,category_id=form.category.data,images=json.dumps([])  # 初始化为空列表)db.session.add(item)db.session.commit()# 处理图片上传image_filenames = []if form.images.data:for image in form.images.data:if image and allowed_file(image.filename):filename = save_image(image)if filename:image_filenames.append(filename)# 更新商品图片列表item.images = json.dumps(image_filenames)db.session.commit()flash('商品发布成功!', 'success')return redirect(url_for('item.detail', item_id=item.id))return render_template('item/new.html', form=form, title='发布商品')@item_bp.route('/<int:item_id>')
def detail(item_id):"""商品详情页"""item = Item.query.get_or_404(item_id)# 增加浏览次数if current_user.is_authenticated and current_user.id != item.seller_id:item.increment_view()# 检查当前用户是否已收藏该商品is_favorite = Falseif current_user.is_authenticated:is_favorite = Favorite.query.filter_by(user_id=current_user.id, item_id=item.id).first() is not None# 获取相似商品similar_items = Item.query.filter(Item.category_id == item.category_id,Item.id != item.id,Item.is_sold == False,Item.is_active == True).order_by(Item.created_at.desc()).limit(4).all()# 获取卖家其他商品seller_other_items = Item.query.filter(Item.seller_id == item.seller_id,Item.id != item.id,Item.is_sold == False,Item.is_active == True).order_by(Item.created_at.desc()).limit(4).all()return render_template('item/detail.html', item=item, is_favorite=is_favorite,similar_items=similar_items,seller_other_items=seller_other_items,title=item.title)@item_bp.route('/<int:item_id>/edit', methods=['GET', 'POST'])
@login_required
def edit_item(item_id):"""编辑商品"""item = Item.query.get_or_404(item_id)# 检查是否为商品卖家if item.seller_id != current_user.id:flash('您没有权限编辑此商品', 'danger')return redirect(url_for('item.detail', item_id=item.id))# 检查商品是否已售出if item.is_sold:flash('已售出的商品不能编辑', 'warning')return redirect(url_for('item.detail', item_id=item.id))form = ItemForm()form.category.choices = [(c.id, c.name) for c in Category.query.all()]if form.validate_on_submit():# 更新商品信息item.title = form.title.dataitem.description = form.description.dataitem.price = form.price.dataitem.original_price = form.original_price.dataitem.condition = form.condition.dataitem.location = form.location.dataitem.category_id = form.category.data# 处理图片上传if form.images.data and any(form.images.data):image_filenames = item.image_listfor image in form.images.data:if image and allowed_file(image.filename):filename = save_image(image)if filename:image_filenames.append(filename)item.images = json.dumps(image_filenames)db.session.commit()flash('商品信息已更新', 'success')return redirect(url_for('item.detail', item_id=item.id))# GET请求,填充表单数据elif request.method == 'GET':form.title.data = item.titleform.description.data = item.descriptionform.price.data = item.priceform.original_price.data = item.original_priceform.condition.data = item.conditionform.location.data = item.locationform.category.data = item.category_idreturn render_template('item/edit.html', form=form, item=item, title='编辑商品')@item_bp.route('/<int:item_id>/delete', methods=['POST'])
@login_required
def delete_item(item_id):"""删除商品"""item = Item.query.get_or_404(item_id)# 检查是否为商品卖家if item.seller_id != current_user.id:flash('您没有权限删除此商品', 'danger')return redirect(url_for('item.detail', item_id=item.id))# 检查是否有关联订单if item.orders.count() > 0:flash('此商品已有订单,不能删除', 'warning')return redirect(url_for('item.detail', item_id=item.id))# 删除商品图片for image_filename in item.image_list:image_path = os.path.join(current_app.root_path, 'static/uploads/items', image_filename)if os.path.exists(image_path):os.remove(image_path)# 删除收藏记录Favorite.query.filter_by(item_id=item.id).delete()# 删除商品db.session.delete(item)db.session.commit()flash('商品已删除', 'success')return redirect(url_for('user.dashboard'))@item_bp.route('/<int:item_id>/toggle_status', methods=['POST'])
@login_required
def toggle_status(item_id):"""切换商品状态(上架/下架)"""item = Item.query.get_or_404(item_id)# 检查是否为商品卖家if item.seller_id != current_user.id:flash('您没有权限操作此商品', 'danger')return redirect(url_for('item.detail', item_id=item.id))# 检查商品是否已售出if item.is_sold:flash('已售出的商品不能操作', 'warning')return redirect(url_for('item.detail', item_id=item.id))# 切换状态item.is_active = not item.is_activedb.session.commit()status = "上架" if item.is_active else "下架"flash(f'商品已{status}', 'success')return redirect(url_for('item.detail', item_id=item.id))@item_bp.route('/<int:item_id>/favorite', methods=['POST'])
@login_required
def toggle_favorite(item_id):"""收藏/取消收藏商品"""item = Item.query.get_or_404(item_id)# 检查是否已收藏favorite = Favorite.query.filter_by(user_id=current_user.id, item_id=item.id).first()if favorite:# 取消收藏db.session.delete(favorite)is_favorite = Falsemessage = '已取消收藏'else:# 添加收藏favorite = Favorite(user_id=current_user.id, item_id=item.id)db.session.add(favorite)is_favorite = Truemessage = '已收藏'db.session.commit()# 如果是AJAX请求,返回JSON响应if request.headers.get('X-Requested-With') == 'XMLHttpRequest':return jsonify({'success': True,'is_favorite': is_favorite,'message': message})# 普通请求,重定向回商品详情页flash(message, 'success')return redirect(url_for('item.detail', item_id=item.id))@item_bp.route('/<int:item_id>/buy', methods=['GET', 'POST'])
@login_required
@confirmed_required
def buy_item(item_id):"""购买商品"""item = Item.query.get_or_404(item_id)# 检查商品是否可购买if item.is_sold:flash('此商品已售出', 'warning')return redirect(url_for('item.detail', item_id=item.id))if not item.is_active:flash('此商品已下架', 'warning')return redirect(url_for('item.detail', item_id=item.id))# 检查是否为自己的商品if item.seller_id == current_user.id:flash('不能购买自己的商品', 'warning')return redirect(url_for('item.detail', item_id=item.id))form = OrderForm()if form.validate_on_submit():# 创建订单order = Order(order_number=Order.generate_order_number(),price=item.price,message=form.message.data,buyer_id=current_user.id,seller_id=item.seller_id,item_id=item.id,status='pending'  # 待付款状态)# 标记商品为已售出item.is_sold = Truedb.session.add(order)db.session.commit()flash('订单已创建,请尽快完成支付', 'success')return redirect(url_for('order.detail', order_number=order.order_number))return render_template('item/buy.html', form=form, item=item, title='购买商品')@item_bp.route('/search')
def search():"""搜索商品"""form = ItemSearchForm(request.args, meta={'csrf': False})form.category.choices = [(0, '所有分类')] + [(c.id, c.name) for c in Category.query.all()]page = request.args.get('page', 1, type=int)per_page = current_app.config.get('ITEMS_PER_PAGE', 12)# 构建查询query = Item.query.filter_by(is_active=True, is_sold=False)# 关键词搜索if form.keyword.data:search_term = f"%{form.keyword.data}%"query = query.filter((Item.title.like(search_term)) | (Item.description.like(search_term)))# 分类筛选if form.category.data and form.category.data != 0:query = query.filter_by(category_id=form.category.data)# 价格范围if form.min_price.data is not None:query = query.filter(Item.price >= form.min_price.data)if form.max_price.data is not None:query = query.filter(Item.price <= form.max_price.data)# 物品状况if form.condition.data:query = query.filter_by(condition=form.condition.data)# 排序if form.sort.data == 'newest':query = query.order_by(Item.created_at.desc())elif form.sort.data == 'price_asc':query = query.order_by(Item.price.asc())elif form.sort.data == 'price_desc':query = query.order_by(Item.price.desc())elif form.sort.data == 'popular':query = query.order_by(Item.views.desc())# 分页pagination = query.paginate(page=page, per_page=per_page, error_out=False)items = pagination.itemsreturn render_template('item/search.html', form=form, items=items, pagination=pagination,title='搜索商品')@item_bp.route('/category/<int:category_id>')
def category(category_id):"""按分类浏览商品"""category = Category.query.get_or_404(category_id)page = request.args.get('page', 1, type=int)per_page = current_app.config.get('ITEMS_PER_PAGE', 12)# 获取该分类下的所有活跃且未售出的商品query = Item.query.filter_by(category_id=category_id, is_active=True, is_sold=False).order_by(Item.created_at.desc())# 分页pagination = query.paginate(page=page, per_page=per_page, error_out=False)items = pagination.itemsreturn render_template('item/category.html', category=category, items=items, pagination=pagination,title=f'{category.name} - 商品分类')@item_bp.route('/api/items/<int:item_id>/favorite', methods=['POST'])
@login_required
def api_toggle_favorite(item_id):"""API: 收藏/取消收藏商品"""item = Item.query.get_or_404(item_id)# 检查是否已收藏favorite = Favorite.query.filter_by(user_id=current_user.id, item_id=item.id).first()if favorite:# 取消收藏db.session.delete(favorite)is_favorite = Falseelse:# 添加收藏favorite = Favorite(user_id=current_user.id, item_id=item.id)db.session.add(favorite)is_favorite = Truedb.session.commit()return jsonify({'success': True,'is_favorite': is_favorite})@item_bp.route('/api/items/<int:item_id>/images/<path:filename>', methods=['DELETE'])
@login_required
def api_delete_image(item_id, filename):"""API: 删除商品图片"""item = Item.query.get_or_404(item_id)# 检查是否为商品卖家if item.seller_id != current_user.id:return jsonify({'success': False,'message': '您没有权限删除此图片'}), 403# 从商品图片列表中移除if item.remove_image(filename):# 删除实际文件image_path = os.path.join(current_app.root_path, 'static/uploads/items', filename)if os.path.exists(image_path):os.remove(image_path)db.session.commit()return jsonify({'success': True,'message': '图片已删除'})return jsonify({'success': False,'message': '图片不存在'}), 404

app\routes\main.py

from flask import Blueprint, render_template, redirect, url_for, flash, request, current_app
from flask_login import current_user, login_requiredfrom app import db
from app.models.user import User# 创建主蓝图
main_bp = Blueprint('main', __name__)@main_bp.route('/')
@main_bp.route('/index')
def index():"""首页视图"""return render_template('main/index.html', title='首页')@main_bp.route('/about')
def about():"""关于页面"""return render_template('main/about.html', title='关于我们')@main_bp.route('/contact')
def contact():"""联系我们页面"""return render_template('main/contact.html', title='联系我们')@main_bp.route('/help')
def help():"""帮助页面"""return render_template('main/help.html', title='帮助中心')@main_bp.before_app_request
def before_request():"""在每个请求之前执行"""if current_user.is_authenticated:current_user.ping()  # 更新用户最后访问时间db.session.commit()

app\routes\order.py

from flask import Blueprint, render_template, redirect, url_for, flash, request, abort, current_app
from flask_login import login_required, current_user
from app import db
from app.models.item import Order, Item, Review
from app.forms.item import ReviewForm
from app.utils.decorators import confirmed_requiredorder_bp = Blueprint('order', __name__)@order_bp.route('/orders')
@login_required
def list_orders():"""订单列表页"""# 获取查询参数role = request.args.get('role', 'buyer')  # 默认查看作为买家的订单status = request.args.get('status', 'all')  # 默认查看所有状态的订单# 构建查询if role == 'buyer':query = Order.query.filter_by(buyer_id=current_user.id)else:  # sellerquery = Order.query.filter_by(seller_id=current_user.id)# 按状态筛选if status != 'all':query = query.filter_by(status=status)# 按创建时间倒序排序orders = query.order_by(Order.created_at.desc()).all()return render_template('order/list.html', orders=orders, role=role, status=status,title='我的订单')@order_bp.route('/order/<string:order_number>')
@login_required
def detail(order_number):"""订单详情页"""order = Order.query.filter_by(order_number=order_number).first_or_404()# 检查是否为订单买家或卖家if order.buyer_id != current_user.id and order.seller_id != current_user.id:abort(403)# 获取商品信息item = Item.query.get_or_404(order.item_id)# 获取评价信息review = Review.query.filter_by(order_id=order.id).first()# 判断当前用户角色is_buyer = (order.buyer_id == current_user.id)return render_template('order/detail.html', order=order, item=item, review=review,is_buyer=is_buyer,title='订单详情')@order_bp.route('/order/<string:order_number>/pay', methods=['POST'])
@login_required
@confirmed_required
def pay_order(order_number):"""支付订单"""order = Order.query.filter_by(order_number=order_number).first_or_404()# 检查是否为订单买家if order.buyer_id != current_user.id:flash('您没有权限支付此订单', 'danger')return redirect(url_for('order.detail', order_number=order_number))# 检查订单状态if order.status != 'pending':flash('此订单状态不允许支付', 'warning')return redirect(url_for('order.detail', order_number=order_number))# 更新订单状态为已支付order.status = 'paid'db.session.commit()flash('支付成功!请联系卖家进行交易', 'success')return redirect(url_for('order.detail', order_number=order_number))@order_bp.route('/order/<string:order_number>/cancel', methods=['POST'])
@login_required
def cancel_order(order_number):"""取消订单"""order = Order.query.filter_by(order_number=order_number).first_or_404()# 检查是否为订单买家或卖家if order.buyer_id != current_user.id and order.seller_id != current_user.id:flash('您没有权限取消此订单', 'danger')return redirect(url_for('order.detail', order_number=order_number))# 检查订单状态if order.status not in ['pending', 'paid']:flash('此订单状态不允许取消', 'warning')return redirect(url_for('order.detail', order_number=order_number))# 更新订单状态为已取消order.status = 'cancelled'# 将商品重新设为可售状态item = Item.query.get(order.item_id)item.is_sold = Falsedb.session.commit()flash('订单已取消', 'success')return redirect(url_for('order.detail', order_number=order_number))@order_bp.route('/order/<string:order_number>/complete', methods=['POST'])
@login_required
def complete_order(order_number):"""完成订单"""order = Order.query.filter_by(order_number=order_number).first_or_404()# 检查是否为订单买家if order.buyer_id != current_user.id:flash('只有买家可以确认完成订单', 'danger')return redirect(url_for('order.detail', order_number=order_number))# 检查订单状态if order.status != 'paid':flash('此订单状态不允许确认完成', 'warning')return redirect(url_for('order.detail', order_number=order_number))# 更新订单状态为已完成order.status = 'completed'db.session.commit()flash('订单已完成,请对卖家进行评价', 'success')return redirect(url_for('order.review', order_number=order_number))@order_bp.route('/order/<string:order_number>/review', methods=['GET', 'POST'])
@login_required
def review(order_number):"""评价订单"""order = Order.query.filter_by(order_number=order_number).first_or_404()# 检查是否为订单买家if order.buyer_id != current_user.id:flash('只有买家可以评价订单', 'danger')return redirect(url_for('order.detail', order_number=order_number))# 检查订单状态if order.status != 'completed':flash('只有已完成的订单才能评价', 'warning')return redirect(url_for('order.detail', order_number=order_number))# 检查是否已评价existing_review = Review.query.filter_by(order_id=order.id).first()if existing_review:flash('您已经评价过此订单', 'info')return redirect(url_for('order.detail', order_number=order_number))form = ReviewForm()if form.validate_on_submit():# 创建评价review = Review(content=form.content.data,rating=form.rating.data,order_id=order.id,reviewer_id=current_user.id,target_user_id=order.seller_id)db.session.add(review)db.session.commit()flash('评价提交成功,感谢您的反馈!', 'success')return redirect(url_for('order.detail', order_number=order_number))return render_template('order/review.html', form=form, order=order,title='评价订单')

app\routes\transaction.py

from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, current_app
from flask_login import login_required, current_user
from app.models.item import Item, Order, Review
from app.models.user import User, Notification
from app.forms.transaction import PaymentForm, DisputeForm
from app import db
import datetime
import uuid
import os# 创建交易蓝图
transaction = Blueprint('transaction', __name__)@transaction.route('/payment/<string:order_number>', methods=['GET', 'POST'])
@login_required
def payment(order_number):"""处理订单支付"""order = Order.query.filter_by(order_number=order_number).first_or_404()# 验证当前用户是否为买家if order.buyer_id != current_user.id:flash('您无权访问此页面', 'danger')return redirect(url_for('main.index'))# 验证订单状态是否为待支付if order.status != 'pending':flash('该订单状态不允许支付', 'warning')return redirect(url_for('order.detail', order_number=order_number))form = PaymentForm()if form.validate_on_submit():# 模拟支付处理# 实际项目中应集成第三方支付APItry:# 更新订单状态order.status = 'paid'order.updated_at = datetime.datetime.now()# 创建通知给卖家notification = Notification(user_id=order.seller_id,title='订单已支付',content=f'订单 {order.order_number} 已支付,请及时与买家联系。',notification_type='order_paid')db.session.add(notification)db.session.commit()flash('支付成功!', 'success')return redirect(url_for('order.detail', order_number=order_number))except Exception as e:db.session.rollback()current_app.logger.error(f"支付处理失败: {str(e)}")flash('支付处理失败,请稍后再试', 'danger')item = order.itemreturn render_template('transaction/payment.html', order=order, item=item, form=form)@transaction.route('/complete/<string:order_number>', methods=['POST'])
@login_required
def complete_transaction(order_number):"""完成交易"""order = Order.query.filter_by(order_number=order_number).first_or_404()# 验证当前用户是否为买家if order.buyer_id != current_user.id:flash('您无权执行此操作', 'danger')return redirect(url_for('main.index'))# 验证订单状态是否为已支付if order.status != 'paid':flash('该订单状态不允许完成交易', 'warning')return redirect(url_for('order.detail', order_number=order_number))try:# 更新订单状态order.status = 'completed'order.updated_at = datetime.datetime.now()# 更新商品状态为已售出item = order.itemitem.status = 'sold'# 创建通知给卖家notification = Notification(user_id=order.seller_id,title='交易已完成',content=f'订单 {order.order_number} 已完成,买家已确认收货。',notification_type='order_completed')db.session.add(notification)db.session.commit()flash('交易已完成!感谢您的购买', 'success')except Exception as e:db.session.rollback()current_app.logger.error(f"完成交易失败: {str(e)}")flash('操作失败,请稍后再试', 'danger')return redirect(url_for('order.detail', order_number=order_number))@transaction.route('/cancel/<string:order_number>', methods=['POST'])
@login_required
def cancel_transaction(order_number):"""取消交易"""order = Order.query.filter_by(order_number=order_number).first_or_404()# 验证当前用户是否为买家或卖家if order.buyer_id != current_user.id and order.seller_id != current_user.id:flash('您无权执行此操作', 'danger')return redirect(url_for('main.index'))# 验证订单状态是否允许取消if order.status not in ['pending', 'paid']:flash('该订单状态不允许取消', 'warning')return redirect(url_for('order.detail', order_number=order_number))try:# 更新订单状态order.status = 'cancelled'order.updated_at = datetime.datetime.now()# 如果订单已支付,则需要处理退款逻辑# 实际项目中应集成第三方支付API的退款功能# 如果是买家取消,通知卖家;如果是卖家取消,通知买家if current_user.id == order.buyer_id:notify_user_id = order.seller_idaction_user = "买家"else:notify_user_id = order.buyer_idaction_user = "卖家"notification = Notification(user_id=notify_user_id,title='订单已取消',content=f'订单 {order.order_number} 已被{action_user}取消。',notification_type='order_cancelled')db.session.add(notification)db.session.commit()flash('订单已成功取消', 'success')except Exception as e:db.session.rollback()current_app.logger.error(f"取消交易失败: {str(e)}")flash('操作失败,请稍后再试', 'danger')return redirect(url_for('order.detail', order_number=order_number))@transaction.route('/dispute/<string:order_number>', methods=['GET', 'POST'])
@login_required
def dispute(order_number):"""提交交易纠纷"""order = Order.query.filter_by(order_number=order_number).first_or_404()# 验证当前用户是否为买家或卖家if order.buyer_id != current_user.id and order.seller_id != current_user.id:flash('您无权访问此页面', 'danger')return redirect(url_for('main.index'))# 验证订单状态是否允许提交纠纷if order.status not in ['paid', 'completed']:flash('该订单状态不允许提交纠纷', 'warning')return redirect(url_for('order.detail', order_number=order_number))form = DisputeForm()if form.validate_on_submit():try:# 创建纠纷记录# 实际项目中应有专门的纠纷表# 更新订单状态order.status = 'disputed'order.updated_at = datetime.datetime.now()# 通知对方if current_user.id == order.buyer_id:notify_user_id = order.seller_idaction_user = "买家"else:notify_user_id = order.buyer_idaction_user = "卖家"notification = Notification(user_id=notify_user_id,title='订单纠纷',content=f'订单 {order.order_number} 已被{action_user}提交纠纷申请。',notification_type='order_disputed')# 通知管理员admin_users = User.query.filter_by(role='admin').all()for admin in admin_users:admin_notification = Notification(user_id=admin.id,title='新订单纠纷',content=f'订单 {order.order_number} 已提交纠纷申请,请尽快处理。',notification_type='admin_dispute')db.session.add(admin_notification)db.session.add(notification)db.session.commit()flash('纠纷申请已提交,请等待平台处理', 'success')return redirect(url_for('order.detail', order_number=order_number))except Exception as e:db.session.rollback()current_app.logger.error(f"提交纠纷失败: {str(e)}")flash('操作失败,请稍后再试', 'danger')item = order.itemreturn render_template('transaction/dispute.html', order=order, item=item, form=form)@transaction.route('/transaction_records')
@login_required
def transaction_records():"""查看交易记录"""page = request.args.get('page', 1, type=int)# 获取用户作为买家的订单buyer_orders = Order.query.filter_by(buyer_id=current_user.id).order_by(Order.created_at.desc())# 获取用户作为卖家的订单seller_orders = Order.query.filter_by(seller_id=current_user.id).order_by(Order.created_at.desc())# 合并订单并按时间排序all_orders = buyer_orders.union(seller_orders).order_by(Order.created_at.desc())# 分页pagination = all_orders.paginate(page=page, per_page=10, error_out=False)orders = pagination.itemsreturn render_template('transaction/records.html', orders=orders, pagination=pagination)@transaction.route('/statistics')
@login_required
def statistics():"""交易统计"""# 获取用户作为买家的已完成订单buyer_completed = Order.query.filter_by(buyer_id=current_user.id, status='completed').count()# 获取用户作为卖家的已完成订单seller_completed = Order.query.filter_by(seller_id=current_user.id, status='completed').count()# 获取用户作为买家的总消费buyer_total = db.session.query(db.func.sum(Order.price)).filter_by(buyer_id=current_user.id, status='completed').scalar() or 0# 获取用户作为卖家的总收入seller_total = db.session.query(db.func.sum(Order.price)).filter_by(seller_id=current_user.id, status='completed').scalar() or 0# 获取用户最近的交易记录recent_orders = Order.query.filter(((Order.buyer_id == current_user.id) | (Order.seller_id == current_user.id)) &(Order.status == 'completed')).order_by(Order.updated_at.desc()).limit(5).all()# 获取用户收到的评价reviews = Review.query.filter_by(target_user_id=current_user.id).order_by(Review.created_at.desc()).limit(5).all()# 计算平均评分avg_rating = db.session.query(db.func.avg(Review.rating)).filter_by(target_user_id=current_user.id).scalar() or 0return render_template('transaction/statistics.html',buyer_completed=buyer_completed,seller_completed=seller_completed,buyer_total=buyer_total,seller_total=seller_total,recent_orders=recent_orders,reviews=reviews,avg_rating=avg_rating)

app\routes\user.py

from flask import Blueprint, render_template, redirect, url_for, flash, request, current_app
from flask_login import login_required, current_user
from werkzeug.utils import secure_filename
import os
from datetime import datetimefrom app import db
from app.models.user import User
from app.forms.user import EditProfileForm, ChangePasswordForm
from app.utils.decorators import check_confirmed# 创建用户蓝图
user_bp = Blueprint('user', __name__, url_prefix='/user')@user_bp.route('/profile/<username>')
def profile(username):"""用户个人资料页面"""user = User.query.filter_by(username=username).first_or_404()# 获取用户发布的物品page = request.args.get('page', 1, type=int)items = user.items.order_by(Item.created_at.desc()).paginate(page=page, per_page=current_app.config['ITEMS_PER_PAGE'],error_out=False)return render_template('user/profile.html', user=user, items=items)@user_bp.route('/edit_profile', methods=['GET', 'POST'])
@login_required
def edit_profile():"""编辑个人资料"""form = EditProfileForm()if form.validate_on_submit():# 更新用户信息current_user.username = form.username.datacurrent_user.phone = form.phone.datacurrent_user.bio = form.bio.datacurrent_user.dormitory = form.dormitory.data# 处理头像上传if form.avatar.data:# 检查文件类型filename = secure_filename(form.avatar.data.filename)if '.' in filename and filename.rsplit('.', 1)[1].lower() in current_app.config['ALLOWED_EXTENSIONS']:# 保存头像current_user.save_avatar(form.avatar.data)else:flash('不支持的文件类型', 'danger')db.session.commit()flash('个人资料已更新', 'success')return redirect(url_for('user.profile', username=current_user.username))# 预填表单elif request.method == 'GET':form.username.data = current_user.usernameform.phone.data = current_user.phoneform.bio.data = current_user.bioform.dormitory.data = current_user.dormitoryreturn render_template('user/edit_profile.html', title='编辑个人资料', form=form)@user_bp.route('/change_password', methods=['GET', 'POST'])
@login_required
def change_password():"""修改密码"""form = ChangePasswordForm()if form.validate_on_submit():# 验证当前密码if not current_user.verify_password(form.current_password.data):flash('当前密码不正确', 'danger')return redirect(url_for('user.change_password'))# 更新密码current_user.password = form.new_password.datadb.session.commit()flash('密码已更新', 'success')return redirect(url_for('user.profile', username=current_user.username))return render_template('user/change_password.html', title='修改密码', form=form)@user_bp.route('/dashboard')
@login_required
def dashboard():"""用户控制面板"""# 获取用户发布的物品user_items = current_user.items.order_by(Item.created_at.desc()).limit(5).all()# 获取用户的购买订单user_orders = current_user.orders.order_by(Order.created_at.desc()).limit(5).all()# 获取用户的销售订单sold_orders = current_user.sold_orders.order_by(Order.created_at.desc()).limit(5).all()# 获取用户的未读消息数unread_messages_count = current_user.messages_received.filter_by(is_read=False).count()# 获取用户的收藏物品favorites = current_user.favorites.join(Item).filter(Item.is_active==True).limit(5).all()return render_template('user/dashboard.html', title='用户控制面板',user_items=user_items,user_orders=user_orders,sold_orders=sold_orders,unread_messages_count=unread_messages_count,favorites=favorites)@user_bp.route('/notifications')
@login_required
def notifications():"""用户通知页面"""# 标记所有通知为已读current_user.last_notification_read_time = datetime.utcnow()db.session.commit()# 获取通知page = request.args.get('page', 1, type=int)notifications = current_user.notifications.order_by(Notification.timestamp.desc()).paginate(page=page, per_page=current_app.config['ITEMS_PER_PAGE'],error_out=False)return render_template('user/notifications.html', title='我的通知',notifications=notifications)

app\static\css\style.css

/* 全局样式 */
:root {--primary-color: #3490dc;--secondary-color: #6c757d;--success-color: #38c172;--danger-color: #e3342f;--warning-color: #ffed4a;--info-color: #6cb2eb;--light-color: #f8f9fa;--dark-color: #343a40;
}body {font-family: 'Nunito', 'Helvetica Neue', Arial, sans-serif;background-color: #f5f8fa;color: #333;min-height: 100vh;display: flex;flex-direction: column;
}.content-wrapper {flex: 1;
}/* 导航栏样式 */
.navbar-brand {font-weight: 700;font-size: 1.5rem;
}.navbar {box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}.nav-link {font-weight: 600;
}.dropdown-item:active {background-color: var(--primary-color);
}/* 卡片样式 */
.card {border: none;border-radius: 0.5rem;transition: transform 0.3s, box-shadow 0.3s;
}.card-header {border-top-left-radius: 0.5rem !important;border-top-right-radius: 0.5rem !important;
}.card:hover {transform: translateY(-5px);box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}.shadow {box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) !important;
}/* 按钮样式 */
.btn {font-weight: 600;border-radius: 0.25rem;transition: all 0.3s;
}.btn-primary {background-color: var(--primary-color);border-color: var(--primary-color);
}.btn-primary:hover {background-color: #2779bd;border-color: #2779bd;
}.btn-outline-primary {color: var(--primary-color);border-color: var(--primary-color);
}.btn-outline-primary:hover {background-color: var(--primary-color);color: white;
}/* 表单样式 */
.form-control {border-radius: 0.25rem;border: 1px solid #ddd;padding: 0.75rem 1rem;
}.form-control:focus {border-color: var(--primary-color);box-shadow: 0 0 0 0.2rem rgba(52, 144, 220, 0.25);
}.form-label {font-weight: 600;
}/* 头像样式 */
.avatar-small {width: 40px;height: 40px;object-fit: cover;
}.avatar-medium {width: 100px;height: 100px;object-fit: cover;
}.avatar-large {width: 150px;height: 150px;object-fit: cover;
}/* 商品卡片样式 */
.item-card {transition: transform 0.3s, box-shadow 0.3s;
}.item-card:hover {transform: translateY(-5px);box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}.item-thumbnail {height: 200px;object-fit: cover;
}/* 分类图标样式 */
.category-icon {width: 60px;height: 60px;border-radius: 50%;background-color: #f8f9fa;display: flex;align-items: center;justify-content: center;margin: 0 auto;transition: all 0.3s;
}.category-link:hover .category-icon {background-color: var(--primary-color);color: white;
}/* 页脚样式 */
.footer {background-color: #343a40;color: #f8f9fa;padding: 2rem 0;margin-top: 3rem;
}.footer a {color: #f8f9fa;text-decoration: none;
}.footer a:hover {color: var(--primary-color);
}.footer-heading {font-weight: 700;margin-bottom: 1.5rem;
}/* 响应式调整 */
@media (max-width: 767.98px) {.item-thumbnail {height: 180px;}.avatar-medium {width: 80px;height: 80px;}
}/* 动画效果 */
.fade-in {animation: fadeIn 0.5s ease-in;
}@keyframes fadeIn {from {opacity: 0;transform: translateY(10px);}to {opacity: 1;transform: translateY(0);}
}/* 通知样式 */
.notification-badge {position: absolute;top: 0;right: 0;background-color: var(--danger-color);color: white;border-radius: 50%;width: 18px;height: 18px;font-size: 0.7rem;display: flex;align-items: center;justify-content: center;
}/* 评分样式 */
.star-rating .fa-star {color: #ccc;
}.star-rating .fa-star.checked {color: #ffc107;
}/* 标签样式 */
.tag {display: inline-block;background-color: #e9ecef;color: #495057;padding: 0.25rem 0.5rem;border-radius: 0.25rem;margin-right: 0.5rem;margin-bottom: 0.5rem;font-size: 0.875rem;
}/* 加载动画 */
.spinner {width: 40px;height: 40px;border: 4px solid rgba(0, 0, 0, 0.1);border-radius: 50%;border-top-color: var(--primary-color);animation: spin 1s ease-in-out infinite;
}@keyframes spin {to {transform: rotate(360deg);}
}/* 自定义滚动条 */
::-webkit-scrollbar {width: 8px;
}::-webkit-scrollbar-track {background: #f1f1f1;
}::-webkit-scrollbar-thumb {background: #888;border-radius: 4px;
}::-webkit-scrollbar-thumb:hover {background: #555;
}

app\templates\base.html

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>{% block title %}校园二手物品交易平台{% endblock %}</title><!-- Bootstrap CSS --><link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.1.3/css/bootstrap.min.css" rel="stylesheet"><!-- Font Awesome --><link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"><!-- Custom CSS --><link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">{% block styles %}{% endblock %}
</head>
<body><!-- 导航栏 --><nav class="navbar navbar-expand-lg navbar-dark bg-primary"><div class="container"><a class="navbar-brand" href="{{ url_for('main.index') }}"><i class="fas fa-exchange-alt"></i> 校园二手交易</a><button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarNav"><ul class="navbar-nav me-auto"><li class="nav-item"><a class="nav-link" href="{{ url_for('main.index') }}">首页</a></li>{% if current_user.is_authenticated %}<li class="nav-item"><a class="nav-link" href="#">浏览商品</a></li><li class="nav-item"><a class="nav-link" href="#">发布商品</a></li>{% endif %}<li class="nav-item"><a class="nav-link" href="{{ url_for('main.about') }}">关于我们</a></li><li class="nav-item"><a class="nav-link" href="{{ url_for('main.help') }}">帮助中心</a></li></ul><ul class="navbar-nav">{% if current_user.is_authenticated %}<li class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown"><img src="{{ current_user.get_avatar_url() }}" alt="头像" class="avatar-small rounded-circle me-1">{{ current_user.username }}</a><ul class="dropdown-menu dropdown-menu-end"><li><a class="dropdown-item" href="{{ url_for('user.dashboard') }}"><i class="fas fa-tachometer-alt me-2"></i>控制面板</a></li><li><a class="dropdown-item" href="{{ url_for('user.profile', username=current_user.username) }}"><i class="fas fa-user me-2"></i>个人资料</a></li><li><a class="dropdown-item" href="#"><i class="fas fa-shopping-bag me-2"></i>我的商品</a></li><li><a class="dropdown-item" href="#"><i class="fas fa-heart me-2"></i>我的收藏</a></li><li><a class="dropdown-item" href="#"><i class="fas fa-envelope me-2"></i>我的消息{% if current_user.messages_received.filter_by(is_read=False).count() > 0 %}<span class="badge bg-danger">{{ current_user.messages_received.filter_by(is_read=False).count() }}</span>{% endif %}</a></li><li><hr class="dropdown-divider"></li><li><a class="dropdown-item" href="{{ url_for('auth.logout') }}"><i class="fas fa-sign-out-alt me-2"></i>退出登录</a></li></ul></li>{% else %}<li class="nav-item"><a class="nav-link" href="{{ url_for('auth.login') }}">登录</a></li><li class="nav-item"><a class="nav-link" href="{{ url_for('auth.register') }}">注册</a></li>{% endif %}</ul></div></div></nav><!-- 消息提示 --><div class="container mt-3">{% with messages = get_flashed_messages(with_categories=true) %}{% if messages %}{% for category, message in messages %}<div class="alert alert-{{ category }} alert-dismissible fade show">{{ message }}<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>{% endfor %}{% endif %}{% endwith %}</div><!-- 主要内容 --><main class="container mt-4">{% block content %}{% endblock %}</main><!-- 页脚 --><footer class="bg-light text-center text-lg-start mt-5"><div class="container p-4"><div class="row"><div class="col-lg-6 col-md-12 mb-4 mb-md-0"><h5 class="text-uppercase">校园二手物品交易平台</h5><p>我们致力于为校园师生提供一个安全、便捷、高效的二手物品交易平台,让闲置资源得到充分利用,促进校园资源的循环利用。</p></div><div class="col-lg-3 col-md-6 mb-4 mb-md-0"><h5 class="text-uppercase">链接</h5><ul class="list-unstyled mb-0"><li><a href="{{ url_for('main.about') }}" class="text-dark">关于我们</a></li><li><a href="{{ url_for('main.help') }}" class="text-dark">帮助中心</a></li><li><a href="{{ url_for('main.contact') }}" class="text-dark">联系我们</a></li><li><a href="#" class="text-dark">使用条款</a></li></ul></div><div class="col-lg-3 col-md-6 mb-4 mb-md-0"><h5 class="text-uppercase">联系我们</h5><ul class="list-unstyled mb-0"><li><i class="fas fa-envelope me-2"></i>contact@campus-trading.com</li><li><i class="fas fa-phone me-2"></i>(+86) 123-4567-8901</li><li><i class="fas fa-map-marker-alt me-2"></i>中国某大学</li></ul></div></div></div><div class="text-center p-3" style="background-color: rgba(0, 0, 0, 0.05);">© 2025 校园二手物品交易平台 - 保留所有权利</div></footer><!-- Bootstrap JS --><script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script><!-- jQuery --><script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script><!-- Custom JS --><script src="{{ url_for('static', filename='js/main.js') }}"></script>{% block scripts %}{% endblock %}
</body>
</html>

app\templates\auth\login.html

{% extends "base.html" %}{% block title %}登录 - {{ super() }}{% endblock %}{% block content %}
<div class="row justify-content-center"><div class="col-md-6"><div class="card shadow"><div class="card-header bg-primary text-white"><h4 class="mb-0"><i class="fas fa-sign-in-alt me-2"></i>用户登录</h4></div><div class="card-body"><form method="POST" action="{{ url_for('auth.login') }}">{{ form.hidden_tag() }}<div class="mb-3">{{ form.email.label(class="form-label") }}{% if form.email.errors %}{{ form.email(class="form-control is-invalid") }}<div class="invalid-feedback">{% for error in form.email.errors %}{{ error }}{% endfor %}</div>{% else %}{{ form.email(class="form-control", placeholder="请输入您的邮箱") }}{% endif %}</div><div class="mb-3">{{ form.password.label(class="form-label") }}{% if form.password.errors %}{{ form.password(class="form-control is-invalid") }}<div class="invalid-feedback">{% for error in form.password.errors %}{{ error }}{% endfor %}</div>{% else %}{{ form.password(class="form-control", placeholder="请输入您的密码") }}{% endif %}</div><div class="mb-3 form-check">{{ form.remember_me(class="form-check-input") }}{{ form.remember_me.label(class="form-check-label") }}</div><div class="d-grid gap-2">{{ form.submit(class="btn btn-primary") }}</div></form></div><div class="card-footer"><div class="d-flex justify-content-between"><a href="{{ url_for('auth.reset_password_request') }}">忘记密码?</a><a href="{{ url_for('auth.register') }}">没有账号?立即注册</a></div></div></div></div>
</div>
{% endblock %}

app\templates\auth\register.html

{% extends "base.html" %}{% block title %}注册 - {{ super() }}{% endblock %}{% block content %}
<div class="row justify-content-center"><div class="col-md-8"><div class="card shadow"><div class="card-header bg-primary text-white"><h4 class="mb-0"><i class="fas fa-user-plus me-2"></i>用户注册</h4></div><div class="card-body"><form method="POST" action="{{ url_for('auth.register') }}">{{ form.hidden_tag() }}<div class="row"><div class="col-md-6 mb-3">{{ form.username.label(class="form-label") }}{% if form.username.errors %}{{ form.username(class="form-control is-invalid") }}<div class="invalid-feedback">{% for error in form.username.errors %}{{ error }}{% endfor %}</div>{% else %}{{ form.username(class="form-control", placeholder="请输入用户名") }}{% endif %}</div><div class="col-md-6 mb-3">{{ form.email.label(class="form-label") }}{% if form.email.errors %}{{ form.email(class="form-control is-invalid") }}<div class="invalid-feedback">{% for error in form.email.errors %}{{ error }}{% endfor %}</div>{% else %}{{ form.email(class="form-control", placeholder="请输入邮箱") }}{% endif %}</div></div><div class="row"><div class="col-md-6 mb-3">{{ form.student_id.label(class="form-label") }}{% if form.student_id.errors %}{{ form.student_id(class="form-control is-invalid") }}<div class="invalid-feedback">{% for error in form.student_id.errors %}{{ error }}{% endfor %}</div>{% else %}{{ form.student_id(class="form-control", placeholder="请输入学号") }}{% endif %}</div><div class="col-md-6 mb-3">{{ form.phone.label(class="form-label") }}{% if form.phone.errors %}{{ form.phone(class="form-control is-invalid") }}<div class="invalid-feedback">{% for error in form.phone.errors %}{{ error }}{% endfor %}</div>{% else %}{{ form.phone(class="form-control", placeholder="请输入手机号") }}{% endif %}</div></div><div class="row"><div class="col-md-6 mb-3">{{ form.password.label(class="form-label") }}{% if form.password.errors %}{{ form.password(class="form-control is-invalid") }}<div class="invalid-feedback">{% for error in form.password.errors %}{{ error }}{% endfor %}</div>{% else %}{{ form.password(class="form-control", placeholder="请输入密码") }}{% endif %}</div><div class="col-md-6 mb-3">{{ form.confirm_password.label(class="form-label") }}{% if form.confirm_password.errors %}{{ form.confirm_password(class="form-control is-invalid") }}<div class="invalid-feedback">{% for error in form.confirm_password.errors %}{{ error }}{% endfor %}</div>{% else %}{{ form.confirm_password(class="form-control", placeholder="请确认密码") }}{% endif %}</div></div><div class="mb-3"><div class="form-text"><small>注册即表示您同意我们的<a href="#">服务条款</a>和<a href="#">隐私政策</a></small></div></div><div class="d-grid gap-2">{{ form.submit(class="btn btn-primary") }}</div></form></div><div class="card-footer text-center">已有账号?<a href="{{ url_for('auth.login') }}">立即登录</a></div></div></div>
</div>
{% endblock %}

app\templates\auth\reset_password.html

{% extends "base.html" %}{% block title %}重置密码 - {{ super() }}{% endblock %}{% block content %}
<div class="row justify-content-center"><div class="col-md-6"><div class="card shadow"><div class="card-header bg-primary text-white"><h4 class="mb-0"><i class="fas fa-key me-2"></i>设置新密码</h4></div><div class="card-body"><form method="POST" action="{{ url_for('auth.reset_password', token=token) }}">{{ form.hidden_tag() }}<div class="mb-3">{{ form.password.label(class="form-label") }}{% if form.password.errors %}{{ form.password(class="form-control is-invalid") }}<div class="invalid-feedback">{% for error in form.password.errors %}{{ error }}{% endfor %}</div>{% else %}{{ form.password(class="form-control", placeholder="请输入新密码") }}{% endif %}</div><div class="mb-3">{{ form.confirm_password.label(class="form-label") }}{% if form.confirm_password.errors %}{{ form.confirm_password(class="form-control is-invalid") }}<div class="invalid-feedback">{% for error in form.confirm_password.errors %}{{ error }}{% endfor %}</div>{% else %}{{ form.confirm_password(class="form-control", placeholder="请确认新密码") }}{% endif %}</div><div class="d-grid gap-2">{{ form.submit(class="btn btn-primary") }}</div></form></div></div></div>
</div>
{% endblock %}

app\templates\auth\reset_password_request.html

{% extends "base.html" %}{% block title %}重置密码 - {{ super() }}{% endblock %}{% block content %}
<div class="row justify-content-center"><div class="col-md-6"><div class="card shadow"><div class="card-header bg-primary text-white"><h4 class="mb-0"><i class="fas fa-key me-2"></i>重置密码</h4></div><div class="card-body"><p class="card-text">请输入您的注册邮箱,我们将发送密码重置链接到您的邮箱。</p><form method="POST" action="{{ url_for('auth.reset_password_request') }}">{{ form.hidden_tag() }}<div class="mb-3">{{ form.email.label(class="form-label") }}{% if form.email.errors %}{{ form.email(class="form-control is-invalid") }}<div class="invalid-feedback">{% for error in form.email.errors %}{{ error }}{% endfor %}</div>{% else %}{{ form.email(class="form-control", placeholder="请输入您的邮箱") }}{% endif %}</div><div class="d-grid gap-2">{{ form.submit(class="btn btn-primary") }}</div></form></div><div class="card-footer text-center"><a href="{{ url_for('auth.login') }}">返回登录</a></div></div></div>
</div>
{% endblock %}

app\templates\email\reset_password.html

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>重置密码 - 校园二手物品交易平台</title><style>body {font-family: 'Helvetica Neue', Arial, sans-serif;line-height: 1.6;color: #333;max-width: 600px;margin: 0 auto;padding: 20px;}.header {text-align: center;margin-bottom: 30px;}.logo {font-size: 24px;font-weight: bold;color: #3490dc;}.content {background-color: #f8f9fa;border-radius: 5px;padding: 30px;margin-bottom: 20px;}.button {display: inline-block;background-color: #3490dc;color: white;text-decoration: none;padding: 10px 20px;border-radius: 5px;margin-top: 15px;margin-bottom: 15px;}.footer {font-size: 12px;color: #6c757d;text-align: center;margin-top: 30px;border-top: 1px solid #e9ecef;padding-top: 20px;}</style>
</head>
<body><div class="header"><div class="logo">校园二手物品交易平台</div></div><div class="content"><h2>密码重置请求</h2><p>您好,{{ user.username }}!</p><p>我们收到了您的密码重置请求。如果这不是您本人操作,请忽略此邮件。</p><p>要重置您的密码,请点击下面的按钮:</p><div style="text-align: center;"><a href="{{ reset_link }}" class="button">重置密码</a></div><p>或者,您可以复制以下链接到浏览器地址栏:</p><p style="word-break: break-all; font-size: 14px;">{{ reset_link }}</p><p>此链接将在 <strong>1小时</strong> 后失效。</p><p>祝您使用愉快!</p><p>校园二手物品交易平台团队</p></div><div class="footer"><p>此邮件由系统自动发送,请勿直接回复。</p><p>&copy; {{ year }} 校园二手物品交易平台 - 保留所有权利</p></div>
</body>
</html>

app\templates\email\reset_password.txt

校园二手物品交易平台 - 密码重置您好,{{ user.username }}!我们收到了您的密码重置请求。如果这不是您本人操作,请忽略此邮件。要重置您的密码,请访问以下链接:
{{ reset_link }}此链接将在1小时后失效。祝您使用愉快!
校园二手物品交易平台团队---
此邮件由系统自动发送,请勿直接回复。
© {{ year }} 校园二手物品交易平台 - 保留所有权利

app\templates\item\buy.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/41834.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

丝杆支撑座间隙调整不当会带来哪些影响?

丝杆支撑座是一种用于支撑滚珠丝杆的零件&#xff0c;通常用于机床、数控机床、自动化生产线等高精度机械设备中。支撑座间隙调整不当会对机械设备的运行产生多方面的影响&#xff0c;接下来一起了解一下&#xff1a; 1、降低加工精度&#xff1a;在机械加工设备中&#xff0c;…

Unity:EasyRoad3D插件学习 二期

前言&#xff1a; 书接上回。 一、场景视图状态&#xff1a; 创建好道路以后&#xff0c;切换到第一个选项&#xff0c;场景视图状态&#xff0c;查看道路信息&#xff0c;Main Settings修改道路名称、类型&#xff0c;宽度&#xff0c;是否闭环。 RoadWidth改为15&#xff…

内网渗透-DLL和C语言加载木马

免杀进阶技术 1、DLL的定义与使用 DLL:Dynamic Link library,动态链接库&#xff0c;是一个无法自己运行&#xff0c;需要额外的命令或程序来对其接口进行调用&#xff08;类方法、函数&#xff09;。 (1)在DevCpp中创建一个DLL项目 (2)在dllmain.c中定义源代码函数接口 #i…

一洽让常见问题的快速咨询,触手可及

在客户服务场景中&#xff0c;重复性常见问题的处理效率直接影响用户体验与客服成本。针对重复性常见问题&#xff0c;如何以直观的方式呈现给用户&#xff0c;使其能够快速、精准地提出咨询&#xff0c;已成为提升客户满意度的关键因素。 一、传统客服模式的效率枷锁 用户咨…

WEB攻防-Java安全SPEL表达式SSTI模版注入XXEJDBCMyBatis注入

目录 靶场搭建 JavaSec ​编辑​编辑 Hello-Java-Sec(可看到代码对比) SQL注入-JDBC(Java语言连接数据库) 1、采用Statement方法拼接SQL语句 2.PrepareStatement会对SQL语句进行预编译&#xff0c;但如果直接采取拼接的方式构造SQL&#xff0c;此时进行预编译也无用。 3、…

树莓集团南京园区启航:数字经济新地标!

深耕数字产业&#xff0c;构筑生态闭环 树莓集团在数字产业领域拥有超过十年的深厚积累&#xff0c;专注于构建“数字产业”的融合生态链。其核心优势在于有效整合政府、产业、企业及高校资源&#xff0c;形成一个协同创新、价值共生的产业生态闭环系统。 赋能转型&#xff0c…

Redis之bimap/hyperloglog/GEO

bimap/hyperloglog/GEO的真实需求 这些需求的痛点&#xff1a;亿级数据的收集清洗统计展现。一句话&#xff1a;存的进取得快多维度 真正有价值的是统计。 统计的类型 亿级系统中常见的四种统计 聚合统计 统计多个集合元素的聚合结果&#xff0c;就是交差并等集合统计。 排…

nara wpe去混响学习笔记

文章目录 1.WPE方法去混响的基本流程1.1.基本流程 2.离线迭代方法3.在线求法3.1.回顾卡尔曼方法3.2.在线去混响递推滤波器G方法 nara wpe git地址 博客中demo代码下载 参考论文 NARA - WPE: A Python Package for Weighted Prediction Error Dereverberation in Numpy and Ten…

JavaScript函数、箭头函数、匿名函数

1.示例代码(包括用法和注意事项) <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>JS-函数</title…

练习:求平方根

需求&#xff1a;键盘录入一个大于等于2的整数x&#xff0c;计算并返回x的平方根。结果只保留整数部分&#xff0c;小数部分将被舍去。 代码一&#xff1a; //求平方根 //方法一&#xff1a; package Online; import java.util.Scanner; public class SquareRoot {public sta…

win10 安装后的 系统盘的 分区

win10 安装后的 系统盘的 分区 MBR 分区 GPT 分区

反向 SSH 隧道技术实现内网穿透

反向 SSH 隧道技术实现内网穿透 场景描述 有一台内网的 Linux PC 机&#xff0c;想在其他地方&#xff08;如家中&#xff09;使用浏览器&#xff0c;在浏览器中能够使用内网 Linux PC 机的命令行。 实现思路 内网 Linux PC 机在内网可以使用 SSH 进行连接&#xff0c;但内…

[MRCTF2020]套娃

一。 按F12看源代码 发现代码 读代码发现 1.我们传的参数中不能存在_和%5f&#xff0c;可以通过使用空格来代替_&#xff0c;还是能够上传成功。 2.正则表达式"/^23333/ " &#xff0c;开头结尾都被 " " 和 " /"&#xff0c;开头结尾都被&qu…

基于Windows11的WSL2通过Ollama平台安装部署DeepSeek-R1模型

DeepSeek-R1模型各参数版本硬件要求 一、在Windows上安装Linux子系统WSL2 检查电脑是否支持虚拟化&#xff0c;按住<font style"color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">WindowsR</font>输入<font style"color:rgb(199,…

PHP回调后门小总结

目录 1.call_user_func 函数说明 蚁剑连接 2.数组操作造成的单参数回调后门 array_filter 函数说明 蚁剑连接 array_map 函数说明 蚁剑连接 3.二参数回调函数 uasort 函数说明 uksort array_reduce array_udiff 蚁剑连接 4.三参数的回调后门 array_walk 函数说…

MinGW与使用VScode写C语言适配

压缩包 通过网盘分享的文件&#xff1a;MinGW.zip 链接: https://pan.baidu.com/s/1QB-Zkuk2lCIZuVSHc-5T6A 提取码: 2c2q 需要下载的插件 1.翻译 找到VScode页面&#xff0c;从上数第4个&#xff0c;点击扩展&#xff08;以下通此&#xff09; 搜索---Chinese--点击---安装--o…

-PHP 应用SQL 盲注布尔回显延时判断报错处理增删改查方式

#PHP-MYSQL-SQL 操作 - 增删改查 1 、功能&#xff1a;数据查询(对数据感兴趣&#xff09; 查询&#xff1a; SELECT * FROM news where id$id 2 、功能&#xff1a;新增用户&#xff0c;添加新闻等&#xff08;对操作的结果感兴趣&#xff09; 增加&#xff1a; INSERT INT…

Linux一步部署主DNS服务器

#!/bin/bash #部署DHCP服务 #userli 20250319 if [ "$USER" ! "root" ] then echo "错误&#xff1a;非root用户&#xff0c;权限不足&#xff01;" exit 0 fi #防火墙与高级权限 systemctl stop firewalld && systemctl disable …

Softmax 回归 + 损失函数 + 图片分类数据集

Softmax 回归 softmax 回归是机器学习另外一个非常经典且重要的模型&#xff0c;是一个分类问题。 下面先解释一下分类和回归的区别&#xff1a; 简单来说&#xff0c;分类问题从回归的单输出变成了多输出&#xff0c;输出的个数等于类别的个数。 实际上&#xff0c;对于分…

视频管理平台-信息泄露漏洞

一&#xff1a;漏洞描述 EasyCVR 部分版本存在用户信息泄露漏洞&#xff0c;攻击者可直接通过此漏洞获取所有用户的账号密码。 二&#xff1a;fofa查询 title"EasyCVR" 三&#xff1a;漏洞复现 在fofa中寻找有漏洞的url 并访问 poc:/api/v1/userlist?pageinde…