开发流程
需求分析
配置课时(课程内容管理)模块,主要是对课程内容进行管理。
数据库表分析
course - 课程表
course_section - 课程章节表
course_lesson - 课时信息表
一个课程表对多个课程章节表,一个课程章节表对多个课时表。
实体类设计
Course 类与 Course_Section 类是一对多关系;Course_Section 类与 Course_Lesson 类是一对多关系。
在 Course 类中定义一个 List 集合,并指定 List 的泛型是 Course_Section 类型,表示 一个课程中可以包含多个章节。
在 Course_Section 类中,定义一个 Course 类型的属性,用来保存章节所对应的具体的课程信息。
在 Course_Section 类中定义一个 List 集合,并指定 List 的泛型是 Course_Lesson 类型,这样就可以表示一个章节中包含多个课时。
代码语言:javascript复制// Course 类:
...
List<Course_Section> sectionList = new ArrayList<>();
...
// Course_Section 类:
...
List<Course_Lesson> lessonList = new ArrayList<>();
private Course course;
...
// Course_Lesson 类:
...
private Course_Section course_section;
...
Dao 接口及实现类编写
代码语言:javascript复制/**
* 课程内容管理 DAO 层接口
* */
public interface CourseContentDao {
}
/**
* 课程内容管理 DAO 层实现类
* */
public class CourseContentDaoImpl implements CourseContentDao {
}
Service 接口及实现类编写
代码语言:javascript复制/**
* 课程内容管理 Service 层接口
* */
public interface CourseContentService {
}
/**
* 课程内容管理 Service 层实现类
* */
public class CourseContentServiceImpl implements CourseContentService {
}
`CourseContentServlet` 编写
CourseContentServlet
继承 BaseServlet
@WebServlet("/courseContent")
public class CourseContentServlet extends BaseServlet {
}
功能一:展示课程内容
需求分析
分析:要展示的内容是对应课程下的章节与课时信息
代码语言:javascript复制-- 查询 ID 为 1 的课程的章节与课时信息
SELECT
cs.`id`,
cs.`section_name`,
cl.`theme` '课时名称'
FROM course_section cs INNER JOIN course_lesson cl
ON cs.`id` = cl.`section_id`
WHERE cs.`course_id` = ;
-- 在程序中尽量避免使用连接查询。可以将上面的 SQL 进行拆分,每一条 SQL 对应一个功能
-- 根据课程 ID 查询章节相关的内容
SELECT
id,
course_id,
section_name,
description,
order_num,
`status`
FROM course_section WHERE course_id = ;
-- 根据章节 ID 查询课时相关的内容
SELECT
id,
course_id,
section_id,
theme,
is_free,
order_num,
`status`
FROM course_lesson WHERE section_id = ;
DAO 层编写
编写两个方法
CourseContentDaoImpl
/**
* 根据课程 ID 查询课程相关信息
*
* @param courseId
*/
@Override
public List<Course_Section> findSectionAndLessonByCourseId(int courseId) {
try {
// 创建 QueryRunner
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
// 编写 SQL
String sql = "SELECT n"
"id,n"
"course_id,n"
"section_name,n"
"description,n"
"order_num,n"
"STATUSn"
"FROM course_section WHERE course_id = ?";
// 执行查询
List<Course_Section> sectionList = qr.query(sql,
new BeanListHandler<Course_Section>(Course_Section.class), courseId);
// 根据章节 ID 查询课时信息
for (Course_Section section : sectionList) {
// 调用获取章节对应的课时方法,将课时数据封装到章节对象中
section.setLessonList(findLessonBySectionId(section.getId()));
}
// 返回结果
return sectionList;
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
/**
* 根据章节 ID 查询章节相关的课时信息
*
* @param sectionId
*/
@Override
public List<Course_Lesson> findLessonBySectionId(int sectionId) {
try {
// 创建 QueryRunner
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
// 编写 SQL
String sql = "SELECT n"
"id,n"
"course_id,n"
"section_id,n"
"theme,n"
"duration,n"
"is_free,n"
"order_num,n"
"STATUSn"
"FROM course_lesson WHERE section_id = ?";
// 执行查询
return qr.query(sql, new BeanListHandler<Course_Lesson>(Course_Lesson.class), sectionId);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
DAO 层测试
代码语言:javascript复制public class TestCourseContentDao {
CourseContentDao contentDao = new CourseContentDaoImpl();
/**
* 测试 查询对应课程下的章节与课时
*/
@Test
public void testFindSectionAndLessonByCourseId(){
List<Course_Section> list = contentDao.findSectionAndLessonByCourseId();
// 遍历输出章节信息
for (Course_Section courseSection : list) {
System.out.println(courseSection.getId() " = " courseSection.getSection_name());
List<Course_Lesson> lessonList = courseSection.getLessonList();
// 遍历输出课时信息
for (Course_Lesson courseLesson : lessonList) {
System.out.println(courseLesson.getId() " = " courseLesson.getTheme() " = "
courseLesson.getSection_id());
}
System.out.println();
}
}
}
Service 层编写
CourseContentServiceImpl
CourseContentDao contentDao = new CourseContentDaoImpl();
/**
* 根据课程 id 查询课程内容
*/
@Override
public List<Course_Section> findSectionAndLessonByCourseId(int courseId) {
return contentDao.findSectionAndLessonByCourseId(courseId);
}
Servlet 编写
CourseContentServlet
中添加 findSectionAndLessonByCourseId
方法
/**
* 展示对应课程的章节与课时信息
*/
public void findSectionAndLessonByCourseId(HttpServletRequest request, HttpServletResponse response) {
try {
// 获取参数
String course_id = request.getParameter("course_id");
// 业务处理
CourseContentService contentService = new CourseContentServiceImpl();
List<Course_Section> sectionList = contentService.findSectionAndLessonByCourseId(Integer.parseInt(course_id));
// 返回结果
response.getWriter().println(JSON.toJSONString(sectionList));
} catch (IOException e) {
e.printStackTrace();
}
}
接口测试:使用 Postman 根据接口文档进行测试
功能二:新建章节信息
需求分析
首先根据课程 ID 查询课程名称进行回显,然后获取输入的新的章节信息,执行保存章节数据。
Dao 层编写
CourseContentDaoImpl
...
/**
* 添加章节时进行数据回显
*/
@Override
public Course findCourseByCourseId(int courseId) {
try {
// 创建 QueryRunner
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
// 编写 SQL
String sql = "SELECT id, course_name FROM course WHERE id = ?";
// 执行查询并返回结果
return qr.query(sql, new BeanHandler<Course>(Course.class), courseId);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
/**
* 保存章节信息
*/
@Override
public int saveSection(Course_Section section) {
try {
// 创建 QueryRunner
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
// 编写 SQL
String sql = "INSERT INTO course_section(n"
"course_id, n"
"section_name, n"
"description, n"
"order_num, n"
"`status`, n"
"create_time, n"
"update_time)n"
"VALUES(?, ?, ?, ?, ?, ?, ?);";
// 准备参数
Object[] params = {section.getCourse_id(), section.getSection_name(),
section.getDescription(), section.getOrder_num(), section.getStatus(),
section.getCreate_time(), section.getUpdate_time()};
// 返回受影响的行数
return qr.update(sql, params);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return ;
}
...
Dao 层测试 TestCourseContentDao
/**
* 测试 根据课程 id 回显课程名称
*/
@Test
public void testFindCourseByCourseId() {
Course course = contentDao.findCourseByCourseId();
System.out.println(course.getId() " " course.getCourse_name());
}
/**
* 测试 保存章节信息
*/
@Test
public void testSaveSection() {
Course_Section section = new Course_Section();
section.setCourse_id();
section.setSection_name("章节名字");
section.setDescription("章节描述");
section.setOrder_num();
// 更新时间
String dateFormat = DateUtils.getDateFormat();
section.setCreate_time(dateFormat);
section.setUpdate_time(dateFormat);
// 状态:0 隐藏,1 待更新,2 已发布
section.setStatus();
System.out.println(contentDao.saveSection(section));
}
Service 层编写
CourseContentServiceImpl
...
/**
* 添加章节时进行数据回显
*/
@Override
public Course findCourseById(int courseId) {
return contentDao.findCourseByCourseId(courseId);
}
/**
* 保存章节信息
*/
@Override
public String saveSection(Course_Section section) {
// 补全章节信息
// 状态:0 隐藏,1 待更新,2 已发布
section.setStatus();
String date = DateUtils.getDateFormat();
section.setCreate_time(date);
section.setUpdate_time(date);
// 调用 Dao 进行插入
if (contentDao.saveSection(section) > ) {
// 保存成功
return StatusCode.SUCCESS.toString();
} else {
// 保存失败
return StatusCode.FAIL.toString();
}
}
...
Servlet 编写
课程信息回显接口
代码语言:javascript复制/**
* 回显章节对应的课程信息
*/
public void findCourseById(HttpServletRequest request , HttpServletResponse response){
try {
// 获取参数
String courseId = request.getParameter("course_id");
// 业务处理
CourseContentService contentService = new CourseContentServiceImpl();
Course course = contentService.findCourseById(Integer.parseInt(courseId));
// 返回数据,将对象转换为 JSON,只转换需要的字段
SimplePropertyPreFilter filter = new SimplePropertyPreFilter(Course.class,
"id", "course_name");
response.getWriter().println(JSON.toJSONString(course,filter));
} catch (IOException e) {
e.printStackTrace();
}
}
保存章节信息接口
POST 请求方法常用的三种数据提交格式:
Content-Type : application/x-www-form-urlencoded
请求体中的数据会以普通表单形式(键值对)发送到后端。Content-Type : application/json ; charset=utf-8
请求体中的数据会以 JSON 字符串的形式发送到后端。Content-Type : multipart/form-data
多部件上传既可以上传键值对,也可以上传文件。
第二种 JSON 格式与第三种多部件上传都无法使用 getParameter()
方法获取数据。
根据接口文档描述:前台传输的是 JSON 格式的数据,使用 getParameter()
方法无法获取参数。
如果请求参数是 JSON 格式的数,可以通过 request.getReader()
这个方法获取一个流对象来进行
读取。
首先,在 BaseServlet
中创建一个方法用来获取 JSON 格式的数据:
/**
* 如果 POST 请求格式为 application/json;charset=utf-8
* 则在这个方法中使用流的方式获取到 POST 请求的数据
*/
public String getPostJSON(HttpServletRequest request){
try {
// 从 request 中获取字符缓冲输入流对象
BufferedReader reader = request.getReader();
// 创建 StringBuffer 用来保存读取出的数据
StringBuffer sb = new StringBuffer();
// 循环读取
String line = null;
while((line = reader.readLine()) != null){
// 追加到 StringBuffer 中
sb.append(line);
}
// 将读取到的内容转换为字符串并返回
return sb.toString();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
然后,修改 BaseServlet
中的 doPost
方法:
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 要访问的方法名
String methodName = null;
// 获取 POST 请求的 Content-Type 类型
String contentType = req.getHeader("Content-Type");
// 判断传递的数据是不是 JSON 格式
if("application/json;charset=utf-8".equals(contentType)){
// 是 JSON 格式 调用 getPostJSON
String postJSON = getPostJSON(req);
// 将 JSON 格式的字符串转化为 map
Map<String,Object> map = JSON.parseObject(postJSON, Map.class);
// 从 map 集合中获取 methodName
methodName = (String) map.get("methodName");
// 将获取到的数据保存到 request 域对象中
req.setAttribute("map", map);
} else {
methodName = req.getParameter("methodName");
}
// 判断并执行对应的方法
if (methodName != null) {
// 使用反射方式提升代码的可维护性
try {
// 获取字节码对象
Class aClass = this.getClass();
// 根据传入的方法名获取对应的方法对象
Method method = aClass.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
method.invoke(this, req, resp);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
System.out.println("请求的功能不存在");
}
}
}
最后,编写接口代码,CourseContentServlet
:
/**
* 保存 & 修改 章节信息
*/
public void saveOrUpdateSection(HttpServletRequest request, HttpServletResponse response){
try {
// 从 request 域对象中获取获取参数
Map<String,Object> map = (Map) request.getAttribute("map");
// 创建 Course_Section
Course_Section section = new Course_Section();
// 使用 BeanUtils 工具类,将 map 中的数据封装到 section
BeanUtils.populate(section, map);
// 业务处理并响应结果
CourseContentService contentService = new CourseContentServiceImpl();
response.getWriter().print(contentService.saveSection(section));
} catch (IllegalAccessException | InvocationTargetException | IOException e) {
e.printStackTrace();
}
}
使用 Postman 测试接口:
- 选择 POST 请求方式,设置
Content-Type = application/json
- 选择 raw 发送 JSON 格式数据
功能三:章节信息修改
需求分析
选择章节,点击编辑,传递章节 id;根据章节 id 修改章节信息。
根据接口文档,没有要求编写回显接口,不需要后台根据章节 id 查询对应章节信息进行回显,所以回显操作由前端代码完成。
DAO 层编写
CourseContentDaoImpl
/**
* 修改章节信息
*/
@Override
public int updateSection(Course_Section section) {
try {
// 创建 QueryRunner
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
// 编写 SQL
String sql = "UPDATE course_section SET n"
"section_name = ?,n"
"description = ?,n"
"order_num = ?,n"
"update_time = ?n"
"WHERE id = ?";
// 准备参数
Object[] param = {section.getSection_name(), section.getDescription(),
section.getOrder_num(), section.getUpdate_time(), section.getId()};
// 执行修改操作
return qr.update(sql, param);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return ;
}
Dao 层测试 TestCourseContentDao
/**
* 测试 修改章节信息
*/
@Test
public void testUpdateSection() {
Course_Section section = new Course_Section();
section.setId();
section.setSection_name("微服务架构-Renda");
section.setDescription("微服务架构详解-Renda");
section.setOrder_num();
section.setUpdate_time(DateUtils.getDateFormat());
System.out.println(contentDao.updateSection(section));
}
Service 层编写
CourseContentServiceImpl
/**
* 修改章节信息
*
* @param section
*/
@Override
public String updateSection(Course_Section section) {
// 补全章节信息
String date = DateUtils.getDateFormat();
section.setUpdate_time(date);
// 调用 Dao 进行插入,根据修改是否成功,封装对应信息
if (contentDao.updateSection(section) > ) {
return StatusCode.SUCCESS.toString();
} else {
return StatusCode.FAIL.toString();
}
}
Servlet 编写
保存章节信息和修改章节信息,访问的是同一个接口,所以在 CourseContentServlet
的 saveOrUpdateSection
方法中进行一下判断:携带 id 就是修改章节操作;未携带 id 就是新增章节操作。
...
if (section.getId() != ) {
// 修改操作
response.getWriter().print(contentService.updateSection(section));
} else {
// 保存操作
response.getWriter().print(contentService.saveSection(section));
}
...
接口测试:根据接口文档,使用 Postman 进行测试。
功能四:章节状态管理
需求分析
根据选择的状态信息,发送对应的状态编号,进行修改 status 状态;0 隐藏,1 待更新,2 已发布。
DAO 层编写
CourseContentDaoImpl
/**
* 修改章节的状态
*/
@Override
public int updateSectionStatus(int id, int status) {
try {
// 创建 QueryRunner
QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
// 编写 SQL
String sql = "UPDATE course_section SET `status` = ?, update_time = ? WHERE id = ?";
// 准备参数
Object[] param = {status, DateUtils.getDateFormat(), id};
// 执行修改操作
return qr.update(sql, param);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return ;
}
Dao 层测试 TestCourseContentDao
/**
* 测试 修改章节状态
*/
@Test
public void testUpdateSectionStatus() {
System.out.println(contentDao.updateSectionStatus(, ));
}
Service 层编写
CourseContentServiceImpl
/**
* 修改章节的状态
*/
@Override
public String updateSectionStatus(int id, int status) {
// 调用 Dao 修改状态,根据修改是否成功,封装对应信息
if (contentDao.updateSectionStatus(id, status) > ){
return StatusCode.SUCCESS.toString();
} else {
return StatusCode.FAIL.toString();
}
}
Servlet 编写
CourseContentServlet
/**
* 修改章节状态
*/
public void updateSectionStatus(HttpServletRequest request , HttpServletResponse response) {
try {
// 获取参数
int id = Integer.parseInt(request.getParameter("id"));
int status = Integer.parseInt(request.getParameter("status"));
// 业务处理并返回结果数据
CourseContentService contentService = new CourseContentServiceImpl();
response.getWriter().println(contentService.updateSectionStatus(id, status));
} catch (IOException e) {
e.printStackTrace();
}
}
接口测试:根据接口文档,使用 Postman 进行测试。