二手教材交易一直是个让大学生头疼的问题。每到学期初,新生需要花高价购买新教材;而高年级学生用过的教材却堆在宿舍吃灰。这个基于Django的二手书交易平台就是为了解决这个痛点而设计的。
我在大学期间就深有体会:大一时花800多块买的专业课教材,到大四毕业时5块钱都没人要。现在很多高校都有类似的线下交易渠道,但要么信息不对称,要么交易流程繁琐。这个平台的核心价值在于:
选择Django作为后端框架主要基于以下考虑:
前端采用Bootstrap 5 + jQuery的组合:
数据库使用MySQL 8.0:
mermaid复制graph TD
A[用户系统] --> B[书籍管理]
A --> C[订单系统]
B --> D[搜索系统]
C --> E[消息通知]
D --> F[推荐系统]
主要功能模块包括:
users_user表(用户信息)
sql复制CREATE TABLE `users_user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`student_id` varchar(20) NOT NULL,
`real_name` varchar(50) NOT NULL,
`college` varchar(100) NOT NULL,
`major` varchar(100) NOT NULL,
`grade` varchar(10) NOT NULL,
`credit_score` int DEFAULT '100',
`avatar` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `student_id` (`student_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
books_book表(书籍信息)
sql复制CREATE TABLE `books_book` (
`id` bigint NOT NULL AUTO_INCREMENT,
`isbn` varchar(20) DEFAULT NULL,
`title` varchar(200) NOT NULL,
`author` varchar(100) NOT NULL,
`publisher` varchar(100) NOT NULL,
`edition` varchar(20) DEFAULT NULL,
`course` varchar(100) DEFAULT NULL,
`original_price` decimal(10,2) NOT NULL,
`selling_price` decimal(10,2) NOT NULL,
`condition` varchar(20) NOT NULL,
`description` text,
`seller_id` bigint NOT NULL,
`status` varchar(20) NOT NULL DEFAULT 'available',
`cover_image` varchar(100) DEFAULT NULL,
`post_time` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `books_book_seller_id_3a8a6e1b_fk_users_user_id` (`seller_id`),
CONSTRAINT `books_book_seller_id_3a8a6e1b_fk_users_user_id` FOREIGN KEY (`seller_id`) REFERENCES `users_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
学生认证:
书籍状态管理:
价格策略:
采用Django Haystack + Whoosh实现全文搜索:
python复制# search_indexes.py
class BookIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
title = indexes.CharField(model_attr='title')
author = indexes.CharField(model_attr='author')
course = indexes.CharField(model_attr='course')
def get_model(self):
return Book
def index_queryset(self, using=None):
return self.get_model().objects.filter(status='available')
搜索模板文件(search/indexes/books/book_text.txt):
code复制{{ object.title }}
{{ object.author }}
{{ object.publisher }}
{{ object.course }}
{{ object.description }}
使用django-fsm实现订单状态管理:
python复制from django_fsm import FSMField, transition
class Order(models.Model):
STATUS_CHOICES = (
('created', '已创建'),
('paid', '已支付'),
('delivered', '已交付'),
('completed', '已完成'),
('cancelled', '已取消')
)
status = FSMField(default='created', protected=True)
@transition(field=status, source='created', target='paid')
def make_payment(self):
"""买家付款"""
pass
@transition(field=status, source='paid', target='delivered')
def confirm_delivery(self):
"""卖家确认交付"""
pass
@transition(field=status, source='delivered', target='completed')
def confirm_receipt(self):
"""买家确认收货"""
pass
使用Django Channels实现实时通知:
python复制# consumers.py
class NotificationConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.user_id = self.scope['url_route']['kwargs']['user_id']
await self.channel_layer.group_add(
f'notifications_{self.user_id}',
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
await self.channel_layer.group_discard(
f'notifications_{self.user_id}',
self.channel_name
)
async def send_notification(self, event):
await self.send(text_data=json.dumps(event['message']))
认证系统:
数据保护:
交易安全:
缓存策略:
python复制# settings.py
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'TIMEOUT': 60 * 15, # 15分钟
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
数据库优化:
静态文件处理:
推荐使用Docker Compose部署:
yaml复制version: '3.8'
services:
web:
build: .
command: gunicorn config.wsgi:application --bind 0.0.0.0:8000
volumes:
- .:/code
ports:
- "8000:8000"
depends_on:
- redis
- db
environment:
- DEBUG=0
- DATABASE_URL=mysql://booktrade:password@db/booktrade
- REDIS_URL=redis://redis:6379/0
db:
image: mysql:8.0
volumes:
- db_data:/var/lib/mysql
environment:
- MYSQL_DATABASE=booktrade
- MYSQL_USER=booktrade
- MYSQL_PASSWORD=password
- MYSQL_ROOT_PASSWORD=rootpassword
redis:
image: redis:6-alpine
volumes:
db_data:
日志配置:
python复制LOGGING = {
'version': 1,
'handlers': {
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': '/var/log/django/booktrade.log',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'INFO',
},
},
}
健康检查:
问题:学生不愿意花时间填写详细的书籍信息
解决方案:
问题:书籍描述与实际不符引发纠纷
解决方案:
问题:如何让学生知道并使用平台
解决方案:
在实际开发过程中,有几个关键点值得特别注意:
用户认证流程:初期我们尝试自动同步学校教务系统数据,但遇到接口权限问题。后来改为"学号+姓名"人工审核模式,虽然增加了运营成本,但大幅提高了账号真实性。
图片存储方案:开始使用本地存储,很快出现磁盘空间不足。迁移到阿里云OSS后,不仅解决了存储问题,还提高了图片加载速度。关键配置:
python复制DEFAULT_FILE_STORAGE = 'storages.backends.aliyun.AliyunOSSStorage'
ALIYUN_OSS_ACCESS_KEY_ID = 'your_key_id'
ALIYUN_OSS_ACCESS_KEY_SECRET = 'your_key_secret'
ALIYUN_OSS_ENDPOINT = 'oss-cn-beijing.aliyuncs.com'
ALIYUN_OSS_BUCKET_NAME = 'your-bucket-name'
交易通知时效性:最初采用邮件通知,发现学生很少查看。改为微信服务号推送+站内消息双通道后,重要消息的到达率从30%提升到85%。
测试数据生成:使用django-seed生成测试数据时,特别注意保持专业/课程信息的合理性:
python复制def generate_books():
courses = ['高等数学', '大学物理', '数据结构', '计算机网络']
conditions = ['全新', '轻微使用', '明显使用痕迹', '有笔记']
for i in range(100):
Book.objects.create(
title=f'教材{i}',
author=f'作者{i%10}',
course=random.choice(courses),
condition=random.choice(conditions),
original_price=decimal.Decimal(random.randint(30, 100)),
selling_price=decimal.Decimal(random.randint(10, 80)),
seller=random.choice(User.objects.all())
)
这个项目从技术角度看不算复杂,但真正挑战在于如何设计出符合学生真实使用习惯的功能。我们在3个校区做了200份问卷调查后发现:85%的学生更倾向线下面交、最关注的是教材版本是否正确、平均期望的二手书价格是新书的4-6折。这些数据直接影响了我们的功能优先级排序。