Day07 后端Web实战:部门管理


图1 第3部分【后端Web实战】
【Tlias智能学习辅助系统】需求
图2 【Tlias智能学习辅助系统】需求
图3 【部门管理】需求
目录

1. 准备工作

1.1 开发规范-开发模式

①前后端混合开发
图4 前后端混合开发
②前后端分离开发
图5 前后端分离的开发流程
图6 接口文档格式图示
图7 小结

1.2 开发规范-Restful风格

①Restful
传统风格url 请求方式 含义
http://localhost:8080/user/getById?id=1 GET 查询id为1的用户
http://localhost:8080/user/saveUser POST 新增用户
http://localhost:8080/user/updateUser POST 修改用户
http://localhost:8080/user/deleteUser?id=1 GET 删除id为1的用户
REST风格url 请求方式 含义
http://localhost:8080/users/1 GET 查询id为1的用户
http://localhost:8080/users/1 DELETE 删除id为1的用户
http://localhost:8080/users POST 新增用户
http://localhost:8080/users PUT 修改用户
图8 传统风格url和REST风格url比较

REST风格的特点 ?

REST风格中的四种请求方式及对应的操作?

图9 Restful小结
②Apifox
思考:
图10 API工具
Apifox
图11 Apifox安装、测试验证

为什么要使用Apifox?

图12 Apifox小结

1.3 工程搭建

①创建空项目、SpringBoot工程
图13 创建空项目(web-ai-project02)
图14 设置项目字符编码(utf8)
图15 设置项目Maven配置
图16 设置项目的SDK和语言级别17
图17 创建Springboot新模块①
图18 创建Springboot新模块② 设置依赖
图19 删除Springboot模块中多余的配置文件和两个静态目录
②创建数据库tlias、表dept,配置application.yml
图20 创建数据库和数据表
create schema tlias;

use tlias;

CREATE TABLE dept (
    id int unsigned PRIMARY KEY AUTO_INCREMENT COMMENT 'ID, 主键',
    name varchar(10) NOT NULL UNIQUE COMMENT '部门名称',
    create_time datetime DEFAULT NULL COMMENT '创建时间',
    update_time datetime DEFAULT NULL COMMENT '修改时间'
) COMMENT '部门表';

INSERT INTO dept VALUES 
(1,'学工部(杜)','2024-09-25 09:47:40','2024-09-25 09:47:40'),
(2,'教研部','2024-09-25 09:47:40','2024-09-09 15:17:04'),
(3,'咨询部','2024-09-25 09:47:40','2024-09-30 21:26:24'),
(4,'就业部','2024-09-25 09:47:40','2024-09-25 09:47:40'),
(5,'人事部','2024-09-25 09:47:40','2024-09-25 09:47:40'),
(6,'行政部','2024-11-30 20:56:37','2024-09-30 20:56:37');

src/main/resources/application.yml

spring:
  application:
    name: tlias-web-management
  #数据库的连接信息
  datasource:
    url: jdbc:mysql://www.duzhaojiang.cn:3306/tlias?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: ********

#Mybatis的相关配置
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
③准备基础代码结构,创建Dept.java、Result.java
图21 基础代码目录结构
cn/zjy/pojo/Dept.java
package cn.zjy.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dept {
    private Integer id;
    private String name;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}
cn/zjy/pojo/Result.java
package cn.zjy.pojo;
import lombok.Data;

/**
 * 后端统一返回结果
 */
@Data
public class Result {

    private Integer code; //编码:1成功,0为失败
    private String msg; //错误信息
    private Object data; //数据

    public static Result success() {
        Result result = new Result();
        result.code = 1;
        result.msg = "success";
        return result;
    }

    public static Result success(Object object) {
        Result result = new Result();
        result.data = object;
        result.code = 1;
        result.msg = "success";
        return result;
    }

    public static Result error(String msg) {
        Result result = new Result();
        result.msg = msg;
        result.code = 0;
        return result;
    }

}
cn/zjy/mapper/DeptMapper.java
package cn.zjy.mapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface DeptMapper {
}
cn/zjy/service/DeptService.java
package cn.zjy.service;

public interface DeptService {
}
cn/zjy/service/impl/DeptServiceImpl.java
package cn.zjy.service.impl;

import cn.zjy.service.DeptService;
import org.springframework.stereotype.Service;

@Service
public class DeptServiceImpl implements DeptService {
}
cn/zjy/controller/DeptController.java
package cn.zjy.controller;

import org.springframework.web.bind.annotation.RestController;

@RestController
public class DeptController {
}
图22 Resaul返回结果格式统一规范

2. 查询部门

2.1 接口开发

图23 查询部分的接口文档
思路分析
图24 查询部分的三层架构各层职责
功能实现
cn/zjy/mapper/DeptMapper.java 主要代码
@Mapper
public interface DeptMapper {
    @Select("select id, name, create_time, update_time from dept order by update_time desc")
    List<Dept> findAll();
}
cn/zjy/service/DeptService.java 主要代码
public interface DeptService {
    List<Dept> findAll();
}
cn/zjy/service/impl/DeptServiceImpl.java 主要代码
@Service
public class DeptServiceImpl implements DeptService {

    @Autowired
    private DeptMapper deptMapper;
    @Override
    public List<Dept> findAll() {
        return deptMapper.findAll();
    }
}
cn/zjy/controller/DeptController.java
@RestController
public class DeptController {
    @Autowired
    private DeptService deptService;
    //@RequestMapping(value ="/depts",method=RequestMethod.GET)//method:指定请求方式
    @GetMapping("/depts")
    public Result list() {
        System.out.println("查询全部部门数据");
        List<Dept> deptList = deptService.findAll();
        return Result.success(deptList);
    }
}
图25 查询部门测试验证1
图26 三层调用关系
数据封装
图27 属性名和表字段名不一致
@Results({
    @Result(column = "create_time", property = "createTime"),
    @Result(column = "update_time", property = "updateTime")
})
@Select("select id, name, create_time, update_time from dept order by update_time desc")
public List<Dept> findAll();
@Select("select id, name, create_time createTime, update_time updateTime from dept ...")
public List<Dept> findAll();
mybatis:
  configuration: 
    map-underscore-to-camel-case: true
图28 属性名和表字段名不一致解决方法
cn/zjy/mapper/DeptMapper.java
public interface DeptMapper {
    /**
     * 查询所有部门数据
     */
    //方式一:手动结果映射
    //    @Results({
    //        @Result(column = "create_time", property = "createTime"),
    //        @Result(column = "update_time", property = "updateTime")
    //    })

    //方式二:起别名
    //@Select("select id,name, create_time createTime, update_time updateTime from dept order by update_time desc")

    //方式三:开启驼峰命名
    @Select("select id, name, create_time, update_time from dept order by update_time desc")
    List<Dept> findAll();
}
图29 查询部门测试验证2
小结

Mybatis默认数据封装的规则 ?

如果字段名与实体类属性名不一致,如何解决 ?

图30 小结

2.2 前后端联调测试

图31 查询部门前后端联调测试验证2

前端工程请求服务器的地址为http://localhost:90/api/depts,是如何访问到后端的tomcat服务器的?

图32 小结
Nginx代理服务器配置
图33 Nginx代理服务器配置
小结

什么是反向代理?

Nginx中反向代理的配置 ?

图34 反向代理服务器小结

3.删除部门

需求分析
图35 删除部分需求
思路分析
图36 三层架构每层的职责
Controller接收参数

接收请求参数:DELETE /depts?id=8 简单参数

方式一:通过原始的 HttpServletRequest 对象获取请求参数。

@DeleteMapping("/depts")
public Result delete(HttpServletRequest request){
    String idStr = request.getParameter("id");
    int id = Integer.parseInt(idStr);
    System.out.println("根据ID删除部门: " + id);
    return Result.success();
}

方式二:通过Spring提供的 @RequestParam 注解,将请求参数绑定给方法形参。

@DeleteMapping("/depts")
public Result delete(@RequestParam("id") Integer deptId){
    System.out.println("根据ID删除部门: " + deptId);
    return Result.success();
}

注意:@RequestParam注解required属性默认为true,代表该参数必须传递,如果不传递将报错。 如果参数可选,可以将属性设置为false。

方式三:如果请求参数名与形参变量名相同,直接定义方法形参即可接收。(省略@RequestParam 推荐

@DeleteMapping("/depts")
public Result delete(Integer id){
    System.out.println("根据ID删除部门: " + id);
    return Result.success();
}
图37 Controller接受简单参数的方式
小结

简单参数接收方式

图38 Controller接受简单参数小结
删除部门

cn/zjy/controller/DeptController.java 添加下列方法

@DeleteMapping("/depts")
public Result delete(Integer id){
    System.out.println("根据ID删除部门数据: " + id);
    deptService.delete(id);
    return Result.success();
}

cn/zjy/service/impl/DeptServiceImpl.java 添加下列方法

@Override
public void delete(Integer id) {
    deptMapper.delete(id);
}

cn/zjy/mapper/DeptMapper.java 添加下列方法

@Delete("delete from dept where id = #{id}")
void delete(Integer id);
图39 删除部门测试验证
图40 删除部门前后端联调测试验证

4.新增部门

需求分析
图41 新增部门需求
思路分析
图42 三层架构每层的职责
Controller接收参数
图43 Controller接受JSON对象的数据
新增部门

cn/zjy/controller/DeptController.java 添加下列方法

@PostMapping("/depts")
public Result add(@RequestBody Dept dept){
    System.out.println("添加部门: " + dept);
    deptService.add(dept);
    return Result.success();
}

cn/zjy/service/impl/DeptServiceImpl.java 添加下列方法

@Override
public void add(Dept dept) {
    dept.setCreateTime(LocalDateTime.now());
    dept.setUpdateTime(LocalDateTime.now());
    deptMapper.add(dept);
}

cn/zjy/mapper/DeptMapper.java 添加下列方法

@Insert("insert into dept(name, create_time, update_time) values(#{name}, #{createTime}, #{updateTime})")
void add(Dept dept);
图44 新增部门测试验证
图45 新增部门前后端联调测试验证
小结

如何接收JSON格式的请求参数 ?

json格式的请求参数适用场景?

图46 新增部门小结

5.修改部门

需求
图47 修改部门的步骤

5.1 查询回显

需求
图48 查询回显需求题
思路
图49 三层架构每层的职责
Controller接收参数 -- 路径参数
@GetMapping("/depts/{id}")
public Result getInfo(@PathVariable("id") Integer deptId){
    System.out.println("根据ID查询部门数据: " + deptId);
    return Result.success();
}

{…}来标识该路径参数和接受的参数变量同名省略("id")

@GetMapping("/depts/{id}")
public Result getInfo(@PathVariable Integer id){
    System.out.println("根据ID查询部门数据: " + id);
    return Result.success();
}
根据ID查询部门

cn/zjy/controller/DeptController.java 添加下列方法

@GetMapping("/depts/{id}")
public Result getInfo(@PathVariable Integer id){
    System.out.println("根据ID查询部门数据: " + id);
    Dept dept = deptService.getInfo(id);
    return Result.success(dept);
}

cn/zjy/service/impl/DeptServiceImpl.java 添加下列方法

@Override
public Dept getInfo(Integer id) {
    return deptMapper.getById(id);
}

cn/zjy/mapper/DeptMapper.java 添加下列方法

@Select("select id, name, create_time, update_time from dept where id = #{id}")
Dept getById(Integer id);
图50 根据ID查询部门验证
图51 根据ID查询部门前后端联调测试验证
小结
图52 根据ID查询部门小结

5.2 修改数据

需求
图52 修改数据需求
思路
图54 三层架构每层的职责
Controller接收参数
修改数据

cn/zjy/controller/DeptController.java 添加下列方法

@PutMapping("/depts")
public Result update(@RequestBody Dept dept){
    System.out.println("修改部门数据: " + dept);
    deptService.update(dept);
    return Result.success();
}

cn/zjy/service/impl/DeptServiceImpl.java 添加下列方法

@Override
public void update(Dept dept) {
    //1.补全基础属性-updateTime
    dept.setUpdateTime(LocalDateTime.now());
    //2,调用Mapper接口方法更新部门
    deptMapper.update(dept);
}

cn/zjy/mapper/DeptMapper.java 添加下列方法

@Update("update dept set name = #{name}, update_time = #{updateTime} where id = #{id}")
void update(Dept dept);
图55 修改部门测试验证
图56 修改部门前后端联调测试验证
@RequestMapping
图57 @RequestMapping
cn/zjy/controller/DeptController.java 主要代码
@RequestMapping("/depts")
@RestController
public class DeptController {
    @Autowired
    private DeptService deptService;

    //@RequestMapping(value ="/depts",method=RequestMethod.GET)//method:指定请求方式
    @GetMapping
    public Result list() {
        System.out.println("查询全部部门数据");
        List<Dept> deptList = deptService.findAll();
        return Result.success(deptList);
    }

    /**
     * 删除部门:省略@RequestParam(前端传递的请求参数名与服务端方法形参名一致)
     */
    @DeleteMapping
    public Result delete(Integer id) {
        System.out.println("根据ID删除部门:" + id);
        deptService.delete(id);
        return Result.success();
    }

    @PostMapping
    public Result add(@RequestBody Dept dept) {
        System.out.println("添加部门: " + dept);
        deptService.add(dept);
        return Result.success();
    }

    /*
     * 根据ID查询部门
     */
    @GetMapping("/{id}")
    public Result getInfo(@PathVariable Integer id) {
        System.out.println("根据ID查询部门:" + id);
        Dept dept = deptService.getInfo(id);
        return Result.success(dept);
    }

    @PutMapping
    public Result update(@RequestBody Dept dept){
        System.out.println("修改部门数据: " + dept);
        deptService.update(dept);
        return Result.success();
    }
}
小结
图58 @RequestMapping小结

6.日志技术

问题的提出
图59 日志记录存在的问题
日志技术
图60 日志的用途
图61 日志技术

6.1 Logback快速入门

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.4.11</version>
</dependency>
图62 logback的依赖已经传递
src/main/resources/logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度  %logger{50}: 最长50个字符(超出.切割)  %msg:日志消息,%n是换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 日志输出级别 -->
    <root level="debug">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>
src/test/java/cn/zjy/LogTest.java
package cn.zjy;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDateTime;

public class LogTest {
    @Test
    public void testLog(){
        System.out.println(LocalDateTime.now() + " : 开始计算...");

        int sum = 0;
        int[] nums = {1, 5, 3, 2, 1, 4, 5, 4, 6, 7, 4, 34, 2, 23};
        for (int num : nums) {
            sum += num;
        }

        System.out.println("计算结果为: "+sum);
        System.out.println(LocalDateTime.now() + "结束计算...");
    }

    private static final Logger log = LoggerFactory.getLogger(LogTest.class);
    @Test
    public void testLog2(){
        log.debug("开始计算...");
        int sum = 0;
        int[] nums = {1, 5, 3, 2, 1, 4, 5, 4, 6, 7, 4, 34, 2, 23};
        for (int i = 0; i <= nums.length; i++) {
            sum += nums[i];
        }
        log.info("计算结果为: "+sum);
        log.debug("结束计算...");
    }
}
图63 System.out和log测试
停止日志的输出

将 src/main/resources/logback.xml 日志输出级别改为off,将停止日志的输出

<!-- 日志输出级别 -->
<root level="off">
    <appender-ref ref="STDOUT" />
</root>
小结
图64 logback小结

6.2 Logback配置文件详解

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d 表示日期,%thread 表示线程名,%-5level表示级别从左显示5个字符宽度,%logger显示日志记录器的名称, %msg表示日志消息,%n表示换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern>
        </encoder>
    </appender>

    <!-- 系统文件输出 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 日志文件输出的文件名, %i表示序号 -->
            <FileNamePattern>D:/tlias-%d{yyyy-MM-dd}-%i.log</FileNamePattern>
            <!-- 最多保留的历史日志文件数量 -->
            <MaxHistory>30</MaxHistory>
            <!-- 最大文件大小,超过这个大小会触发滚动到新文件,默认为 10MB -->
            <maxFileSize>10MB</maxFileSize>
        </rollingPolicy>

        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d 表示日期,%thread 表示线程名,%-5level表示级别从左显示5个字符宽度,%msg表示日志消息,%n表示换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern>
        </encoder>
    </appender>

    <!-- 日志输出级别 -->
    <root level="ALL">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
    </root>
</configuration>
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">...</appender>

<!-- 系统文件输出 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">...</appender>
<root level="ALL">
    <appender-ref ref="STDOUT" />
    <appender-ref ref="FILE" />
</root>
图65 日志输出测试验证

6.3 Logback日志级别

日志级别 说明 记录方式
trace 追踪,记录程序运行轨迹 【使用很少】 log.trace("...")
debug 调试,记录程序调试过程中的信息,实际应用中一般将其视为最低级别 【使用较多】 log.debug("...")
info 记录一般信息,描述程序运行的关键事件,如:网络连接、io操作 【使用较多】 log.info("...")
warn 警告信息,记录潜在有害的情况 【使用较多】 log.warn("...")
error 错误信息 【使用较多】 log.error("...")
<root level="info">
    <appender-ref ref="STDOUT" />
    <appender-ref ref="FILE" />
</root>
图66 日志级别

src/test/java/cn/zjy/LogTest.java testLog2()方法中加入下列3行

        log.trace("跟踪(杜)trace...");
        log.warn("警告(杜)warn...");
        log.error("错误(杜)error...");
图67 日志级别输出测试验证
优化tlias案例日志记录
cn/zjy/controller/DeptController.java 日志优化
package cn.zjy.controller;

import cn.zjy.pojo.Dept;
import cn.zjy.pojo.Result;
import cn.zjy.service.DeptService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Slf4j
@RequestMapping("/depts")
@RestController
public class DeptController {
    //private static final Logger log = LoggerFactory.getLogger(DeptController.class);

    @Autowired
    private DeptService deptService;

    //@RequestMapping(value ="/depts",method=RequestMethod.GET)//method:指定请求方式
    @GetMapping
    public Result list() {
        //System.out.println("查询全部部门数据");
        log.info("查询全部部门数据");
        List<Dept> deptList = deptService.findAll();
        return Result.success(deptList);
    }

    /*
     *删除部门-方式一:HttpServletRequest获取请求参数
     */
/*    @DeleteMapping("/depts")
    public Result delete(HttpServletRequest request){
        String idStr =request.getParameter("id");
        int id =Integer.parseInt(idStr);
        System.out.println("根据ID删除部门:"+id);
        return Result.success();
    }*/

    /*
     * 删除部门-方式二: @RequestParam
     * 注意事项:一旦声明了aRequestParam,该参数在请求时必须传递,
     * 如果不传递将会报错,(默认required为true)
     */

/*    @DeleteMapping("/depts")
//    public Result delete(@RequestParam("id") Integer deptId) {
    public Result delete(@RequestParam(value = "id", required = false) Integer deptId){
        System.out.println("根据ID删除部门:" + deptId);
        return Result.success();
    }*/

    /**
     * 删除部门-方式三:省略@RequestParam(前端传递的请求参数名与服务端方法形参名一致)
     */
    @DeleteMapping
    public Result delete(Integer id) {
        //System.out.println("根据ID删除部门:" + id);
        log.info("根据ID删除部门:" + id);
        deptService.delete(id);
        return Result.success();
    }

    @PostMapping
    public Result add(@RequestBody Dept dept) {
        //System.out.println("添加部门: " + dept);
        log.info("添加部门: " + dept);
        deptService.add(dept);
        return Result.success();
    }

    /*
     * 根据ID查询部门
     */
    //@GetMapping("/depts/{id}")
/*    public Result getInfo(@PathVariable("id") Integer deptId) {
        System.out.println("根据ID查询部门:" + deptId);
        return Result.success();
    }*/

    /*
     * 根据ID查询部门
     */
    @GetMapping("/{id}")
    public Result getInfo(@PathVariable Integer id) {
        //System.out.println("根据ID查询部门:" + id);
        log.info("根据ID查询部门:" + id);
        Dept dept = deptService.getInfo(id);
        return Result.success(dept);
    }

    @PutMapping
    public Result update(@RequestBody Dept dept){
        //System.out.println("修改部门数据: " + dept);
        log.info("修改部门数据: " + dept);
        deptService.update(dept);
        return Result.success();
    }
}
图68 日志优化测试验证
图69 Logback日志小结

①②③④⑤⑥⑦⑧⑨⑩


返回