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

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

项目地址:课程管理模块

产品的原型图

课程管理

  • 实现以下功能:
    • 展示课程列表
    • 根据课程名和状态进行查询
    • 新建课程
    • 课程上架与下架

【作业题】后台管理系统中的课程管理模块(后端篇)_第1张图片

营销信息

  • 营销信息,其实就是设置课程的详细信息
    • 回显课程信息
    • 修改课程信息,包含了图片上传

【作业题】后台管理系统中的课程管理模块(后端篇)_第2张图片

配置课时

  • 对课程下所属的章节与课时进行配置(一个课程对应多个章节,一个章节有多个课时)
    • 以树形结构的下拉框形式,展示课程对应的章节与课时信息
    • 添加&修改章节功能
    • 修改章节状态功能
    • 添加&修改课时功能

【作业题】后台管理系统中的课程管理模块(后端篇)_第3张图片

课程管理模块表设计

【作业题】后台管理系统中的课程管理模块(后端篇)_第4张图片

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. 前后端分离会更加合理的分配团队的工作量,减轻后端团队的工作量,提高了性能和可扩展性。

【作业题】后台管理系统中的课程管理模块(后端篇)_第5张图片

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

【作业题】后台管理系统中的课程管理模块(后端篇)_第6张图片

创建Maven项目

2.2 项目目录

【作业题】后台管理系统中的课程管理模块(后端篇)_第7张图片

2.3 导入pom.xml

见项目地址pom.xml文件

2.4 导入工具类及配置文件

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

2.5 导入实体类

导入表对应的实体类

【作业题】后台管理系统中的课程管理模块(后端篇)_第8张图片

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

【作业题】后台管理系统中的课程管理模块(后端篇)_第9张图片

3) 添加依赖

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

<dependency>
    <groupId>org.projectlombokgroupId>
    <artifactId>lombokartifactId>
    <version>1.18.0version>
    <scope>providedscope>
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 需求分析

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

【作业题】后台管理系统中的课程管理模块(后端篇)_第10张图片

4.1.2 编写代码
Dao层编写

修改CourseDao,添加 findCourseList 方法

  • idea中try/catch快捷键

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

接口 CourseDao
    //查询课程列表信息
  	public List<Course> findCourseList();

实现类 CourseDaoImpl
	 @Override
    public List<Course> findCourseList() {

        try {
            //1.创建QueryRunner
            QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());

            //2.编写SQL
       String 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
	//创建 CourseDao
    CourseDao courseDao = new CourseDaoImpl();

    @Override
    public 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.alibabagroupId>
    <artifactId>fastjsonartifactId>
    <version>1.1.37version>
dependency>

<dependency>
    <groupId>com.colobugroupId>
    <artifactId>fastjson-jaxrs-json-providerartifactId>
    <version>0.3.1version>
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

【作业题】后台管理系统中的课程管理模块(后端篇)_第11张图片

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层编写

实现类
	/**
     * 根据课程名称 课程状态 查询课程信息
     * */
    //根据条件查询课程信息
    @Override
    public List<Course> findByCourseNameAndStatus(String courseName, String status) {

        try {
        //1.创建QueryRunner
        QueryRunner 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 转换为 int
            int 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);

实现类

   	//保存课程营销信息
    @Override
    public int saveCourseSalesInfo(Course course) {

        try {
            //1.创建QueryRunner
            QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());

            //2.编写SQL
            String 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/set
        public 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
        @Override
        public String toString() {
            JSONObject object = new JSONObject();
            object.put("status",code);
            object.put("msg",message);
            return object.toString();
        }
    }
    
  2. 编写Service

    接口
        public String saveCourseSalesInfo(Course course);
    
    实现类
    	@Override
        public 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属性和值

【作业题】后台管理系统中的课程管理模块(后端篇)_第12张图片

注意: 默认情况下,表单的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-iogroupId>
        <artifactId>commons-ioartifactId>
        <version>1.4version>
    dependency>
    
    <dependency>
        <groupId>commons-fileuploadgroupId>
        <artifactId>commons-fileuploadartifactId>
        <version>1.2.1version>
    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 {

    @Override
    protected 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();

                            //避免图片名重复 拼接UUID
                            String 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();
        }

    }

    @Override
    protected 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在项目源文件夹中启动一样;

        【作业题】后台管理系统中的课程管理模块(后端篇)_第13张图片

  2. 在webapps中创建upload目录

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

    【作业题】后台管理系统中的课程管理模块(后端篇)_第14张图片

  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文件夹

    【作业题】后台管理系统中的课程管理模块(后端篇)_第15张图片
    在这里插入图片描述

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

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

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

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

  1. 导入依赖
<dependency>
    <groupId>commons-beanutilsgroupId>
    <artifactId>commons-beanutilsartifactId>
    <version>1.8.3version>
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服务器中
     * */
    @Override
    protected 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){
                    //是普通表单项,获取表单项中的数据,保存到map
                    String 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();
        }

    }

    @Override
    protected 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"

【作业题】后台管理系统中的课程管理模块(后端篇)_第16张图片

  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查询课程相关信息
	@Override
	public List<Course_Section> findSectionAndLessonByCourseId(int courseId) {
        try {
            //1.创建QueryRunner
            QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
            
            //2.编写SQL
            String 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, new
            BeanListHandler<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查询课时信息
    @Override
    public 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, new
            BeanListHandler<Course_Lesson>(Course_Lesson.class), sectionId);
            	return lessonList;
            } catch (SQLException e) {
                e.printStackTrace();
                return null;
            }
    }

你可能感兴趣的:(一些Java作业,java)