巩固web阶段知识,完成后台管理系统中的课程管理模块,该模块包含了添加课程、配置课程相关信息、管理课程章节等功能。

项目地址:课程管理模块

产品的原型图

课程管理

  • 实现以下功能:

    • 展示课程列表
    • 根据课程名和状态进行查询
    • 新建课程
    • 课程上架与下架

营销信息

  • 营销信息,其实就是设置课程的详细信息

    • 回显课程信息
    • 修改课程信息,包含了图片上传

配置课时

  • 对课程下所属的章节与课时进行配置(一个课程对应多个章节,一个章节有多个课时)

    • 以树形结构的下拉框形式,展示课程对应的章节与课时信息
    • 添加&修改章节功能
    • 修改章节状态功能
    • 添加&修改课时功能

课程管理模块表设计

1. 项目架构

1.1 前后端分离架构

前后端分离的核心思想就是前端HTML页面通过AJAX调用后端的API接口,并通过JSON数据进行交互。

1.1.1 前后端耦合的缺陷 (以JSP为例)

  1. UI出好设计图之后,前端开发工程师只负责将设计图切成HTML,需要由Java开发工程师来将HTML套成JSP页面,修改问题的时候需要双方协同开发,效率低下。
  2. JSP页面必须要在支持Java的WEB服务器上运行(如Tomcat、Jetty等),无法使用Nginx等(官方宣称单实例HTTP并发高达5W),性能提升不上来。
  3. 第一次请求JSP,必须要在WEB服务器中编译成Servlet,第一次运行会较慢。 之后的每次请求JSP都是访问Servlet再用输出流输出的HTML页面,效率没有直接使用HTML高。

1.1.2 前后端分离的优势

  1. 前后端分离的模式下,如果发现Bug,可以快速定位是谁的问题,不会出现互相踢皮球的现象
  2. 前后端分离可以减少后端服务器的并发/负载压力。除了接口以外的其他所有HTTP请求全部转移到前端Nginx上,接口的请求则转发调用Tomcat。
  3. 前后端分离的模式下,即使后端服务器暂时超时或宕机了,前端页面也会正常访问,只不过数据刷不出来而已。
  4. 前后端分离会更加合理的分配团队的工作量,减轻后端团队的工作量,提高了性能和可扩展性。

1.2 接口文档

1.2.1 什么是接口文档?

​ 在我们的项目中使用的是前后端分离开发方式,需要由前后端工程师共同定义接口,编写接口文档,之后大家都根据这个接口文档进行开发,到项目结束前都要一直进行接口文档的维护。

1.2.2 为什么要写接口文档?

  1. 项目开发过程中前后端工程师有一个统一的文件进行沟通交流,并行开发
  2. 项目维护中或者项目人员更迭,方便后期人员查看、维护

1.2.3 接口规范是什么?

一个接口的描述至少包括下面几项:

  • 名称: findCourseList

  • 描述: 根据条件查询课程信息

  • URL: http://localhost:8080/lagou_edu_home/course/

  • 请求方式: GET

  • 请求参数

    methodName:"findCourseList";
    
  • 响应结果

    {"status": "0","msg": "success"
    }
    

1.3 技术选型

1.3.1 前端技术选型

前端技术 说明
Vue.js 是一套用于构建用户界面的渐进式JavaScript框架
Element UI库 element-ui 是饿了么前端出品的基于 Vue.js的后台组件库,
方便程序员进行页面快速布局和构建
node.js 简单的说 Node.js 就是运行在服务端的 JavaScript 运行环境
axios 对ajax的封装, 简单来说就是ajax技术实现了局部数据的刷新,axios实现了对ajax的封装

1.3.2 后端技术选型

后端技术 说明
Web层 a) Servlet:前端控制器
b) Filter:过滤器
c) BeanUtils:数据封装
Service层 a) 业务处理
dao层 a) Mysql:数据库
b) Druid:数据库连接池
c) DBUtils: 操作数据库

2. 环境搭建

2.1 创建项目

使用Maven快速构建工程,New Module 项目名为: lagou_edu_home

创建Maven项目

2.2 项目目录

2.3 导入pom.xml

见项目地址pom.xml文件

2.4 导入工具类及配置文件

见项目地址utils文件夹、resources文件夹内容

2.5 导入实体类

导入表对应的实体类

Course.java

package com.lagou.pojo;import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;/*** 课程类* */
@Data
public class Course implements Serializable {//使用 JSONField 设置ordinal的值,来对转换成的JSON数据进行排序//课程ID@JSONField(ordinal = 1)private int id;//课程名称@JSONField(ordinal = 2)private String course_name;//课程介绍@JSONField(ordinal = 3)private String brief;//讲师名称@JSONField(ordinal = 4)private String teacher_name;//讲师介绍@JSONField(ordinal = 5)private String teacher_info;//课程原价@JSONField(ordinal = 6)private double price;//原价标签@JSONField(ordinal = 7)private String price_tag;//课程优惠价@JSONField(ordinal = 8)private double discounts;//课程概述@JSONField(ordinal = 9)private String preview_first_field;//课程概述第二个字段@JSONField(ordinal = 10)private String preview_second_field;//分享图片url@JSONField(ordinal = 11)private String course_img_url;//分享标题@JSONField(ordinal = 12)private String share_title;//分享描述@JSONField(ordinal = 13)private String share_description;//课程描述@JSONField(ordinal = 14)private String course_description;//排序@JSONField(ordinal = 15)private int sort_num;//课程状态,0-草稿,1-上架@JSONField(ordinal = 16)private int status;//创建时间@JSONField(ordinal = 17)private String create_time;//修改时间@JSONField(ordinal = 18)private String update_time;//是否删除@JSONField(ordinal = 19)private int isDel;@JSONField(ordinal = 20)private String share_image_title; //分享图title//使用JSONField(serialize = false)排除不需要转换的字段@JSONField(serialize = false)private int total_course_time; //课时数@JSONField(serialize = false)private int sales; //显示销量@JSONField(serialize = false)private int actual_sales; //真实销量@JSONField(serialize = false)private int is_new; //是否新品@JSONField(serialize = false)private String is_new_des; //广告语@JSONField(serialize = false)private int last_operator_id; //最后操作者@JSONField(serialize = false)private int total_duration; //总时长@JSONField(serialize = false)private long course_type; //课程类型@JSONField(serialize = false)private String last_notice_time;  //最后课程最近通知时间@JSONField(serialize = false)private long is_gray; //是否是灰度课程@JSONField(serialize = false)private long grade; //级别
}

2.5.1 Lombok

1) Lombok介绍

在项目中使用Lombok可以减少很多重复代码的书写,比如getter/setter/toString等方法的编写。

2) IDEA中安装 lombok插件

打开IDEA的Setting –> 选择Plugins选项 –> 搜索lombok –> 点击安装 –> 安装完成重启IDEA

3) 添加依赖

在项目中添加Lombok依赖jar,在pom文件中添加如下部分

<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.0</version><scope>provided</scope>
</dependency>

4) Lombok常用注解

  • @Getter/@Setter: 作用类上,生成所有成员变量的getter/setter方法

  • @ToString : 作用于类,覆盖默认的toString()方法 ,可以通过of属性限定显示某些字段,通过exclude属性排除某些字段

  • @AllArgsConstructor:生成全参构造器

  • @NoArgsConstructor:生成无参构造器

  • @Data: 该注解使用在上,该注解会提供 gettersetterequalshashCodetoString 方法。

2.5.2 FastJson的@JSONField注解

通过 @JSONField 我们可以自定义字段的名称进行输出,并控制字段的排序,还可以进行序列化标记。

  • 指定 name属性, 字段的名称
  • 使用 ordinal属性, 指定字段的顺序
  • 使用 serialize属性, 指定字段不序列化

Fastjson 简明教程

3. 开发流程

需求分析、数据库表分析、实体类设计、Dao接口及实现类编写、Service接口及实现类编写、Servlet编写。

其中Servlet层有技巧:BaseServlet简化Servlet操作

4. 功能实现(举例)

4.1 功能一: 查询课程列表信息

4.1.1 需求分析

页面分析,需要展示哪些数据

4.1.2 编写代码

Dao层编写

修改CourseDao,添加 findCourseList 方法

  • idea中try/catch快捷键

    选中要包裹代码 + Ctrl + Alt +t

接口 CourseDao//查询课程列表信息public List<Course> findCourseList();实现类 CourseDaoImpl@Overridepublic List<Course> findCourseList() {try {//1.创建QueryRunnerQueryRunner qr = new QueryRunner(DruidUtils.getDataSource());//2.编写SQLString sql = "SELECT id,course_name,price,sort_num,STATUS FROM course where id_del =  ?";//3.执行查询List<Course> courseList = qr.query(sql, new BeanListHandler<Course>(Course.class), 0);return courseList;} catch (SQLException e) {e.printStackTrace();return null;}}

逻辑删除

  • 逻辑删除的本质是修改操作,所谓的逻辑删除其实并不是真正的删除,而是在表中将对应的是否删除标识做修改操作。
  • 比如:0是未删除,1是删除。在逻辑上数据是被删除的,但数据本身依然存在库中。

物理删除

  • 物理删除就是真正的从数据库中做删除操作了。
Service层编写

修改CourseService 添加 findCourseList 方法

接口 CourseService public List<Course> findCourseList();实现类 CourseServiceImpl//创建 CourseDaoCourseDao courseDao = new CourseDaoImpl();@Overridepublic List<Course> findCourseList() {//调用Dao 进行查询return courseDao.findCourseList();}
Servlet编写

接口开发规范

​ 我们在做的是一个前后端分离项目、需要通过接口文档对接的项目。所以开发过程中要仔细查看前端所需的api接口和参数字段。

为了严格按照接口进行开发,提高效率,对请求及响应格式进行规范化。

开发规范
1、get 请求时,采用key/value格式请求,Servlet中可以使用 getParameter() 获取。
2、post请求时有三种数据格式
第一种: Json数据,json类型的数据 Servlet中使用 fastjson进行解析
第二种: 提交form表单数据
第三种: 文件等多部件类型(multipart/form-data)
3、响应结果统一格式为json

为什么使用JSON?

​ 数据格式比较简单,易于读写,JSON格式能够直接为服务器端代码使用,大大简化了服务器端和客户端的代码开发量,但是完成的任务不变,且易于维护。

JSON 语法

本项目使用的是 JSON解析工具为阿里巴巴的fastjson,maven工程导入下面的依赖即可。

Fastjson 简明教程

<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.1.37</version>
</dependency><dependency><groupId>com.colobu</groupId><artifactId>fastjson-jaxrs-json-provider</artifactId><version>0.3.1</version>
</dependency>

接口文档

​ 前端的开发基于服务端编写的接口,如果前端人员等待服务端人员将接口开发完毕再去开发前端内容这样做效率是非常低下的,所以当接口定义完成,可以使用工具生成接口文档,前端人员查看接口文档即可进行前端开发,这样前端和服务人员并行开发,大大提高了生产效率。

接口 查询课程列表信息

  • 名称: findCourseList
  • 描述: 查询课程列表信息
  • URL: http://localhost:8080/lagou_edu_home/course
  • 请求方式: GET
  • 请求参数
字段 说明 类型 是否必须 备注
methodName 要访问的功能名 String 该字段必须填写,用来确定要访问是
哪一个的方法
  • 请求参数示例:
methodName: "findCourseList"
  • 响应结果
字段 说明 类型 是否必须 备注
id 课程id int
course_name 课程名称 String
price 课程价格 double 课程的原价格
sort_num 课程排序 int 数字越大,越排在后面
status 课程状态 int 0-草稿,1-上架
  • 响应结果示例
[{"id": 1,"course_name": "32个Java面试必考点","price": 8000,"sort_num": 1,"status": 1
}]

编写CourseServlet

在CourseServlet中添加 findCourseList方法

@WebServlet("/course")
public class CourseServlet extends BaseServlet {//查询课程信息列表public void findCourseList(HttpServletRequest request, HttpServletResponse response){try {//1.接收参数//2.业务处理CourseService cs = new CourseServiceImpl();List<Course> courseList = cs.findCourseList();//3.响应结果//SimplePropertyPreFilter 指定要转换的JSON字段SimplePropertyPreFilter filter = new SimplePropertyPreFilter(Course.class,"id","course_name","price","sort_num","status");String result = JSON.toJSONString(courseList,filter);response.getWriter().print(result);} catch (IOException e) {e.printStackTrace();}}
}
  • SimplePropertyPreFilter:结合FastJson指定要转换的JSON字段

    • 定制序列化:SimplePropertyPreFilter filter = new SimplePropertyPreFilter(类名.class,"字段1","字段2");
    • JSON.toJSONString(object,filter);(object是Java对象)

4.1.3 Postman测试接口

  1. 发送请求到指定的
http://localhost:8080/lagou_edu_home/course?methodName=findCourseList

4.2 功能实现中用到的知识及技巧

4.2.1 功能:根据课程名称和课程状态进行查询

4.2.1.1 WHERE 1=1

多条件查询要注意多个参数情况下SQL的编写

where默认为1=1,这样用户即使不选择任何条件,sql查询也不会出错;如果还选择了其他的条件,就不断在where条件后追加 and语句就行了。

如果不用1=1的话,每加一个条件,都要判断前面有没有where 条件,如果没有就写where,有就写and语句,因此用1=1简化了应用程序的复杂度。

4.2.1.2 StringBuffer拼接SQL字符串
4.2.1.3 like查询(模糊查询)需要拼接 %

Dao层编写

实现类/*** 根据课程名称 课程状态 查询课程信息* *///根据条件查询课程信息@Overridepublic List<Course> findByCourseNameAndStatus(String courseName, String status) {try {//1.创建QueryRunnerQueryRunner qr = new QueryRunner(DruidUtils.getDataSource());//2.编写SQL 当前的查询为多条件不定项查询//2.1 创建StringBuffer 对象,将SQL字符串 添加进缓冲区StringBuffer sb = new StringBuffer("SELECT id,course_name,price,sort_num,STATUS FROM course WHERE 1=1 and is_del = ? ");//2.2 创建list集合 保存参数List<Object> list = new ArrayList<>();list.add(0);//2.3 判断传入的参数是否为空if(courseName != null && courseName != ""){sb.append(" AND course_name LIKE ?");//like查询 需要拼接 %courseName = "%"+courseName+"%";//将条件放进list集合list.add(courseName);}if(status != null && status != ""){sb.append("AND STATUS = ?");//将status 转换为 intint i = Integer.parseInt(status);list.add(i);}//执行查询List<Course> courseList = qr.query(sb.toString(), new BeanListHandler<Course>(Course.class), list.toArray());//返回结果return courseList;} catch (SQLException e) {e.printStackTrace();return null;}
}

4.2.2 功能:新建课程营销信息

文字部分:

Dao层编写

接口//保存课程营销信息public int saveCourseSalesInfo(Course course);实现类//保存课程营销信息@Overridepublic int saveCourseSalesInfo(Course course) {try {//1.创建QueryRunnerQueryRunner qr = new QueryRunner(DruidUtils.getDataSource());//2.编写SQLString sql = "INSERT INTO course(\n" +"course_name,\n" +"brief,\n" +"teacher_name,\n" +"teacher_info,\n" +"preview_first_field,\n" +"preview_second_field,\n" +"discounts,\n" +"price,\n" +"price_tag,\n" +"share_image_title,\n" +"share_title,\n" +"share_description,\n" +"course_description,\n" +"course_img_url,\n" +"STATUS,\n" +"create_time,\n" +"update_time\n" +")VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);";//3.准备参数Object[] param = {course.getCourse_name(),course.getBrief(),course.getTeacher_name(),course.getTeacher_info(),course.getPreview_first_field(),course.getPreview_second_field(),course.getDiscounts(),course.getPrice(),course.getPrice_tag(),course.getShare_image_title(),course.getShare_title(),course.getShare_description(),course.getCourse_description(),course.getCourse_img_url(),course.getStatus(),course.getCreate_time(),course.getUpdate_time()};//4.执行插入操作int row = qr.update(sql, param);return row;} catch (SQLException e) {e.printStackTrace();return 0;}}

Service层编写

4.2.2.1 枚举类
  1. 编写枚举类,设置响应状态码(放在base文件夹下)

    Java 枚举(enum)

    枚举类的使用方法

    public enum StatusCode {SUCCESS(0,"success"),FAIL(1,"fail");//定义属性private int code;private String message;//定义构造StatusCode(int code, String message) {this.code = code;this.message = message;}//get/setpublic int getCode() {return code;}public void setCode(int code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}//重写toString,将枚举对象转化为JSON@Overridepublic String toString() {JSONObject object = new JSONObject();object.put("status",code);object.put("msg",message);return object.toString();}
    }
    
  2. 编写Service

    接口public String saveCourseSalesInfo(Course course);实现类@Overridepublic String saveCourseSalesInfo(Course course) {//1.补全课程信息String dateFormart = DateUtils.getDateFormart();course.setCreate_time(dateFormart);course.setUpdate_time(dateFormart);course.setStatus(0);//2.调用Dao进行插入int i = courseDao.saveCourseSalesInfo(course);if(i > 0){//保存成功String result = StatusCode.SUCCESS.toString();return result; // {"msg":"success","status":0}}else{//保存失败String result = StatusCode.FAIL.toString();return result; // {"msg":"fail","status":1}}}
    

文件上传部分:

4.2.2.2 图片上传分析

在添加课程营销信息的表单中,有一个图片上传项

文件上传的实质:文件的拷贝

  • 文件上传:从本地将文件拷贝到服务器磁盘上

    • 客户端: 需要编写文件上传表单
    • 服务端: 需要编写代码,接收上传的文件

客户端编码

文件上传三要素:

  1. 表单提交方式: post (get方式提交有大小限制,post没有)
  2. 表单的enctype属性:必须设置为 multipart/form-data
    • enctype就是encodetype就是编码类型的意思
    • multipart/form-data是多部件文件上传 , 指表单数据有多部分构成,既有文本数据,又有文件等二进制数据的意思
  3. 表单必须有文件上传项: file,必须要有name属性和值

注意: 默认情况下,表单的enctype的值是application/x-www-form-urlencoded,不能用于文件上传,只有使用了multipart/form-data,才能完整的传递文件数据

服务端编码

4.2.2.3 FileUpload工具类
  1. 导入依赖

    FileUpload包可以很容易地将文件上传到你的Web应用程序

    IOUtils封装了Java中io的常见操作,使用十分方便 ,需要下载 commons-io-1.4.jar 包

    <dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>1.4</version>
    </dependency><dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.2.1</version>
    </dependency>
    
  2. FileUpload 核心类介绍

    类名 介绍
    DiskFileItemFactory 磁盘文件项工厂, 读取文件时相关的配置,比如: 缓存的大小 , 临时目录的位置
    ServletFileUplaod 文件上传的一个核心类
    FileItem 代表每一个表单项
  3. 文件上传的API的详解

    • ServletFileUpload
    方法 说明
    isMultipartContent(request); 判断是否是一个文件上传的表单
    parseRequest(request); 解析request获得表单项的集合
    setHeaderEncoding(“UTF-8”); 设置上传的文件名的编码方式
    • FileItem
    方法 说明
    isFormField() 判断是否是普通表单项
    getFieldName() 获得表单的name属性值
    item.getString() 获得表单的value值
    getName() 获得上传文件的名称
    getInputStream() 获得上传文件
    delete() 删除临时文件
  4. 文件上传后台代码编写

    FileUpload使用步骤:

    ​ 1、创建磁盘文件项工厂

    ​ 2、创建文件上传的核心类

    ​ 3、解析request获得文件项集合

    ​ 4、遍历文件项集合

    ​ 5、判断普通表单项/文件上传项

@WebServlet("/upload")
public class FileUploadServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {try {//1.创建磁盘文件项工厂DiskFileItemFactory factory = new DiskFileItemFactory();//2.创建文件上传核心类ServletFileUpload upload = new ServletFileUpload(factory);//2.1 设置上传文件名的编码upload.setHeaderEncoding("utf-8");//2.2 判断表单是否是文件上传表单boolean multipartContent = upload.isMultipartContent(req);//2.3 是文件上传表单if(multipartContent){//3. 解析request ,获取文件项集合List<FileItem> list = upload.parseRequest(req);if(list != null){//4.遍历获取表单项for (FileItem item : list) {//5. 判断是不是一个普通表单项boolean formField = item.isFormField();if(formField){//普通表单项, 当 enctype="multipart/form-data"时, request的getParameter()方法 无法获取参数String fieldName = item.getFieldName();String value = item.getString("utf-8");//设置编码System.out.println(fieldName + "=" + value);}else{//文件上传项//文件名String fileName = item.getName();//避免图片名重复 拼接UUIDString newFileName = UUIDUtils.getUUID()+"_"+ fileName;//获取输入流InputStream in = item.getInputStream();//创建输出流 输出到H盘FileOutputStream fos = new FileOutputStream("H:/upload/" +newFileName);//使用工具类IOUtils,copy文件IOUtils.copy(in,fos);//关闭流fos.close();in.close();}}}}} catch (FileUploadException e) {e.printStackTrace();}}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doGet(req, resp);}}
4.2.2.4 将图片上传到tomcat服务器

图片若存到服务器磁盘上,不方便访问;上传到tomcat服务器则可以通过外部加载图片。

  1. 将项目部署到webapps

    将部署方式改变为 war模式,把项目部署在tomcat的webapps下

    • idea中部署项目两种方式

      • war模式:将项目以war包的形式上传真实到服务器的webapps目录中;

      • war exploded模式:仅仅是目录的映射,就相当于tomcat在项目源文件夹中启动一样;

  2. 在webapps中创建upload目录

    upload目录专门用来保存上传过来的图片

  3. 修改代码,将图片上传到服务器

    • 修改图片的输出路径

      1. 获取到项目的运行目录信息
      2. 截取到webapps的 目录路径
      3. 拼接输出路径,将图片保存到upload
    //获取上传文件的内容
    InputStream in = item.getInputStream();// 动态获取项目的运行目录
    String path = this.getServletContext().getRealPath("/");// 截取到 webapps路径
    String webappsPath = path.substring(0, path.indexOf("lagou_edu_home"));// 拼接输出路径
    OutputStream out = new FileOutputStream(webappsPath+"/upload/"+newFileName);
    //拷贝文件到服务器
    IOUtils.copy(in,out);out.close();
    in.close();
    
  4. 页面加载图片

    将tomcat作为图片服务器使用时,存储上传的图片后,如果想要图片可以访问,需要在idea中进行配置:

    1. 选择external source —> 找到webapps目录下的的upload文件夹


    1. 上传一张图片到服务器

    2. 在项目内部页面加载图片

    <img src="/upload/abbd99891af442a8a9cb65848744452e_qiyu.jpg">
    
    1. 也可以通过HTTP方式访问
    http://localhost:8080/upload/abbd99891af442a8a9cb65848744452e_qiyu.jpg
    
4.2.2.5 BeanUtils工具类
  1. 介绍

​ BeanUtils 是 Apache commons组件的成员之一,主要用于简化JavaBean封装数据的操作。可以将一个表单提交的所有数据封装到JavaBean中

  1. 导入依赖
<dependency><groupId>commons-beanutils</groupId><artifactId>commons-beanutils</artifactId><version>1.8.3</version>
</dependency>
  1. BeanUtils 对象常用方法
方法 描述
populate(Object bean, Map properties) 将Map数据封装到指定Javabean中,
一般用于将表单的所有数据封装到javabean
setProperty(Object obj,String name,Object value) 设置属性值
getProperty(Object obj,String name) 获得属性值

Servlet编写

接口 保存&修改 课程营销信息

  • 名称: courseSalesInfo
  • 描述: 保存课程相关的营销信息
  • URL: http://localhost:8080/lagou_edu_home/courseSalesInfo
  • 请求方式: POST
  • 请求参数
字段 说明 类型 是否必需 备注
id 课程id int 添加操作不用携带, 修改操作必须携带ID
course_name 课程名称 String
brief 课程简介 String 一句话介绍课程
teacher_name 讲师名称 String
teacher_info 讲师介绍 String
preview_first_field 课程概述1 String 第一段描述 例如: 课程共15讲
preview_second_field 课程概述2 String 第二段描述 例如: 每周五更新
discounts 售卖价格 double 课程的售卖价格
price 商品原价 double 课程的原销售价
price_tag 促销文案 String 例如: 立即抢购
share_image_title 分享图title String
share_title 分享标题 String
share_description 分享描述 String
course_description 课程描述 String
file 文件
  • 请求参数示例 key:value 格式
file:文件
course_name: 微服务架构
brief: 大厂架构师带你一起学
teacher_name: PDD
teacher_info: 技术精湛安全驾驶30年
preview_first_field: 共5讲
preview_second_field: 每周二更新
discounts: 88.8
price: 800.0
price_tag: 先到先得
share_image_title: hello word
share_title: IT修炼之路永无止境
share_description: 金牌讲师带你了解最新最牛的技术让你的实力再次进阶!
course_description: 十年编程两茫茫,工期短,需求长。千行代码,Bug何处藏。纵使上线又如何,新版本,继续忙。黑白颠倒没商量,睡地铺,吃食堂。夜半梦醒,无人在身旁。最怕灯火阑珊时,手机响,心里慌.
  • 响应结果
字段 说明 类型 是否必须 备注
status 表示执行成功或失败 int 0 表示成功, 1 表示失败
msg 响应消息 String
  • 响应结果示例
成功
{"msg":"success","status":0}失败
{"msg":"fail","status":1}

创建CourseSalesInfoServlet类,继承HttpServlet , 完成保存课程营销信息操作。

因为上传的信息包含文件信息,无法直接通过request直接获取参数,所以不能继承BaseServlet

小技巧:判断请求列表里是否有id,若有则为修改操作,若无则为新建操作

@WebServlet("/courseSalesInfo")
public class CourseSalesInfoServlet  extends HttpServlet {/*** 保存课程营销信息*      收集表单数据,封装到course对象中,将图片上传到tomcat服务器中* */@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {try {//1.创建Course对象Course course = new Course();//2.创建Map集合,用来收集数据Map<String,Object> map = new HashMap<>();//3.创建磁盘工厂对象DiskFileItemFactory factory = new DiskFileItemFactory();//4.文件上传核心对象ServletFileUpload fileUpload = new ServletFileUpload(factory);//5.解析request对象,获取表单项集合List<FileItem> list = fileUpload.parseRequest(req);//6.遍历集合 判断哪些是普通的表单项,那些是文件表单项for (FileItem item : list) {boolean formField = item.isFormField();if(formField){//是普通表单项,获取表单项中的数据,保存到mapString fieldName = item.getFieldName();String value = item.getString("UTF-8");System.out.println(fieldName +" " + value);//使用map收集数据map.put(fieldName,value);}else{//文件上传项//获取文件名String fileName = item.getName();String newFileName = UUIDUtils.getUUID()+"_"+fileName;//获取输入流InputStream in = item.getInputStream();//获取webapps的目录路径String realPath = this.getServletContext().getRealPath("/");String wabappsPath = realPath.substring(0, realPath.indexOf("lagou_edu_home"));//创建输出流OutputStream out = new FileOutputStream(wabappsPath+"/upload/" + newFileName);IOUtils.copy(in,out);out.close();in.close();//将图片路径进行保存map.put("course_img_url", Constants.LOCAL_URL+"/upload/" + newFileName);}}//使用BeanUtils 将map中的数据封装到course对象BeanUtils.populate(course,map);// 业务处理CourseService cs = new CourseServiceImpl();String dateFormart = DateUtils.getDateFormart();if(map.get("id") != null) {//修改操作//补全信息course.setUpdate_time(dateFormart);//修改时间String result = cs.updateCourseSalesInfo(course);//响应结果resp.getWriter().print(result);}else {//新建操作//补全信息course.setCreate_time(dateFormart);//创建时间course.setUpdate_time(dateFormart);//修改时间course.setStatus(1); //上架String result = cs.saveCourseSalesInfo(course);//响应结果resp.getWriter().print(result);}} catch (Exception e) {e.printStackTrace();}}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doGet(req, resp);}
}
  • 保存图片URL优化

    1. 创建常量类
    public final class Constants {//本地访问地址public static final String LOCAL_URL = "http://localhost:8080";
    }
    
    1. 拼接图片URL
     //将图片路径进行保存map.put("course_img_url", Constants.LOCAL_URL+"/upload/" + newFileName);
    

postman测试上传文件

  1. 接口地址填写正确
  2. 将请求方式设置为POST
  3. 需要上传文件, 设置Headers: “key”:“Content-Type”, “value”:"multipart/form-data"

  1. Body选择form-data

  2. key 右侧下拉选择file;value 点击Select Files选择文件,按照接口文档补全测试参数

4.2.3 功能:展示课程内容

要展示的内容是对应课程下的章节与课时信息(一门课程有多个章节,一个章节有多个课时)

4.2.3.1 泛型

Course 类与Course_Section 类是一对多关系

  • Course 类中定义一个List集合,并指定List的泛型是 Course_Section 类型,表示 一个课程中可以包含多个章节

    Course类
    //添加list集合 泛型是 Course_Section
    List<Course_Section> sectionList = new ArrayList<>();
    
  • Course_Section 类中,定义一个Course类型的属性,用来保存章节所对应的具体的课程信息

    Course_Section 类
    //添加一个Course类型的属性
    private Course course;
    

Course_Section 类与 Course_Lesson 类是一对多关系

Course_Section类
//添加一个list集合 泛型是 Course_lesson
List<Course_Lesson> lessonList = new ArrayList<>();Course_Lesson类
//添加一个Course_Section类型的属性
private Course_Section course_section;
  • 后续可封装Course_Lesson类对象进行输出

    • 将课时数据封装到 章节对象中 section.setLessonList(lessonList);
    • Course_Section类有FastJson的@JSONField注解,自动生成get/ set方法

DAO层编写

我们在程序中尽量避免使用连接查询,将SQL进行拆分,每一条SQL对应一个功能

接口//根据课程ID查询课程相关信息public List<Course_Section> findSectionAndLessonByCourseId(int courseId);//根据章节ID 查询章节相关的课时信息public List<Course_Lesson> findLessonBySectionId(int sectionId);实现类//根据课程ID查询课程相关信息@Overridepublic List<Course_Section> findSectionAndLessonByCourseId(int courseId) {try {//1.创建QueryRunnerQueryRunner qr = new QueryRunner(DruidUtils.getDataSource());//2.编写SQLString sql = "SELECT \n" +"id,\n" +"course_id,\n" +"section_name,\n" +"description,\n" +"order_num,\n" +"STATUS\n" +"FROM course_section WHERE course_id = ?";//3.执行查询List<Course_Section> sectionList = qr.query(sql, newBeanListHandler<Course_Section>(Course_Section.class), courseId);//4.根据章节ID查询课时信息for (Course_Section section : sectionList) {//调用方法 获取章节对应的课时List<Course_Lesson> lessonList =findLessonBySectionId(section.getId());//将课时数据封装到 章节对象中section.setLessonList(lessonList);}return sectionList;} catch (SQLException e) {e.printStackTrace();return null;}}//根据章节ID查询课时信息@Overridepublic List<Course_Lesson> findLessonBySectionId(int sectionId) {try {QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());String sql = "SELECT \n" +"id,\n" +"course_id,\n" +"section_id,\n" +"theme,\n" +"duration,\n" +"is_free,\n" +"order_num,\n" +"STATUS\n" +"FROM course_lesson WHERE section_id = ?";List<Course_Lesson> lessonList = qr.query(sql, newBeanListHandler<Course_Lesson>(Course_Lesson.class), sectionId);return lessonList;} catch (SQLException e) {e.printStackTrace();return null;}}

【作业题】后台管理系统中的课程管理模块(后端篇)相关推荐

  1. 10.1 黑马Vue电商后台管理系统之完善订单管理模块--加入修改订单模块

    效果如下: 实现如下: 1.我仍然保留了添加地址这一个对话框,但只是绑定在另一个按钮上面,而点击左侧第一个按钮就会跳转到"修改订单"模块 <template v-slot=& ...

  2. 拉勾教育后台管理系统(SSM)(课程管理模块开发)【学习笔记】

    文章目录 1.项目架构 1.1.项目介绍 1.2.技术选型 1.2.1.前端技术选型 1.2.2.后端技术选型 1.3.项目开发环境 2.Maven进阶使用(Maven聚合工程) 2.1.maven的 ...

  3. 06_04_任务一:拉勾教育后台管理系统[课程管理模块、图片上传、 BeanUtils封装实体类](SSM)

    拉勾教育后台管理系统(SSM) 1. 项目架构 1.1 项目介绍 ​ 拉勾教育后台管理系统,是提供给拉勾教育的相关业务人员使用的一个后台管理系统, 业务人员可以在 这个后台管理系统中,对课程信息.广告 ...

  4. 70-项目实战后续(课程管理模块)

    项目实战后续(课程管理模块) 项目介绍: 前面我们完成了初步的项目,接下来实现更高等级的作用(基于框架的项目编写) 页面原型展示: 技术选型: 前端技术选型: 后端技术选型: 项目开发环境 : 开发工 ...

  5. PHP:【商城后台管理系统】部署角色管理,角色添加,菜单权限,删除角色功能

    PHP:[商城后台管理系统]部署角色管理,角色添加,菜单权限,删除角色功能 一.角色管理界面 ①首页 ②角色添加 ③角色编辑 ④角色删除 二.部署流程 部署流程 后端采用thinkphp6.0框架,角 ...

  6. MES管理系统中的批次管理,贯穿了生产制造的整个流程

    MES管理系统可以帮助制造企业解决很多生产管理中出现的问题,而且MES管理系统的功能可定制,所以企业可以根据自身需求进行开发定制. 在制造企业特别是流程制造行业,存在一些需要按批号管理的原材料.半成品 ...

  7. srm采购管理系统中的协同管理

    随着企业的不断壮大发展,市场竞争势必会更加激烈,srm采购管理系统的存在,对于整个企业以及实现供应资源整合是必不可少,srm采购管理系统的投入使用,也是企业经营管理中,企业提升核心竞争力的战略砝码,如 ...

  8. 基于前端Vue后端.NetCore Web后台管理系统通用开本框架采用前后端分离技术,前端使用vue2.6.0,后端使用.netcore3.1,支持跨平台、多租户

    基于前端Vue后端.NetCore Web后台管理系统通用开本框架采用前后端分离技术,前端使用vue2.6.0,后端使用.netcore3.1,支持跨平台.多租户.支持MySQL/SQLServer/ ...

  9. 谷粒学院(十)课程管理模块 | 课程大纲列表 | 二级联动 | 富文本编辑器

    一.课程添加分析 发布流程图 数据表对应 课程相关表的关系 二.课程管理–添加课程后端 1.使用代码生成器生成相关课程相关的代码 CodeGenerator类中生成 2.细节问题 (1)创建vo实体类 ...

最新文章

  1. C++学习笔记1[数据类型]
  2. js 层 分页显示选择用户名
  3. Gradle个人笔记(未完)
  4. 计算机实现数论 奇偶排列问题
  5. 一文讲述数仓组件SysCache
  6. Redis,Memcache,MongoDb的特点与区别
  7. JavaScript传参年月日格式日期自动运算问题解决
  8. group by 子句的
  9. 十天学会单片机和c语言编程 ppt,十天学会单片机和C语言编程笔记1
  10. iec611313标准下载_iec611313编程标准.ppt
  11. oa服务器硬件配置,OA办公系统
  12. 阿里编程规范(精简版)
  13. HTML制作诗词,利用 html2canvas 做个简单的诗词卡片生成器
  14. 【面试宝典】Java八股文之多线程并发面试题
  15. 微信小程序红包 php,微信小程序实现红包功能(后端PHP实现逻辑)
  16. C语言基础之5:运算符、表达式和语句
  17. 微信小程序之文件上传PHP后台接收
  18. Android 快速集成谷歌账户登录
  19. 导入数据库显示服务器发生意外,mysql 数据库无法启动(Ignoring the redo log due to missing M...
  20. 音频知识点(12)- P.563 工具编译及使用教程

热门文章

  1. 浅析国内云计算云主机现状市场
  2. memcache高集群搭建----主从同步实验
  3. 卤味商城小程序开发,打开市场竞争格局
  4. 后台管理界面-- 管理员管理[4]
  5. Android 支付开发(支付宝)
  6. 扁平化嵌套列表迭代器 [树的递归前序遍历 + 迭代前序遍历]
  7. Firebug安装与使用详解
  8. 组播报文转发过程RPF
  9. flex blazed 配置多个remoting-config
  10. springboot的学习记录