1. 项目背景与核心功能设计
电影院售票管理系统作为典型的B/S架构应用,完美契合了Django+Vue3的技术组合优势。这个项目本质上需要解决三个核心问题:影院资源管理、票务交易处理和用户体验优化。
在数据模型设计上,系统需要建立影片(Movie)、放映厅(Hall)、场次(Session)、座位(Seat)、订单(Order)等核心实体。Django的ORM层特别适合处理这种关系型数据结构,比如一个场次Session会同时关联影片Movie和放映厅Hall:
python复制class Session(models.Model):
movie = models.ForeignKey(Movie, on_delete=models.CASCADE)
hall = models.ForeignKey(Hall, on_delete=models.CASCADE)
start_time = models.DateTimeField()
end_time = models.DateTimeField()
price = models.DecimalField(max_digits=6, decimal_places=2)
前端采用Vue3的组合式API开发,可以更好地处理动态座位选择这种复杂交互。比如使用<transition-group>实现座位图的动画效果,配合Pinia管理选座状态:
javascript复制const seatStore = useSeatStore()
const toggleSeat = (seat) => {
if(seat.status === 'available') {
seatStore.addSelected(seat)
} else if(seat.status === 'selected') {
seatStore.removeSelected(seat.id)
}
}
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术栈选型与项目搭建
2.1 Django后端工程配置
使用Python 3.8+和Django 4.1 LTS版本,这是目前最稳定的组合。项目创建时需要注意几个关键配置:
- 数据库配置推荐使用PostgreSQL,其在事务处理和并发性能上优于SQLite:
python复制DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'cinema',
'USER': 'cinema_admin',
'PASSWORD': 'complexpassword123',
'HOST': 'localhost',
'PORT': '5432',
}
}
- 跨域配置必须正确设置,否则Vue前端无法访问API:
python复制CORS_ALLOWED_ORIGINS = [
"http://localhost:5173",
"http://127.0.0.1:5173"
]
- 使用Django REST framework构建API时,认证配置建议采用JWT:
python复制REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
}
2.2 Vue3前端工程初始化
使用Vite创建Vue3项目能获得更好的开发体验:
bash复制npm create vite@latest cinema-frontend --template vue
项目结构建议按功能模块划分:
code复制src/
├── api/ # 接口请求封装
├── components/ # 通用组件
├── composables/ # 组合式函数
├── pages/ # 页面组件
├── stores/ # Pinia状态管理
└── utils/ # 工具函数
安装必要依赖时要注意版本兼容性:
bash复制npm install pinia vue-router@4 axios
npm install --save-dev sass @vitejs/plugin-vue
3. 核心功能模块实现
3.1 场次管理与座位编排
后端需要提供场次查询接口,通常需要处理复杂的关联查询:
python复制class SessionViewSet(viewsets.ModelViewSet):
queryset = Session.objects.select_related('movie', 'hall').prefetch_related('hall__seats')
serializer_class = SessionSerializer
@action(detail=True, methods=['get'])
def seats(self, request, pk=None):
session = self.get_object()
seats = session.hall.seats.all()
serializer = SeatSerializer(seats, many=True)
return Response(serializer.data)
前端座位选择界面需要考虑多种状态:
vue复制<template>
<div class="seat-map">
<div
v-for="seat in seats"
:key="seat.id"
:class="['seat', seat.status]"
@click="toggleSeat(seat)"
></div>
</div>
</template>
<style scoped>
.seat {
width: 30px;
height: 30px;
margin: 5px;
&.available { background: #ccc; }
&.selected { background: #4CAF50; }
&.occupied { background: #F44336; }
}
</style>
3.2 订单处理与支付流程
订单创建需要处理事务操作,确保数据一致性:
python复制@transaction.atomic
def create_order(request):
try:
with transaction.atomic():
order = Order.objects.create(
user=request.user,
total_amount=calculate_total(request.data['seats'])
)
for seat_id in request.data['seats']:
OrderItem.objects.create(
order=order,
seat_id=seat_id,
price=get_seat_price(seat_id)
)
Seat.objects.filter(id=seat_id).update(status='occupied')
return Response(OrderSerializer(order).data)
except Exception as e:
return Response({'error': str(e)}, status=400)
前端支付流程需要处理多种状态:
javascript复制const handlePayment = async () => {
paymentStore.setLoading(true)
try {
const { data } = await createOrder(selectedSeats.value)
const res = await startPayment(data.id)
if(res.success) {
router.push(`/orders/${data.id}`)
}
} catch (error) {
notification.error(error.message)
} finally {
paymentStore.setLoading(false)
}
}
4. 项目部署与性能优化
4.1 生产环境部署方案
使用Docker容器化部署能简化环境配置:
dockerfile复制# Django后端Dockerfile
FROM python:3.8
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
COPY requirements.txt /code/
RUN pip install -r requirements.txt
COPY . /code/
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000"]
前端部署建议使用Nginx做静态资源服务:
nginx复制server {
listen 80;
server_name cinema.example.com;
location / {
root /var/www/cinema-frontend/dist;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://backend:8000;
proxy_set_header Host $host;
}
}
4.2 性能优化实践
- 数据库查询优化:
python复制# 使用select_related减少查询次数
sessions = Session.objects.select_related('movie', 'hall').filter(
start_time__gte=timezone.now()
)
# 使用prefetch_related优化多对多关系
halls = Hall.objects.prefetch_related('seats').all()
- 前端性能优化:
javascript复制// 使用虚拟滚动优化长列表
import { useVirtualList } from '@vueuse/core'
const { list, containerProps, wrapperProps } = useVirtualList(
allMovies,
{ itemHeight: 60 }
)
- 缓存策略:
python复制# 使用Django缓存框架
from django.core.cache import cache
def get_popular_movies():
key = 'popular_movies'
movies = cache.get(key)
if not movies:
movies = Movie.objects.filter(is_featured=True)[:10]
cache.set(key, movies, timeout=60*15) # 缓存15分钟
return movies
5. 常见问题与调试技巧
5.1 跨域问题深度解决
除了基础的CORS配置,还需要注意:
- 开发环境下Vite代理配置:
javascript复制// vite.config.js
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
}
})
- 生产环境Nginx配置需要处理OPTIONS请求:
nginx复制location /api {
if ($request_method = OPTIONS) {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
}
5.2 Vue3与Django模板变量冲突
当使用Django模板渲染Vue应用时,需要修改Vue的模板分隔符:
javascript复制// main.js
import { createApp } from 'vue'
const app = createApp(App)
app.config.compilerOptions.delimiters = ['${', '}']
5.3 时区处理最佳实践
- Django设置:
python复制TIME_ZONE = 'Asia/Shanghai'
USE_TZ = True
- 前端显示时统一转换:
javascript复制import { format } from 'date-fns'
const formatSessionTime = (datetime) => {
return format(new Date(datetime), 'MM/dd HH:mm')
}
- 数据库存储UTC时间,展示时转换:
python复制from django.utils.timezone import localtime
def get_session_time(session):
return localtime(session.start_time).strftime('%H:%M')
6. 项目扩展与进阶功能
6.1 微信小程序集成
通过Django REST framework提供API给小程序端:
python复制class MiniProgramAuth(authentication.BaseAuthentication):
def authenticate(self, request):
code = request.data.get('code')
# 调用微信接口获取openid
openid = get_openid(code)
user = User.objects.get_or_create(openid=openid)
return (user, None)
6.2 数据分析看板
使用Django的聚合功能生成报表数据:
python复制from django.db.models import Count, Sum
def get_daily_sales():
return Order.objects.annotate(
day=TruncDay('created_at')
).values('day').annotate(
total=Sum('total_amount'),
count=Count('id')
).order_by('day')
前端使用ECharts可视化:
javascript复制import * as echarts from 'echarts'
const initChart = () => {
const chart = echarts.init(document.getElementById('chart'))
chart.setOption({
xAxis: { type: 'category', data: dates },
yAxis: { type: 'value' },
series: [{ data: sales, type: 'line' }]
})
}
6.3 微服务化改造
将系统拆分为多个服务:
- 用户服务:处理认证和用户数据
- 影片服务:管理影片信息和排片
- 订单服务:处理购票和支付
- 通知服务:发送短信和邮件提醒
使用DRF的API Gateway模式:
python复制# api_gateway/views.py
import requests
def proxy_to_service(request, service_path):
service_url = f'http://{determine_service(request.path)}/{service_path}'
headers = {'Authorization': request.headers.get('Authorization')}
resp = requests.request(
method=request.method,
url=service_url,
headers=headers,
data=request.body
)
return Response(resp.json(), status=resp.status_code)
7. 测试策略与质量保障
7.1 Django后端测试
模型测试示例:
python复制class MovieModelTest(TestCase):
def test_create_movie(self):
movie = Movie.objects.create(
title='Test Movie',
duration=120,
description='Test description'
)
self.assertEqual(movie.title, 'Test Movie')
self.assertEqual(movie.status, 'coming')
API测试使用DRF的APIClient:
python复制class SessionAPITest(APITestCase):
def setUp(self):
self.movie = Movie.objects.create(title='Inception')
self.hall = Hall.objects.create(name='Hall 1', capacity=100)
def test_list_sessions(self):
response = self.client.get('/api/sessions/')
self.assertEqual(response.status_code, 200)
7.2 Vue组件测试
使用Vitest测试组件:
javascript复制import { mount } from '@vue/test-utils'
import SeatMap from './SeatMap.vue'
test('renders seat map', async () => {
const wrapper = mount(SeatMap, {
props: { seats: [{ id: 1, status: 'available' }] }
})
expect(wrapper.find('.seat').exists()).toBe(true)
})
7.3 E2E测试方案
使用Cypress进行端到端测试:
javascript复制describe('Movie Booking Flow', () => {
it('should complete booking process', () => {
cy.visit('/movies')
cy.contains('Inception').click()
cy.get('.session-time:first').click()
cy.get('.seat.available:first').click()
cy.contains('Confirm').click()
cy.url().should('include', '/payment')
})
})
8. 安全防护措施
8.1 常见Web安全防护
- CSRF防护(Django默认启用):
python复制# settings.py
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True
- XSS防护:
vue复制<!-- 使用v-html时过滤内容 -->
<div v-html="sanitizeHtml(content)"></div>
// 使用DOMPurify
import DOMPurify from 'dompurify'
const sanitizeHtml = (dirty) => DOMPurify.sanitize(dirty)
- SQL注入防护:
python复制# 永远使用ORM或参数化查询
# 错误示范
Session.objects.raw(f'SELECT * FROM app_session WHERE id = {user_input}')
# 正确做法
Session.objects.filter(id=user_input)
8.2 敏感数据保护
- 密码哈希:
python复制# Django自动使用PBKDF2算法
user = User.objects.create_user(
username='admin',
password='complexpassword123' # 自动哈希存储
)
- 敏感字段加密:
python复制from django_cryptography.fields import encrypt
class PaymentInfo(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
card_number = encrypt(models.CharField(max_length=20))
- 日志脱敏:
python复制import re
def sanitize_log(message):
return re.sub(r'\b\d{4}[\s-]?\d{4}[\s-]?\d{4}\b', '[CARD]', message)
9. 项目文档与协作
9.1 API文档生成
使用drf-yasg生成Swagger文档:
python复制# urls.py
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
schema_view = get_schema_view(
openapi.Info(
title="Cinema API",
default_version='v1',
),
public=True,
)
urlpatterns = [
path('swagger/', schema_view.with_ui('swagger')),
]
9.2 前端组件文档
使用Storybook记录Vue组件:
javascript复制// SeatMap.stories.js
export default {
title: 'SeatMap',
component: SeatMap
}
const Template = (args) => ({
components: { SeatMap },
setup() { return { args } },
template: '<SeatMap v-bind="args" />'
})
export const Default = Template.bind({})
Default.args = {
seats: [
{ id: 1, status: 'available' },
{ id: 2, status: 'occupied' }
]
}
9.3 数据库文档
使用django-extensions生成ER图:
bash复制python manage.py graph_models -a -o erd.png
10. 项目经验与实用技巧
10.1 开发环境加速
- 使用django-debug-toolbar优化查询:
python复制# settings.py
INSTALLED_APPS += ['debug_toolbar']
MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']
INTERNAL_IPS = ['127.0.0.1']
- Vue开发时按需加载组件:
javascript复制const MovieList = defineAsyncComponent(() => import('./MovieList.vue'))
10.2 部署实用技巧
- 使用Whitenoise处理静态文件:
python复制# settings.py
MIDDLEWARE = ['whitenoise.middleware.WhiteNoiseMiddleware']
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
- 前端资源版本控制:
javascript复制// vite.config.js
export default defineConfig({
build: {
rollupOptions: {
output: {
assetFileNames: 'assets/[name]-[hash][extname]'
}
}
}
})
10.3 调试技巧
- Django shell_plus增强:
bash复制pip install django-extensions ipython
python manage.py shell_plus --ipython
- Vue3开发工具技巧:
javascript复制// 在setup函数中使用debugger
setup() {
const state = reactive({ count: 0 })
debugger // 浏览器会在此暂停
return { state }
}
- 网络请求调试:
javascript复制// axios拦截器
axios.interceptors.request.use(config => {
console.log('Request:', config.method, config.url)
return config
})
axios.interceptors.response.use(response => {
console.log('Response:', response.status, response.data)
return response
})
