这个基于SpringBoot的网络课程信息管理系统是一个典型的全栈开发项目,采用了前后端分离的架构设计。系统后端使用SpringBoot+MyBatis技术栈,前端采用Vue.js框架,同时支持Uniapp小程序端访问。作为一个完整的教学管理系统,它涵盖了课程管理、用户权限、数据统计等核心功能模块。
在实际开发过程中,我发现这种技术组合特别适合中小型教育机构的信息化建设需求。SpringBoot提供了快速开发的能力,Vue.js带来了流畅的前端体验,而Uniapp则实现了多端适配。下面我将详细解析这个系统的技术实现和关键设计思路。
SpringBoot作为我们的基础框架,版本选择2.7.x(长期支持版本)。这个版本在稳定性和功能完整性上达到了很好的平衡。我们特别利用了它的几个核心特性:
MyBatis作为ORM框架,版本使用3.5.6。我们采用了XML映射文件+注解的混合模式,既保持了SQL的可控性,又简化了简单CRUD操作。一个典型的Mapper接口如下:
java复制@Mapper
public interface CourseMapper {
@Select("SELECT * FROM course WHERE id = #{id}")
Course selectById(@Param("id") Long id);
@Insert("INSERT INTO course(name,teacher,start_time) VALUES(#{name},#{teacher},#{startTime})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(Course course);
// 复杂查询使用XML配置
List<Course> selectByCondition(CourseQuery query);
}
Vue 3.x组合式API是我们的主要选择,配合以下关键库:
前端项目结构采用功能模块划分:
code复制src/
├── api/ # API请求封装
├── assets/ # 静态资源
├── components/ # 公共组件
├── composables/ # 组合式函数
├── router/ # 路由配置
├── stores/ # Pinia状态
├── styles/ # 全局样式
└── views/ # 页面组件
系统使用MySQL 8.0作为主数据库,主要表结构包括:
用户体系相关:
业务核心表:
系统功能表:
一个典型的课程表DDL示例:
sql复制CREATE TABLE `edu_course` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(50) NOT NULL COMMENT '课程标题',
`cover` varchar(255) DEFAULT NULL COMMENT '课程封面',
`teacher_id` bigint NOT NULL COMMENT '讲师ID',
`subject_id` bigint DEFAULT NULL COMMENT '课程分类',
`price` decimal(10,2) DEFAULT '0.00' COMMENT '课程价格',
`lesson_num` int DEFAULT '0' COMMENT '课时数',
`status` tinyint DEFAULT '0' COMMENT '课程状态:0未发布 1已发布',
`view_count` bigint DEFAULT '0' COMMENT '浏览数量',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_teacher_id` (`teacher_id`),
KEY `idx_subject_id` (`subject_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程表';
系统采用RBAC(基于角色的访问控制)模型,实现分为以下几个关键部分:
java复制@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequiresPermissions {
String[] value();
Logical logical() default Logical.AND;
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequiresRoles {
String[] value();
Logical logical() default Logical.AND;
}
java复制public class PermissionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
// 检查权限注解
RequiresPermissions permissions = method.getAnnotation(RequiresPermissions.class);
if (permissions != null) {
String[] requiredPermissions = permissions.value();
Logical logical = permissions.logical();
// 获取当前用户权限
Set<String> userPermissions = getCurrentUserPermissions();
// 权限校验逻辑
if (logical == Logical.AND) {
for (String permission : requiredPermissions) {
if (!userPermissions.contains(permission)) {
throw new PermissionDeniedException();
}
}
} else {
boolean hasAny = false;
for (String permission : requiredPermissions) {
if (userPermissions.contains(permission)) {
hasAny = true;
break;
}
}
if (!hasAny) {
throw new PermissionDeniedException();
}
}
}
return true;
}
}
javascript复制// 全局权限检查方法
export function checkPermission(permissions) {
const userStore = useUserStore()
if (!permissions) return true
const userPermissions = userStore.permissions
if (Array.isArray(permissions)) {
return permissions.some(perm => userPermissions.includes(perm))
}
return userPermissions.includes(permissions)
}
// 指令方式使用
<el-button v-permission="'course:add'">新增课程</el-button>
课程管理是系统的核心功能,主要包含以下子模块:
视频上传采用了分片上传技术,核心代码如下:
java复制@PostMapping("/upload")
public R upload(@RequestParam("file") MultipartFile file,
@RequestParam("chunkNumber") int chunkNumber,
@RequestParam("totalChunks") int totalChunks,
@RequestParam("identifier") String identifier) throws IOException {
// 创建临时目录
String tempDir = System.getProperty("java.io.tmpdir") + "/upload/" + identifier;
File dir = new File(tempDir);
if (!dir.exists()) {
dir.mkdirs();
}
// 保存分片
File chunkFile = new File(tempDir, chunkNumber + ".part");
file.transferTo(chunkFile);
// 检查是否所有分片已上传
if (dir.listFiles().length == totalChunks) {
// 合并文件
String filename = file.getOriginalFilename();
File outputFile = new File(uploadPath, filename);
try (FileOutputStream fos = new FileOutputStream(outputFile, true)) {
for (int i = 1; i <= totalChunks; i++) {
File partFile = new File(tempDir, i + ".part");
Files.copy(partFile.toPath(), fos);
partFile.delete();
}
}
dir.delete();
// 保存文件信息到数据库
Video video = new Video();
video.setName(filename);
video.setUrl("/uploads/" + filename);
video.setSize(file.getSize());
videoService.save(video);
return R.ok().data("url", video.getUrl());
}
return R.ok();
}
我们设计了统一的API响应格式:
json复制{
"code": 200,
"message": "操作成功",
"data": {
// 业务数据
},
"timestamp": 1630000000000
}
对应的Java封装类:
java复制public class R<T> implements Serializable {
private int code;
private String message;
private T data;
private long timestamp;
public static <T> R<T> ok() {
return restResult(null, 200, "操作成功");
}
public static <T> R<T> ok(T data) {
return restResult(data, 200, "操作成功");
}
public static <T> R<T> error(String message) {
return restResult(null, 500, message);
}
public R<T> data(T data) {
this.data = data;
return this;
}
private static <T> R<T> restResult(T data, int code, String message) {
R<T> apiResult = new R<>();
apiResult.setCode(code);
apiResult.setData(data);
apiResult.setMessage(message);
apiResult.setTimestamp(System.currentTimeMillis());
return apiResult;
}
// getters and setters
}
前端axios封装示例:
javascript复制const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 10000
})
// 请求拦截器
service.interceptors.request.use(
config => {
const userStore = useUserStore()
if (userStore.token) {
config.headers['Authorization'] = 'Bearer ' + userStore.token
}
return config
},
error => {
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data
if (res.code !== 200) {
ElMessage.error(res.message || 'Error')
return Promise.reject(new Error(res.message || 'Error'))
}
return res.data
},
error => {
ElMessage.error(error.message)
return Promise.reject(error)
}
)
我们推荐以下两种部署方式:
传统部署:
nohup java -jar启动Docker容器化部署:
dockerfile复制# 后端Dockerfile
FROM openjdk:11-jre
COPY target/edu-system.jar /app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
# 前端Dockerfile
FROM nginx:alpine
COPY dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
对应的docker-compose.yml:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: edu_system
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
frontend:
build: ./frontend
ports:
- "80:80"
volumes:
mysql_data:
数据库优化:
缓存策略:
java复制@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用Jackson2JsonRedisSerializer序列化
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(om);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
// 使用缓存注解
@Cacheable(value = "course", key = "#id")
public Course getById(Long id) {
return courseMapper.selectById(id);
}
虽然我们在开发环境下可以通过前端代理解决跨域,但生产环境需要后端配置:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.exposedHeaders("Authorization")
.allowCredentials(true)
.maxAge(3600);
}
}
SpringBoot默认文件上传大小为1MB,需要调整配置:
yaml复制spring:
servlet:
multipart:
max-file-size: 50MB
max-request-size: 100MB
查询结果映射问题:
@ResultMap或@Results注解明确指定映射关系动态SQL编写技巧:
xml复制<select id="selectByCondition" resultType="Course">
SELECT * FROM course
<where>
<if test="title != null and title != ''">
AND title LIKE CONCAT('%', #{title}, '%')
</if>
<if test="teacherId != null">
AND teacher_id = #{teacherId}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
ORDER BY create_time DESC
</select>
响应式数据更新不生效:
push()、splice()等变更方法Vue.set()或展开运算符路由守卫使用:
javascript复制router.beforeEach((to, from, next) => {
const userStore = useUserStore()
if (to.meta.requiresAuth && !userStore.token) {
next('/login')
} else {
next()
}
})
基于当前系统,可以考虑以下几个扩展方向:
微服务化改造:
多端适配增强:
数据分析功能:
智能化功能:
在实际开发中,我发现SpringBoot+Vue的组合特别适合快速开发教育类管理系统。这种架构既保持了Java后端的稳定性,又获得了现代前端框架的优秀用户体验。特别是在处理复杂表单和权限控制时,这种架构展现出了很好的灵活性。