阅读前请先下载项目源码,边读边看源码以加深理解和实操,
源码地址已放于文章末尾!
效果预览:





07-课程展示-知识的殿堂
“芝麻开门”之后,我们终于进入了宝库内部!但问题来了,现在的宝库里空空荡荡,跟“毛坯房”一样,这可留不住用户。所以,咱们今天的主要任务,就是把这个“毛坯房”精装修一下,把各种“珍宝”——也就是我们的课程,给陈列展示出来。
我们将分三步走:
- 后端施工:编写
课程和课程分类的CRUD接口。 - 前端改造:在主页(
index.html)上,通过axios调用后端接口,把课程数据动态地渲染到页面上。 - 前后端联调:确保前端能正确拿到数据并展示出来。
第一步:后端施工 - 构建课程API
和之前用户模块的流程一样,我们也是按照 Entity -> Mapper -> Service -> Controller 的顺序来构建我们的课程模块。
1. Entity(实体)
我们在 entity 包下创建 Category.java, Course.java, Lesson.java。这些代码结构和User.java类似,主要是字段不同。(为节省篇幅,这里只展示Course.java,其他的类似)。
entity/Course.java:
package com.weke.learningsystem.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.sql.Timestamp;
@Data
@TableName("course")
public class Course {
private Long id;
private String title;
private String description;
private String coverImageUrl;
private Long categoryId;
private Timestamp createdAt;
}
2. Mapper(数据访问)
在 mapper 包下创建 CategoryMapper.java, CourseMapper.java。它们都继承自 BaseMapper。
mapper/CourseMapper.java:
package com.weke.learningsystem.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.weke.learningsystem.entity.Course;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CourseMapper extends BaseMapper<Course> {
}
3. Service(业务逻辑)
在 service 包下创建对应的 Service 接口和实现类。
service/CourseService.java:
package com.weke.learningsystem.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.weke.learningsystem.entity.Course;
public interface CourseService extends IService<Course> {
}
service/CourseServiceImpl.java:
package com.weke.learningsystem.service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.weke.learningsystem.entity.Course;
import com.weke.learningsystem.mapper.CourseMapper;
import org.springframework.stereotype.Service;
@Service
public class CourseServiceImpl extends ServiceImpl<CourseMapper, Course> implements CourseService {
}
4. Controller(API接口)
这是后端的“出口”,我们在这里把对课程的查询能力暴露给前端。
controller/CourseController.java:
package com.weke.learningsystem.controller;
import com.weke.learningsystem.entity.Course;
import com.weke.learningsystem.service.CourseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/courses")
public class CourseController {
@Autowired
private CourseService courseService;
// 获取所有课程列表
@GetMapping
public List<Course> getAllCourses() {
return courseService.list();
}
// 根据ID获取单个课程的详细信息
@GetMapping("/{id}")
public Course getCourseById(@PathVariable Long id) {
return courseService.getById(id);
}
// (后面我们还会加上给管理员使用的创建、更新、删除课程的接口)
// @PostMapping
// public boolean createCourse(@RequestBody Course course) { ... }
}
好了,后端的部分就暂时告一段落。我们已经拥有了获取所有课程和单个课程详情的API。现在,重启后端服务,确保它正常运行。
第二步:前端改造 - 动态渲染页面
现在,轮到前端“表演”了。打开我们的主页文件 index.html。
我们的目标是,当页面加载完成后:
- 从
localStorage中检查是否存在jwt_token,如果没有,说明用户没登录,直接“踹”回登录页。 - 如果已登录,就向后端的
/courses接口发送一个GET请求。 - 拿到课程列表数据后,用JavaScript动态地创建HTML元素,把课程信息一个一个地“塞”到页面的课程列表区域里。
首先,我们需要在 axios 的配置中,加入一个“请求拦截器”(Request Interceptor)。它的作用是,在我们每次发送请求前,都自动检查 localStorage 里有没有Token,如果有,就把它塞到请求的 Authorization 头里。这样,我们就不用每次请求都手动加一遍Token了。
修改 Scripts/api.js:
const apiClient = axios.create({
baseURL: 'http://localhost:8080',
timeout: 5000,
});
// 添加请求拦截器
apiClient.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
const token = localStorage.getItem('jwt_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
然后,创建 Scripts/index.js 文件,并在 index.html 中引入它。
Scripts/index.js:
document.addEventListener('DOMContentLoaded', function() {
// 1. 登录状态检查
const token = localStorage.getItem('jwt_token');
if (!token) {
// 如果没有token,就跳转回登录页面
alert('请先登录!');
window.location.href = 'Login.html';
return;
}
// 2. 获取课程列表
fetchCourses();
});
function fetchCourses() {
const courseListContainer = document.getElementById('course-list-container'); // 假设课程列表的容器div的id是这个
apiClient.get('/courses')
.then(response => {
const courses = response.data;
// 每次加载前,先清空容器,防止重复渲染
courseListContainer.innerHTML = '';
// 3. 遍历课程数据,动态创建HTML
courses.forEach(course => {
const courseCard = document.createElement('div');
courseCard.className = 'course-card'; // 假设我们用这个class来定义课程卡片的样式
courseCard.innerHTML = `
<img src="${course.coverImageUrl}" alt="${course.title}">
<h3>${course.title}</h3>
<p>${course.description}</p>
<a href="course-detail.html?id=${course.id}">查看详情</a>
`;
courseListContainer.appendChild(courseCard);
});
})
.catch(error => {
console.error('获取课程列表失败:', error);
if (error.response && error.response.status === 401) {
// Token失效或未授权
alert('登录已过期,请重新登录!');
localStorage.removeItem('jwt_token');
window.location.href = 'Login.html';
} else {
courseListContainer.innerHTML = '<p>加载课程失败,请稍后再试。</p>';
}
});
}
代码解读:
apiClient.interceptors.request.use(...):这就是axios强大的拦截器功能。它像一个“门卫”,在每个请求“出门”前,都给它盖个“已认证”的章(附上Token)。document.createElement('div')和appendChild():这是原生JavaScript操作DOM的核心方法,我们用它来动态地“凭空造物”,把从后端拿到的数据,变成用户看得见的HTML元素。<a href="course-detail.html?id=${course.id}">:我们还在“查看详情”的链接里,巧妙地把课程的id作为URL参数传了过去。这样,在详情页我们就能知道要加载哪个课程的数据了。
第三步:联调与验证
现在,是时候见证奇迹了。
- 确保你的数据库里有几条课程的测试数据。(你可以手动
INSERT几条) - 确保后端服务已经重启并正常运行。
- 确保你之前已经成功登录,并且
localStorage里存有jwt_token。 - 用浏览器打开
index.html。
如果一切顺利,你应该能看到页面上不再是静态的假数据,而是真真正正从你数据库里读取出来的课程列表!
至此,我们已经打通了登录后获取核心业务数据的流程,我们的“知识殿堂”终于不再是空架子了!
下一期预告:
课程列表虽然出来了,但用户最关心的还是点进去看视频。下一章,我们将完成课程详情页和视频播放功能,让用户能够真正地“学起来”。这将涉及到如何处理URL参数,以及如何在前端集成一个好用的视频播放器。最精彩的部分,即将上演!
源码下载地址:
https://download.youkuaiyun.com/download/THMAIL/91753658

1704

被折叠的 条评论
为什么被折叠?



