1. 项目概述
去年接手了一个校园物品租赁平台的开发需求,甲方要求三个月内完成从零到上线的全流程。作为主力开发,我选择了Django作为后端框架,最终交付的系统日均处理订单量超过2000笔。本文将分享这个项目的完整技术实现方案,包含那些教科书里不会写的实战细节。
这个系统本质上是一个P2P租赁平台,连接物品所有者和需求方。与传统电商不同,租赁业务涉及物品状态追踪、使用时长计费、押金管理等特殊场景。我们采用Django + MySQL的基础架构,配合Redis做缓存,前端使用Vue.js实现响应式布局,整套系统在阿里云2核4G的服务器上稳定支撑了校庆期间3倍于平时的流量峰值。
2. 技术选型解析
2.1 为什么选择Django?
相比Flask等轻量级框架,Django自带的Admin后台、ORM系统和认证模块能节省约40%的开发时间。具体到租赁系统:
- 内置Auth系统直接满足用户认证需求,扩展用户模型只需继承AbstractUser:
python复制class User(AbstractUser):
credit_score = models.IntegerField(default=100)
id_card = models.CharField(max_length=18, blank=True)
- Admin后台快速生成管理界面,通过简单配置即可实现:
python复制@admin.register(Item)
class ItemAdmin(admin.ModelAdmin):
list_display = ('name', 'owner', 'rental_price', 'status')
list_filter = ('category', 'status')
search_fields = ('name', 'description')
- ORM系统处理复杂查询,比如获取某用户所有待归还物品:
python复制overdue_items = Item.objects.filter(
current_rental__user=request.user,
current_rental__end_date__lt=timezone.now()
).select_related('owner')
2.2 数据库设计要点
租赁系统的核心在于状态流转,我们设计了7张主表:
| 表名 | 关键字段 | 说明 |
|---|---|---|
| user | credit_score, deposit | 用户信用分和押金余额 |
| item | status, hourly_rate | 物品状态和计费方式 |
| rental_order | start_time, actual_end_time | 订单时间记录 |
| payment | amount, payment_method | 支付流水 |
| damage_report | images, compensation | 损坏赔偿 |
| review | rating, content | 评价系统 |
| message | is_read, content | 站内信 |
特别注意item表的status字段设计:
python复制STATUS_CHOICES = (
('available', '可租用'),
('rented', '出租中'),
('maintenance', '维护中'),
('reported', '报损中')
)
3. 核心业务实现
3.1 租赁状态机实现
物品状态流转是系统最复杂的部分,我们采用状态模式封装业务规则:
python复制class ItemState:
def rent(self, item):
raise NotImplementedError
def return_item(self, item):
raise NotImplementedError
class AvailableState(ItemState):
def rent(self, item):
if item.owner == rental_user:
raise ValidationError("不能租赁自己的物品")
item.status = 'rented'
item.save()
class RentedState(ItemState):
def return_item(self, item):
item.status = 'available'
# 计算租金逻辑
duration = timezone.now() - item.current_rental.start_time
charge = duration.total_seconds() / 3600 * item.hourly_rate
create_payment_record(charge)
item.save()
3.2 定时任务设计
使用Celery处理以下异步任务:
- 租金计算:每小时扫描出租中的物品
python复制@app.task
def calculate_hourly_charges():
rentals = RentalOrder.objects.filter(
status='ongoing',
end_date__gt=timezone.now()
)
for rental in rentals:
charge = rental.item.hourly_rate
rental.user.deposit -= charge
rental.user.save()
create_payment_record(charge)
- 逾期处理:每天检查超期未还物品
python复制@app.task
def check_overdue_rentals():
overdue = RentalOrder.objects.filter(
end_date__lt=timezone.now(),
status='ongoing'
)
for rental in overdue:
send_overdue_notification(rental.user)
rental.user.credit_score -= 5
rental.user.save()
4. 安全防护方案
4.1 支付安全实现
支付模块我们对接了支付宝沙箱环境,关键点包括:
- 使用签名验证回调真实性
python复制def verify_alipay_signature(params):
sign = params.pop('sign', None)
params.pop('sign_type', None)
message = '&'.join([f'{k}={v}' for k,v in sorted(params.items())])
return rsa.verify(message.encode(), sign, ALIPAY_PUBLIC_KEY)
- 处理幂等性问题
python复制@transaction.atomic
def process_payment(payment_id):
payment = Payment.objects.select_for_update().get(id=payment_id)
if payment.status != 'pending':
return
# 实际支付逻辑
4.2 防欺诈措施
针对校园场景特别设计:
- 信用分系统:
python复制def update_credit_score(user, action):
SCORE_RULES = {
'overdue': -5,
'damage': -10,
'ontime_return': +2
}
user.credit_score += SCORE_RULES.get(action, 0)
user.save()
- 押金冻结机制:
python复制def freeze_deposit(user, amount):
if user.deposit < amount:
raise InsufficientDeposit
user.frozen_deposit += amount
user.deposit -= amount
user.save()
5. 性能优化实践
5.1 缓存策略
使用Redis缓存三类数据:
- 物品详情:设置5分钟过期
python复制def get_item_details(item_id):
cache_key = f'item_{item_id}'
data = cache.get(cache_key)
if not data:
data = serialize_item(Item.objects.get(id=item_id))
cache.set(cache_key, data, 300)
return data
- 热门分类:每日凌晨更新
python复制def update_hot_categories():
categories = Category.objects.annotate(
count=Count('items')
).order_by('-count')[:5]
cache.set('hot_categories', categories, 86400)
5.2 数据库优化
- 索引优化:
python复制class RentalOrder(models.Model):
class Meta:
indexes = [
models.Index(fields=['user', 'status']),
models.Index(fields=['end_date'])
]
- 查询优化:
python复制# 错误示范(N+1查询)
for order in RentalOrder.objects.all():
print(order.item.name)
# 正确做法
for order in RentalOrder.objects.select_related('item').all():
print(order.item.name)
6. 部署实战记录
6.1 服务器配置
阿里云ECS基础配置:
- 系统:Ubuntu 20.04
- 软件栈:
- Nginx 1.18(负载均衡)
- Gunicorn 20.1(WSGI服务器)
- MySQL 8.0(主从复制)
- Redis 6.2(缓存)
关键Nginx配置:
nginx复制location /static {
alias /var/www/static;
expires 30d;
}
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
6.2 监控方案
使用Prometheus + Grafana监控:
-
关键指标:
- 请求响应时间(<500ms)
- 数据库连接数(<80%)
- 队列积压(<100)
-
自定义指标采集:
python复制from prometheus_client import Counter
RENTAL_REQUESTS = Counter(
'rental_requests_total',
'Total rental requests',
['method', 'status']
)
def rental_view(request):
RENTAL_REQUESTS.labels(method=request.method, status='success').inc()
7. 踩坑经验总结
-
时区问题:
- 永远使用
django.utils.timezone替代datetime - 数据库配置
USE_TZ = True
- 永远使用
-
并发修改:
- 使用
select_for_update()处理库存竞争
python复制with transaction.atomic(): item = Item.objects.select_for_update().get(id=item_id) if item.status == 'available': item.status = 'rented' item.save() - 使用
-
文件上传:
- 限制文件类型和大小
python复制class ItemImageForm(forms.ModelForm): class Meta: model = ItemImage fields = ['image'] def clean_image(self): image = self.cleaned_data['image'] if image.size > 2*1024*1024: raise ValidationError("图片大小不能超过2MB") return image
这个项目让我深刻体会到,好的系统设计必须考虑业务场景的特殊性。比如租赁业务中,我们最初没有设计物品的"预锁定"状态,导致高峰期出现超租问题。后来通过引入Redis分布式锁才彻底解决,具体实现可以参考项目源码中的locking.py模块。