1. 项目背景与技术选型
校园电影网站作为学生群体的娱乐信息平台,需要兼顾内容展示、用户互动和后台管理的综合需求。SpringBoot+Vue的组合完美契合了这类Web应用的开发需求,形成了清晰的前后端分离架构。
SpringBoot作为后端框架的选择主要基于以下考量:
- 内嵌Tomcat服务器简化部署流程
- 自动配置机制减少XML配置
- 丰富的Starter依赖快速集成MyBatis、Redis等组件
- Actuator模块提供完善的监控端点
Vue.js作为前端框架的优势体现在:
- 组件化开发模式提高代码复用率
- 响应式数据绑定简化DOM操作
- Vue Router实现无缝页面切换
- Axios轻松处理RESTful API调用
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计
2.1 整体架构分层
code复制┌───────────────────────────────────────┐
│ 客户端层 │
│ ┌───────────┐ ┌─────────────┐ │
│ │ 移动端 │ │ PC浏览器 │ │
│ └───────────┘ └─────────────┘ │
└───────────────────┬───────────────────┘
│ HTTP(S)
┌───────────────────▼───────────────────┐
│ 表现层 │
│ ┌─────────────────────────────────┐ │
│ │ Vue前端工程 │ │
│ │ ┌───────┐ ┌───────┐ ┌───────┐ │ │
│ │ │组件库 │ │路由管理│ │状态管理│ │ │
│ │ └───────┘ └───────┘ └───────┘ │ │
│ └─────────────────────────────────┘ │
└───────────────────┬───────────────────┘
│ REST API
┌───────────────────▼───────────────────┐
│ 业务逻辑层 │
│ ┌─────────────────────────────────┐ │
│ │ SpringBoot应用 │ │
│ │ ┌───────┐ ┌───────┐ ┌───────┐ │ │
│ │ │控制层 │ │服务层 │ │数据层 │ │ │
│ │ └───────┘ └───────┘ └───────┘ │ │
│ └─────────────────────────────────┘ │
└───────────────────┬───────────────────┘
│ JDBC/ORM
┌───────────────────▼───────────────────┐
│ 数据持久层 │
│ ┌───────┐ ┌───────┐ ┌─────────────┐ │
│ │ MySQL │ │ Redis │ │ 文件存储系统 │ │
│ └───────┘ └───────┘ └─────────────┘ │
└───────────────────────────────────────┘
2.2 数据库设计要点
核心表结构设计示例:
sql复制-- 电影信息表
CREATE TABLE `movie` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL COMMENT '电影名称',
`cover_url` varchar(255) DEFAULT NULL COMMENT '封面图URL',
`duration` int(11) DEFAULT NULL COMMENT '时长(分钟)',
`release_date` date DEFAULT NULL COMMENT '上映日期',
`score` decimal(3,1) DEFAULT '0.0' COMMENT '评分',
`director` varchar(50) DEFAULT NULL COMMENT '导演',
`actors` varchar(255) DEFAULT NULL COMMENT '主演',
`plot` text COMMENT '剧情简介',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FULLTEXT KEY `ft_idx` (`title`,`actors`) COMMENT '全文检索索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 放映场次表
CREATE TABLE `schedule` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`movie_id` int(11) NOT NULL,
`room_id` int(11) NOT NULL COMMENT '放映厅',
`start_time` datetime NOT NULL COMMENT '开始时间',
`end_time` datetime NOT NULL COMMENT '结束时间',
`price` decimal(10,2) NOT NULL COMMENT '票价',
`seat_info` text COMMENT '座位状态JSON',
PRIMARY KEY (`id`),
KEY `idx_movie` (`movie_id`),
KEY `idx_time` (`start_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 用户收藏表
CREATE TABLE `user_favorite` (
`user_id` int(11) NOT NULL,
`movie_id` int(11) NOT NULL,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`user_id`,`movie_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 前端工程实现
3.1 Vue项目结构规划
code复制src/
├── api/ # Axios接口封装
│ ├── movie.js # 电影相关接口
│ └── user.js # 用户相关接口
├── assets/ # 静态资源
├── components/ # 公共组件
│ ├── MovieCard.vue # 电影卡片
│ ├── RatingStar.vue # 评分组件
│ └── SeatPicker.vue # 选座组件
├── router/ # 路由配置
│ └── index.js
├── store/ # Vuex状态管理
│ ├── modules/
│ │ ├── movie.js
│ │ └── user.js
│ └── index.js
├── utils/ # 工具函数
├── views/ # 页面组件
│ ├── Home.vue # 首页
│ ├── Movie/
│ │ ├── Detail.vue # 详情页
│ │ └── List.vue # 列表页
│ └── User/
│ ├── Login.vue # 登录
│ └── Center.vue # 个人中心
└── main.js # 应用入口
3.2 典型组件实现示例
电影卡片组件(MovieCard.vue):
vue复制<template>
<div class="movie-card" @click="goDetail">
<div class="cover-wrapper">
<img :src="coverUrl" :alt="title" class="cover-img">
<div class="score-badge" v-if="score > 0">
{{ score.toFixed(1) }}
</div>
</div>
<div class="info">
<h3 class="title">{{ title }}</h3>
<p class="meta">
<span>{{ duration }}分钟</span>
<span>{{ releaseDate }}</span>
</p>
<button
class="favorite-btn"
@click.stop="toggleFavorite"
:class="{active: isFavorite}"
>
♥
</button>
</div>
</div>
</template>
<script>
export default {
props: {
id: Number,
title: String,
coverUrl: String,
score: Number,
duration: Number,
releaseDate: String,
isFavorite: Boolean
},
methods: {
goDetail() {
this.$router.push(`/movie/${this.id}`)
},
async toggleFavorite() {
try {
const action = this.isFavorite ? 'removeFavorite' : 'addFavorite'
await this.$store.dispatch(`user/${action}`, this.id)
this.$emit('favorite-change', !this.isFavorite)
} catch (e) {
this.$message.error(e.message)
}
}
}
}
</script>
<style scoped>
.movie-card {
width: 200px;
margin: 10px;
cursor: pointer;
transition: all 0.3s;
}
.movie-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.cover-wrapper {
position: relative;
height: 280px;
overflow: hidden;
}
.cover-img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.5s;
}
.movie-card:hover .cover-img {
transform: scale(1.05)
