1. 项目背景与核心需求
玩具租赁与销售系统是近年来儿童消费领域的新兴商业模式。传统玩具购买存在价格高、占用空间大、孩子兴趣转移快等痛点,而租赁模式能够有效降低家庭育儿成本,同时促进玩具资源的循环利用。这个Python+Vue全栈项目正是为解决这一市场需求而设计。
系统需要同时支持两种核心业务模式:
- 玩具租赁:用户可按天/周/月租赁玩具,系统需管理租赁周期、押金、损坏赔偿等流程
- 玩具销售:完整的电商功能,包括商品展示、购物车、订单、支付等模块
技术栈选择上,后端采用Python主要基于以下考虑:
- Django/Flask框架成熟,能快速构建RESTful API
- Pandas等库便于处理租赁业务中的复杂计费逻辑
- 丰富的第三方支付接口集成方案
- 与数据分析模块(如用户行为分析)无缝衔接
前端选用Vue.js因其:
- 组件化开发适合电商类UI的快速迭代
- 丰富的UI库(如Element UI)可加速开发
- 响应式特性适配多端访问(PC/移动)
2. 系统架构设计
2.1 整体技术架构
采用前后端分离架构:
code复制前端:Vue 3 + Vue Router + Pinia + Element Plus
后端:Python 3.10 + Django REST Framework
数据库:PostgreSQL(关系型) + Redis(缓存)
部署:Nginx + Docker
2.2 数据库核心表设计
python复制# Django模型示例
class Toy(models.Model):
TOY_TYPE_CHOICES = [
('R', '可租赁'),
('S', '可销售'),
('B', '均可')
]
name = models.CharField(max_length=100)
description = models.TextField()
type = models.CharField(max_length=1, choices=TOY_TYPE_CHOICES)
rental_price_per_day = models.DecimalField(max_digits=6, decimal_places=2)
selling_price = models.DecimalField(max_digits=8, decimal_places=2)
stock = models.PositiveIntegerField()
image_urls = models.JSONField() # 存储多张图片URL
class RentalOrder(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
toy = models.ForeignKey(Toy, on_delete=models.CASCADE)
start_date = models.DateField()
end_date = models.DateField()
total_price = models.DecimalField(max_digits=8, decimal_places=2)
status = models.CharField(max_length=20) # 待支付/已生效/已完成/已取消
2.3 业务逻辑难点解决方案
租赁价格动态计算:
python复制def calculate_rental_price(toy_id, start_date, end_date):
toy = Toy.objects.get(id=toy_id)
days = (end_date - start_date).days
# 阶梯定价逻辑
if days >= 30:
return toy.rental_price_per_day * days * 0.7 # 月租7折
elif days >= 7:
return toy.rental_price_per_day * days * 0.9 # 周租9折
else:
return toy.rental_price_per_day * days
库存并发控制:
使用Django的select_for_update实现行级锁:
python复制from django.db import transaction
@transaction.atomic
def create_order(toy_id):
toy = Toy.objects.select_for_update().get(id=toy_id)
if toy.stock > 0:
toy.stock -= 1
toy.save()
# 创建订单逻辑...
3. 前端关键实现
3.1 玩具展示组件
vue复制<template>
<div class="toy-card">
<el-carousel :interval="4000" v-if="toy.image_urls.length > 1">
<el-carousel-item v-for="(img, idx) in toy.image_urls" :key="idx">
<img :src="img" class="toy-image" />
</el-carousel-item>
</el-carousel>
<div class="price-section">
<span v-if="toy.type !== 'S'" class="rental-price">
¥{{ toy.rental_price_per_day }}/天
</span>
<span v-if="toy.type !== 'R'" class="selling-price">
¥{{ toy.selling_price }}
</span>
</div>
<el-button-group>
<el-button
v-if="toy.type !== 'S'"
type="primary"
@click="showRentalDialog">
立即租赁
</el-button>
<el-button
v-if="toy.type !== 'R'"
type="success"
@click="addToCart">
加入购物车
</el-button>
</el-button-group>
</div>
</template>
3.2 租赁日期选择逻辑
javascript复制// 禁用过去日期和已被租赁的日期
const disabledDate = (time) => {
return time.getTime() < Date.now() - 8.64e7 ||
rentedDates.value.includes(formatDate(time))
}
// 计算租赁总价(响应式)
watchEffect(() => {
if (rentalForm.startDate && rentalForm.endDate) {
const days = Math.ceil(
(new Date(rentalForm.endDate) - new Date(rentalForm.startDate)) / 8.64e7
)
rentalForm.totalPrice = calculatePrice(toy.value.id, days)
}
})
4. 支付系统集成
4.1 支付流程设计
code复制1. 前端提交订单 ->
2. 后端创建支付记录(状态为待支付) ->
3. 返回支付参数给前端 ->
4. 前端调用支付SDK ->
5. 支付成功后异步通知后端 ->
6. 后端验证后更新订单状态
4.2 支付宝支付示例
python复制# views.py
from alipay import AliPay
def create_alipay_payment(order):
alipay = AliPay(
appid=settings.ALIPAY_APP_ID,
app_notify_url=settings.ALIPAY_NOTIFY_URL,
app_private_key_string=settings.APP_PRIVATE_KEY,
alipay_public_key_string=settings.ALIPAY_PUBLIC_KEY,
sign_type="RSA2"
)
order_string = alipay.api_alipay_trade_page_pay(
out_trade_no=order.order_no,
total_amount=str(order.total_price),
subject=f"玩具{order.toy.name}",
return_url=settings.ALIPAY_RETURN_URL,
notify_url=settings.ALIPAY_NOTIFY_URL
)
return f"{settings.ALIPAY_GATEWAY}?{order_string}"
4.3 支付结果异步通知处理
python复制@app.route('/alipay/notify', methods=['POST'])
def alipay_notify():
data = request.form.to_dict()
signature = data.pop("sign")
if alipay.verify(data, signature):
order = Order.objects.get(order_no=data['out_trade_no'])
if float(data['total_amount']) == order.total_price:
order.payment_status = 'paid'
order.save()
return 'success'
return 'fail'
5. 部署与性能优化
5.1 Docker部署方案
dockerfile复制# backend/Dockerfile
FROM python:3.10
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000"]
# frontend/Dockerfile
FROM node:16 as build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
5.2 缓存策略优化
- 玩具详情页缓存:
python复制from django.core.cache import cache
def get_toy_detail(toy_id):
cache_key = f'toy_{toy_id}_detail'
data = cache.get(cache_key)
if not data:
data = Toy.objects.get(id=toy_id)
cache.set(cache_key, data, timeout=3600) # 1小时缓存
return data
- 前端API请求缓存:
javascript复制// 使用axios拦截器实现请求缓存
const cachedRequests = new Map()
axios.interceptors.request.use(config => {
if (config.method === 'get' && config.cache) {
const key = JSON.stringify(config)
if (cachedRequests.has(key)) {
return Promise.resolve(cachedRequests.get(key))
}
}
return config
})
6. 安全防护措施
6.1 常见攻击防护
XSS防护:
python复制# Django默认开启XSS防护,前端额外处理
import DOMPurify from 'dompurify'
const cleanDescription = DOMPurify.sanitize(toy.description)
CSRF防护:
python复制# settings.py
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True
6.2 敏感数据保护
支付信息加密:
python复制from cryptography.fernet import Fernet
fernet = Fernet(settings.ENCRYPTION_KEY)
def encrypt_payment_data(data):
return fernet.encrypt(data.encode())
def decrypt_payment_data(encrypted_data):
return fernet.decrypt(encrypted_data).decode()
7. 扩展功能设计
7.1 会员积分系统
python复制class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
points = models.PositiveIntegerField(default=0)
def add_points(self, amount, reason):
self.points += amount
self.save()
PointHistory.objects.create(
user=self.user,
amount=amount,
reason=reason
)
7.2 玩具清洁追踪
python复制class CleaningRecord(models.Model):
CLEANING_STATUS = [
('P', '待清洁'),
('C', '清洁中'),
('D', '已完成')
]
toy = models.ForeignKey(Toy, on_delete=models.CASCADE)
cleaning_date = models.DateTimeField(auto_now_add=True)
staff = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
status = models.CharField(max_length=1, choices=CLEANING_STATUS)
notes = models.TextField(blank=True)
在开发过程中,有几个关键点需要特别注意:
- 租赁和销售业务的库存需要分开管理,避免冲突
- 支付回调接口要做好幂等处理,防止重复通知导致多次发货
- 玩具图片建议使用CDN加速,提升用户浏览体验
- 日期选择组件要严格校验,避免出现租赁时间倒挂的情况
实际部署时,建议先在小范围内进行压力测试,特别是模拟节假日等租赁高峰期的并发场景。我们曾遇到过一个典型问题:当某个热门玩具突然被大量用户同时抢租时,单纯的数据库锁会导致系统响应变慢。最终的解决方案是引入Redis队列+乐观锁的混合机制,将库存扣减的实时性要求与订单创建的流程解耦。
