黑马Tlias JavaWeb后台管理系统 07 后端实战 部门管理

接下来呢,我们就要进入到后端Web实战篇的学习,在实战篇中,就要将前面学习的基础知识用起来,来完成一个大的综合案例 - Tlias智能学习辅助系统。

那这个案例里面呢,包括以下功能:

  • 部门管理

  • 员工管理

  • 员工信息统计

  • 学员信息统计

  • 班级、学员管理

在整个实战篇中,我们需要完成如下功能:

  • 部门管理:查询、新增、修改、删除
  • 员工管理:
    • 查询、新增、修改、删除
    • 文件上传
  • 报表统计
  • 登录认证
  • 日志管理
  • 班级管理(自己实战内容)
  • 学员管理(自己实战内容)

准备工作

开发规范

前后端分离开发

前端页面需要数据,可以通过发送异步请求,从后台工程获取。但是,我们前后台是分开来开发的,那么前端人员怎么知道后台返回数据的格式呢?后端人员开发,怎么知道前端人员需要的数据格式呢?

所以针对这个问题,我们前后台统一制定一套规范!我们前后台开发人员都需要遵循这套规范开发,这就是我们的接口文档

Restful风格

REST(Representational State Transfer),表述性状态转换,它是一种软件架构风格。

传统URL风格如下:

基于REST风格URL如下:

通过URL定位要操作的资源,通过HTTP动词(请求方式)来描述具体的操作。更加简洁、更加规范。

描述模块的功能通常使用复数,表示此类资源而非单个资源。

Apifox

  • 前后端并行开发,后端开发完对应的接口之后,对接口进行请求测试
  • 前端开发的过程中,获取数据,测试页面的渲染展示

Postman、Apipost、Apifox等工具。

Apifox工具的功能更加强大、更加完善。

介绍

Apifox是一款集成了Api文档、Api调试、Api Mock、Api测试的一体化协作平台。

作用:接口文档管理、接口请求测试、Mock服务

https://apifox.com/

安装

工程创建

  1. 创建SpringBoot工程

引入Web开发起步依赖、MyBatis、MySQL驱动、lombok

  1. 创建数据库及其对应的表结构,并在application.yml中配置数据库的基本信息
  • 创建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,'学工部','2023-09-25 09:47:40','2024-07-25 09:47:40'),
                      (2,'教研部','2023-09-25 09:47:40','2024-08-09 15:17:04'),
                      (3,'咨询部','2023-09-25 09:47:40','2024-07-30 21:26:24'),
                      (4,'就业部','2023-09-25 09:47:40','2024-07-25 09:47:40'),
                      (5,'人事部','2023-09-25 09:47:40','2024-07-25 09:47:40'),
                      (6,'行政部','2023-11-30 20:56:37','2024-07-30 20:56:37');

  • 在application.yml配置文件中配置数据库的连接信息
spring:
  application:
    name: tlias-web-management
  #mysql连接配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/tlias
    username: root
    password: 1234
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

  1. 准备基础包结构,并引入实体类Dept及统一的响应结果封装类Result
  • 基础包结构

  • 实体类Dept
package com.itheima.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Dept {
    private Integer id;
    private String name;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}
  • 统一响应结果
package com.itheima.pojo;

import lombok.Data;
import java.io.Serializable;

/**
 * 后端统一返回结果
 */
@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;
    }

}
  • 基础代码结构

- DeptMapper
package com.itheima.mapper;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface DeptMapper {
}
- DeptService
package com.itheima.service;

public interface DeptService {
}
- DeptServiceImpl
package com.itheima.service.impl;

import com.itheima.service.DeptService;
import org.springframework.stereotype.Service;

@Service
public class DeptServiceImpl implements DeptService {
}
- DeptController
package com.itheima.controller;

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

/**
 * 部门管理控制器
 */
@RestController
public class DeptController {
}

查询部门

基本实现

需求

查询所有的部门数据,展示在部门管理页面中

接口描述

部门管理 -> 部门列表查询

实现思路

代码实现

  1. Controller层

在DeptController中,增加list方法

/**
 * 部门管理控制器
 */
@RestController
public class DeptController {

    @Autowired
    private DeptService deptService;

    /**
     * 查询部门列表
     */
    @RequestMapping("/depts")
    public Result list(){
        List<Dept> deptList = deptService.findAll();
        return Result.success(deptList);
    }
}
  1. Service层

DeptService中,增加findAll方法

public interface DeptService {
    /**
     * 查询所有部门
     */
    public List<Dept> findAll();
}

实现类中,增加findAll方法

@Service
public class DeptServiceImpl implements DeptService {
    
    @Autowired
    private DeptMapper deptMapper;

    public List<Dept> findAll() {
        return deptMapper.findAll();
    }
}
  1. Mapper层
@Mapper
public interface DeptMapper {
    /**
     * 查询所有部门
     */
    @Select("select * from dept")
    public List<Dept> findAll();
    
}

接口测试

启动项目,使用Apifox进行测试

http://localhost:8080/depts

我们发现已经查询出了所有的部门数据,并且响应回来的就是json格式的数据,与接口文档一致。我们再尝试用POST、PUT、DELETE方式来请求数据。

经过测试发现,我们其实可以通过任何方式的请求来访问查询部门的这个接口。而在接口文档中,明确要求该接口的请求方式为GET,那么如何限制请求的方式呢?

  1. 方式一:在Controller方法的@RequestMapping注解中通过method属性来限定
@RestController
public class DeptController {

    @Autowired
    private DeptService deptService;

    /**
     * 查询部门列表
     */
    @RequestMapping(value = "/depts", method = RequestMethod.GET)
    public Result list(){
        List<Dept> deptList = deptService.findAll();
        return Result.success(deptList);
    }
}
  1. 方式二:在Controller方法上使用,@RequestMapping的衍生注解@GetMapping,该注解就是标识当前方法必须以GET方式请求
@RestController
public class DeptController {

    @Autowired
    private DeptService deptService;

    /**
     * 查询部门列表
     */
    @GetMapping("/depts")
    public Result list(){
        List<Dept> deptList = deptService.findAll();
        return Result.success(deptList);
    }
}

此时用GET以外的请求就无法得到数据了

在项目开发中最好使用的第二种方式,更简洁

  • GET方式:@GetMapping
  • POST方式:@PostMapping
  • PUT方式:@PutMapping
  • DELETE方式:@DeleteMapping

数据封装

在上述测试中,我们发现部门的数据中,id、name两个属性是有值的,但是createTime、updateTime两个字段值并未成功封装,而数据库中是有对应的字段值的

原因如下

  • 实体类属性名和数据库查询返回的字段名一致,MyBatis会自动封装
  • 如果实体类属性名和数据库查询返回的字段名不一致,不能自动封装

解决方案

  • 手动结果映射
  • 起别名
  • 开启驼峰命名
  1. 手动结果映射

在DeptMapper接口方法上,通过 @Results及@Result 进行手动结果映射。

@Results({@Result(column = "create_time", property = "createTime"),
          @Result(column = "update_time", property = "updateTime")})
@Select("select id, name, create_time, update_time from dept")
public List<Dept> findAll();

说明:

  • @Results 注解源码:

  • @Result源代码:

  1. 起别名

在SQL语句中,对不一样的列名起别名,别名和实体类属性名一样

@Select("select id, name, create_time createTime, update_time updateTime from dept")
public List<Dept> findAll();
  1. 开启驼峰命名(推荐)

如果字段名与属性名符合驼峰命名规则,MyBatis会自动通过驼峰命名规则映射。驼峰命名规则:abc_xyz => abcXyz

  • 表中字段名:abc_xyz
  • 表中属性名:abcXyz

在application.yml中做如下配置,开启开关。

mybatis:
  configuration:
    map-underscore-to-camel-case: true
  • 实体类属性 与 数据表中的字段名必须严格遵守驼峰命名。

此时可以查询到createTime和updateTime的值。

前后端联调

联调测试

完成了查询部门的功能,也通过Apifox工具测试通过了,下面我们再基于前后端分离的方式进行接口联调。

  1. 将资料中提供的"前端环境"文件夹中的压缩包拷贝进 没有中文不带空格的目录下并解压。

  1. 双击运行nginX.exe

  1. http://localhost:90

请求访问流程

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

其实这里,是通过前端服务器NginX中提供的反向代理功能实现的。

  1. 浏览器发起请求,请求的是localhost:90,那其实请求的事nginx服务器。
  2. 在nginx服务器中并没有对请求直接进行处理,而是将请求转发给了后端的tomcat服务器,最终由tomcat服务器来处理该请求。

这个过程就是通过nginx反向代理实现的。那为什么浏览器不直接请求后端的tomcat服务器,而是直接请求nginx服务器呢,有以下几点原因:

  1. 安全:由于后端的tomcat服务器一般都会搭建集群,会有很多的服务器,把所有的tomcat暴露给前端,让前端直接请求tomcat,对于后端服务器比较危险
  2. 灵活:基于nginx的反向代理实现更加灵活,后端想增加、减少服务器,对于前端来说是无感知的,只需要在nginx中配置即可
  3. 负载均衡:基于nginx的反向代理,可以很方便地实现后端tomcat的负载均衡操作

具体的请求访问流程如下:

  • location:用于定义匹配特定uri请求的规则
  • ^~/api/:表示精确匹配,即只匹配以/api/开头的路径
  • rewrite:该指令用于重写匹配到的uri路径
  • proxy_pass:该指令用于代理转发,它将匹配到的请求转发给位于后端的指令服务器

删除部门

需求

接口描述

部门管理 -> 删除部门

思路分析

简单参数接收

在Controller中需要接收前端传递的请求参数

  1. 方案一:通过原始的HttpServletRequest对象获取请求参数
/**
* 根据ID删除部门 - 简单参数接收: 方式一 (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();
}

这种方案实现较为繁琐,而且还需要进行手动类型转换。

  1. 方案二:通过Spring提供的@RequestParam注解,将请求参数绑定给方法形参
@DeleteMapping("/depts")
public Result delete(@RequestParam("id") Integer deptId){
    System.out.println("根据ID删除部门: " + deptId);
    return Result.success();
}

@RequestParam注解的value属性,需要与前端传递的参数名保持一致

  • @RequestParam注解required属性默认为true,代表该参数必须传递,如果不传递将报错。如果参数可选,可以将属性设置为false。
  1. 方案三:如果请求参数名与形参变量名相同,直接定义方法参即可接收。(推荐)

此时即可省略@RequestParam

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

代码实现

  1. Controller层
/**
 * 根据id删除部门 - delete http://localhost:8080/depts?id=1
 */
@DeleteMapping("/depts")
public Result delete(Integer id){
    System.out.println("根据id删除部门, id=" + id);
    deptService.deleteById(id);
    return Result.success();
}
  1. Service层

接口

/**
 * 根据id删除部门
 */
void deleteById(Integer id);

实现类

public void deleteById(Integer id) {
deptMapper.deleteById(id);
}
  1. Mapper层
/**
 * 根据id删除部门
 */
@Delete("delete from dept where id = #{id}")
void deleteById(Integer id);

如果mapper接口方法形参只有一个普通类型的参数,#{...}里面的属性名可以随便写,如#{id}``#{value}等。

对于DML语句来说,执行完毕也有返回值,返回值代表的是增删改操作影响的记录数,所以可以将执行DML语句的方法返回值设置为Integer。但是一般开发中不需要这个返回值,所以也可以设置为void。

测试,6 行政部 已删除

新增部门

需求

点击 “新增部门” 的按钮之后,弹出新增部门表单,填写部门名称之后,点击确定之后,保存部门数据。

接口描述

部门管理 -> 新增部门

思路分析

json参数接收

在Controller中需要接收前端传递的json格式的请求参数。

  • JSON格式的参数,通常会使用一个实体对象进行接收。
  • 规则:JSON数据的键名与方法形参对象的属性名相同,并需要使用@RequestBody注解标识

前端传递的请求参数格式为json,内容如下{"name":"研发部"}。这里,我们可以通过一个对象来接收,只需要保证对象中有name属性即可。

代码实现

  1. Controller层
/**
 * 新增部门 - POST http://localhost:8080/depts   请求参数:{"name":"研发部"}
 */
@PostMapping("/depts")
public Result save(@RequestBody Dept dept){
    System.out.println("新增部门, dept=" + dept);
    deptService.save(dept);
    return Result.success();
}
  1. Service层

接口

/**
 * 新增部门
 */
void save(Dept dept);

实现类

public void save(Dept dept) {
//补全基础属性
dept.setCreateTime(LocalDateTime.now());
dept.setUpdateTime(LocalDateTime.now());
//保存部门
deptMapper.insert(dept);
}
  1. Mapper层
/**
 * 保存部门
 */
@Insert("insert into dept(name,create_time,update_time) values(#{name},#{createTime},#{updateTime})")
void insert(Dept dept);

如果在mapper接口中,需要传递多个参数,可以把多个参数封装到一个对象中。

在SQL语句中获取参数的时候,#{...}里面写的是对象的属性名(注意是属性名,而不是表的字段名)

测试

把前面删除的行政部添加回来

添加更多的部门

修改部门

对于任何业务的修改功能来说,一般都会分为两步进行:查询回显、修改数据。

查询回显

需求

当我们点击 “编辑” 的时候,需要根据ID查询部门数据,然后用于页面回显展示。

接口描述

部门管理 -> 根据ID查询

思路分析

路径参数接收

/depts/1/depts/2这种在url中传递的参数,我们称之为路径参数。那么如何接收这样的路径参数呢?

路径参数:通过请求URL直接传递参数,使用{…}来标识该路径参数,需要使用@PathVariable获取路径参数。如下所示:

如果路径参数名与Controller方法形参名称一致,@PathVariable注解的value属性是可以省略的。

代码实现

  1. Controller层
/**
 * 根据ID查询 - GET http://localhost:8080/depts/1
 */
@GetMapping("/depts/{id}")
public Result getById(@PathVariable Integer id){
    System.out.println("根据ID查询, id=" + id);
    Dept dept = deptService.getById(id);
    return Result.success(dept);
}
  1. Service层

接口

/**
 * 根据id查询部门
 */
Dept getById(Integer id);

实现类

public Dept getById(Integer id) {
    return deptMapper.getById(id);
}
  1. Mapper层
/**
* 根据ID查询部门数据
*/
@Select("select id, name, create_time, update_time from dept where id = #{id}")
Dept getById(Integer id);

测试

修改数据

需求

接口描述

部门管理 -> 修改部门

思路分析

前端传递的请求参数是json格式,在Controller的方法中,我们可以通过@RequestBody注解来接收,并将其封装到一个对象中。

代码实现

  1. Controller层
/**
 * 修改部门 - PUT http://localhost:8080/depts  请求参数:{"id":1,"name":"研发部"}
 */
@PutMapping("/depts")
public Result update(@RequestBody Dept dept){
    System.out.println("修改部门, dept=" + dept);
    deptService.update(dept);
    return Result.success();
}
  1. Service层

接口

/**
 * 修改部门
 */
void update(Dept dept);

实现类。由于是修改操作,每一次修改数据都需要更新updateTime。

public void update(Dept dept) {
    //补全基础属性
    dept.setUpdateTime(LocalDateTime.now());
    //保存部门
    deptMapper.update(dept);
}
  1. Mapper层
/**
 * 更新部门
 */
@Update("update dept set name = #{name},update_time = #{updateTime} where id = #{id}")
void update(Dept dept);

测试:

@RequestMapping

部门的增删改查功能都已经实现了。我们发现在DeptController中所定义的方法,所有的请求路径都是由/depts开头的,只要操作的是部门数据,请求路径都是depts开头。

那么这个时候可以把这个公共的路径抽取到类上。

一个完整的请求路径,应该是类上的 @RequestMapping 的value属性 + 方法上的 @RequestMapping的value属性。

/**
 * 部门管理控制器
 */
@RequestMapping("/depts")
@RestController
public class DeptController {
    @Autowired
    private DeptService deptService;

    /**
     * 查询部门列表
     */
    @GetMapping
    public Result list(){
        List<Dept> deptList = deptService.findAll();
        return Result.success(deptList);
    }

    /**
     * 根据id删除部门 - delete http://localhost:8080/depts?id=1
     */
    @DeleteMapping
    public Result delete(Integer id){
        System.out.println("根据id删除部门, id=" + id);
        deptService.deleteById(id);
        return Result.success();
    }

    /**
     * 新增部门 - POST http://localhost:8080/depts   请求参数:{"name":"研发部"}
     */
    @PostMapping
    public Result save(@RequestBody Dept dept){
        System.out.println("新增部门, dept=" + dept);
        deptService.save(dept);
        return Result.success();
    }

    /**
     * 根据ID查询 - GET http://localhost:8080/depts/1
     */
    @GetMapping("/{id}")
    public Result getById(@PathVariable Integer id){
        System.out.println("根据ID查询, id=" + id);
        Dept dept = deptService.getById(id);
        return Result.success(dept);
    }

    /**
     * 修改部门 - PUT http://localhost:8080/depts  请求参数:{"id":1,"name":"研发部"}
     */
    @PutMapping
    public Result update(@RequestBody Dept dept){
        System.out.println("修改部门, dept=" + dept);
        deptService.update(dept);
        return Result.success();
    }

}

日志技术

概述

程序中的日志,是用来记录应用程序的运行信息、状态信息、错误信息的

  • 追踪程序中的数据信息、程序的执行过程
  • 便于对程序的性能进行优化
  • 在程序出现问题之后排查问题、解决问题
  • 监控系统的运行状态

之前我们通过System.out.println(...) 来输出日志,但是存在以下问题

  • 硬编码。所有的记录日志的代码都是硬编码,没有办法做到灵活控制,要想不输出这个日志只能删除掉记录日志的代码。
  • 只能输出日志到控制台
  • 不便于程序扩展、维护

现在在项目开发中会使用专业的日志框架来解决这个问题。

日志框架

  • **JUL:**这是JavaSE平台提供的官方日志框架,也被称为JUL。配置相对简单,但不够灵活,性能较差。
  • **Log4j:**一个流行的日志框架,提供了灵活的配置选项,支持多种输出目标。
  • **Logback:**基于Log4j升级而来,提供了更多的功能和配置选项,性能由于Log4j。
  • Slf4j:(Simple Logging Facade for Java)简单日志门面,提供了一套日志操作的标准接口及抽象类,允许应用程序使用不同的底层日志框架。

Logback入门

  1. 准备工作:引入logback依赖

在springboot中无须引入,已经传递了此依赖

<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.4.11</version>
</dependency>
  1. 引入配置文件logback.xml,在resources目录下
<?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个字符宽度  %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" />
    </root>
</configuration>
  1. 记录日志:定义日志记录对象Logger,记录日志

在Test文件夹

注意import的Logger不要错,org.slf4j.Logger

package com.itheima;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogTest {
    //定义日志记录对象
    private static final Logger log = LoggerFactory.getLogger(LogTest.class);

    @Test
    public void testLog() {
        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("结束计算");
    }
}

运行单元测试,可以看到控制台中输出的日志。

不仅输出了日志的信息,还可以看到日志输出的时间、线程名、具体是哪个类中输出的

Logback配置文件

logback.xml

该配置文件是对Logback日志框架输出的日志进行控制的,可以用来配置输出的格式、位置及日志开关等。

常用的两种输出日志的位置:控制台、系统文件。

  1. 如果需要输出日志到控制台。添加如下配置:
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  <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>
  1. 输出日志到文件
<!-- 按照每天生成日志文件 -->
<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>
  1. 日志开关配置

开启 ALL,取消 OFF

<!-- 日志输出级别 -->
<root level="ALL">
  <!--输出到控制台-->
  <appender-ref ref="STDOUT" />
  <!--输出到文件-->
  <appender-ref ref="FILE" />
</root>

Logback日志级别

优先级从低到高

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

可以在配置文件logback.xml中,灵活的控制输出那些类型的日志。(大于等于配置的日志级别的日志才会输出)

<!-- 日志输出级别 -->
<root level="info">
    <!--输出到控制台-->
    <appender-ref ref="STDOUT" />
    <!--输出到文件-->
    <appender-ref ref="FILE" />
</root>

案例日志记录

/**
 * 部门管理控制器
 */
@Slf4j
@RequestMapping("/depts")
@RestController
public class DeptController {

    @Autowired
    private DeptService deptService;

    /**
     * 查询部门列表
     */
    //@RequestMapping(value = "/depts", method = RequestMethod.GET)
    @GetMapping
    public Result list(){
        //System.out.println("查询部门列表");
        log.info("查询部门列表");
        List<Dept> deptList = deptService.findAll();
        return Result.success(deptList);
    }

    /**
     * 根据id删除部门 - delete http://localhost:8080/depts?id=1
     */
    @DeleteMapping
    public Result delete(Integer id){
        //System.out.println("根据id删除部门, id=" + id);
        log.info("根据id删除部门, id: {}" , id);
        deptService.deleteById(id);
        return Result.success();
    }

    /**
     * 新增部门 - POST http://localhost:8080/depts   请求参数:{"name":"研发部"}
     */
    @PostMapping
    public Result save(@RequestBody Dept dept){
        //System.out.println("新增部门, dept=" + dept);
        log.info("新增部门, dept: {}" , dept);
        deptService.save(dept);
        return Result.success();
    }

    /**
     * 根据ID查询 - GET http://localhost:8080/depts/1
     */
    @GetMapping("/{id}")
    public Result getById(@PathVariable Integer id){
        //System.out.println("根据ID查询, id=" + id);
        log.info("根据ID查询, id: {}" , id);
        Dept dept = deptService.getById(id);
        return Result.success(dept);
    }

    /**
     * 修改部门 - PUT http://localhost:8080/depts  请求参数:{"id":1,"name":"研发部"}
     */
    @PutMapping
    public Result update(@RequestBody Dept dept){
        //System.out.println("修改部门, dept=" + dept);
        log.info("修改部门, dept: {}" , dept);
        deptService.update(dept);
        return Result.success();
    }
}
  • lombok中提供的@Slf4j注解可以简化定义日志记录器这步操作。添加了该注解就相当于在类中定义了日志记录器

<font style="color:rgb(46,161,33);">private static Logger log = LoggerFactory. getLogger(Xxx. class);</font>

### 黑马 Java Web 班级课程资料与教程 对于希望深入学习Java Web开发的学生而言,黑马程序员提供了全面而系统的培训方案。该培训机构的Java Web课程不仅涵盖了基础理论知识,还注重实践技能培养。 #### 前端Web开发部分 前端Web开发主要涉及HTML、CSS以及JavaScript等技术的学习。学生将掌握如何构建响应式的用户界面,并能够创建交互性强的应用程序[^2]。 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <!-- 引入外部样式表 --> <link rel="stylesheet" href="styles.css"> </head> <body> <h1>Hello, world!</h1> <!-- 使用内部脚本 --> <script src="scripts.js"></script> </body> </html> ``` #### 后端Web开发部分 后端Web开发则聚焦于Servlet、JSP技术和Spring框架等内容的教学。通过这些课程模块的学习,学员可以理解并实现服务器端逻辑处理流程,包括但不限于数据存储管理、业务规则定义等方面的工作。 ```java @WebServlet("/hello") public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { PrintWriter writer = resp.getWriter(); writer.println("<h1>Say hello from servlet.</h1>"); } } ``` 此外,在整个教学过程中会穿插项目实战环节,帮助学员更好地巩固所学知识点并将之应用于实际场景当中去。例如,基于Java Web的传智播客CRM企业管理系统的案例分析就非常适合用来作为毕业设计或者大型作业的主题之一[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值