SpringBootWeb案例-员工模块

YangeIT大约 21 分钟SpringBootSpringBootJSON

SpringBootWeb案例-员工模块

今日目标

  1. 修改员工 ✏️
  2. 配置管理 🍐
  3. 统一异常管理 🍐
  4. 员工信息统计 ✏️

知识准备

  1. 完成了部门修改,班级修改,员工分页查询,员工插入
  2. 完成了阿里云工具类封装,并且能上传图片到阿里云
  3. 知道异常的作用

api文档

员工管理

1.1 修改员工之回显

修改员工之回显

需求:修改员工信息

image-20231206150423754

在进行修改员工信息的时候,我们首先先要根据员工的ID查询员工的详细信息用于页面回显展示,然后用户修改员工数据之后,点击保存按钮,就可以将修改的数据提交到服务端,保存到数据库。 具体操作为:

  1. 根据ID查询员工信息
  2. 保存修改的员工信息

接口文档

根据ID查询员工数据

  • 基本信息
    • 请求路径:/emps/{id}
    • 请求方式:GET
    • 接口描述:该接口用于根据主键ID查询员工的信息

点击查看api文档

代码操作

image-20231206150751994
image-20231206150751994

查询回显 时,既需要查询出员工的基本信息 ,又需要查询出该员工的工作经历信息

我们可以先通过一条SQL语句,查询出指定员工的基本信息,及其员工的工作经历信息。SQL如下:

select e.*,
       ee.id ee_id,
       ee.begin ee_begin,
       ee.end ee_end,
       ee.company ee_company,
       ee.job ee_job
from emp e left join emp_expr ee on e.id = ee.emp_id where e.id = 38;

运行截图:image

具体的实现思路如下:

image-20231206150841795

总结

课堂作业

  1. 根据上述提示,完成回显代码!🎤
  2. resultMap 和取别名,驼峰命名的区别?

2.2 修改员工

前言

查询回显之后,就可以在页面上修改员工的信息了。

image-20231206153353232

当用户修改完数据之后,点击保存按钮,就需要将数据提交到服务端,然后服务端需要将修改后的数据更新到数据库中 。

而此次更新的时候,既需要更新员工的基本信息; 又需要更新员工的工作经历信息 。

接口文档

  • 基本信息
    • 请求路径:/emps
    • 请求方式:PUT
    • 接口描述:该接口用于修改员工的数据信息

详细接口情况:接口文档

image-20231206154424569
image-20231206154424569

代码操作

1). EmpController 增加 update 方法接收请求参数,响应数据

/**
* 更新员工信息
*/
@PutMapping
public Result update(@RequestBody Emp emp){
    log.info("修改员工信息, {}", emp);
    empService.update(emp);
    return Result.success();
}

2). EmpService 接口增加 update 方法

/**
* 更新员工信息
* @param emp
*/
void update(Emp emp);

3). EmpServiceImpl 实现类实现 update 方法

@Transactional
@Override
public void update(Emp emp) {
    //1. 根据ID更新员工基本信息
    emp.setUpdateTime(LocalDateTime.now());
    empMapper.updateById(emp);

    //2. 根据员工ID删除员工的工作经历信息 【删除老的】
    empExprMapper.deleteByEmpIds(Arrays.asList(emp.getId()));

    //3. 新增员工的工作经历数据 【新增新的】
    Integer empId = emp.getId();
    List<EmpExpr> exprList = emp.getExprList();
    if(!CollectionUtils.isEmpty(exprList)){
        exprList.forEach(empExpr -> empExpr.setEmpId(empId));
        empExprMapper.insertBatch(exprList);
    }
}

2. 配置文件

员工管理的新增和修改功能我们已开发完成,但在我们所开发的程序中还一些小问题,下面我们就来分析一下当前案例中存在的问题以及如何优化解决。

配置文件

参数配置化

image
image

在我们之前编写的程序中进行文件上传时,需要指定两个参数:

  • endpoint //阿里云OSS域名
  • bucket //存储空间的名字

关于以上的这些阿里云相关配置信息,我们是直接写死在java代码中了(硬编码),如果我们在做项目时每涉及到一个第三方技术服务,就将其参数硬编码,那么在Java程序中会存在两个问题:

  1. 如果这些参数发生变化了,就必须在源程序代码中改动这些参数,然后需要重新进行代码的编译,将Java代码编译成class字节码文件再重新运行程序。(比较繁琐)
  2. 如果我们开发的是一个真实的企业级项目, Java类可能会有很多,如果将这些参数分散的定义在各个Java类当中,我们要修改一个参数值,我们就需要在众多的Java代码当中来定位到对应的位置,再来修改参数,修改完毕之后再重新编译再运行。(参数配置过于分散,是不方便集中的管理和维护)

为了解决以上分析的问题,我们可以将参数配置在配置文件中。如下:

#顶格写
#阿里云配置统一写在配置文件中,方便管理
aliyun:
  oss:
    endpoint: https://oss-cn-wuhan-lr.aliyuncs.com
    access-key-id: LTAI5tGJKWuBifboPQrMQFze
    access-key-secret: PghR2lcWRjJI4UupdqLtPVXCIz9INS
    bucket-name: java86

在将阿里云OSS配置参数交给properties配置文件来管理之后,我们的 UploadController 就变为以下形式:

image
image

因为 application.yml 是springboot项目配置文件,所以springboot程序在启动时会默认读取 application.yml 配置文件,而我们可以使用一个现成的注解:@Value,获取配置文件中的数据。

@Value 注解通常用于外部配置的属性注入,具体用法为: @Value("${配置文件中的key}")

@Component // 表明当前类需要被spring进行实例化,放到ioc容器中
public class AliOSSUtils {
    @Value("${aliyun.oss.endpoint}")
    private String endpoint;
    @Value("${aliyun.oss.access-key-id}")
    private String accessKeyId;
    @Value("${aliyun.oss.access-key-secret}")
    private String accessKeySecret;
    @Value("${aliyun.oss.bucket-name}")
    private String bucketName;

    /**
     * 实现上传图片到OSS
     */
    public String upload(MultipartFile multipartFile) throws IOException {
        // 获取上传的文件的输入流
        InputStream inputStream = multipartFile.getInputStream();

        省略代码。。。
    }

image
image

总结

课堂作业

  1. 配置文件yml格式最注意是什么?🎤
  2. 为什么要将配置放到yml中保存?
  3. 根据上述提示优化你的工程!

3. 异常处理

前言

当前问题

当我们在修改部门数据的时候,如果输入一个在数据库表中已经存在的手机号,点击保存按钮之后,前端提示了错误信息 ,但是返回的结果并不是统一的响应结果,而是框架默认返回的错误结果 。

image-20231206173046369
image-20231206173046369

状态码为500,表示服务器端异常,我们打开idea,来看一下,服务器端出了什么问题。

image-20231206173513852
image-20231206173513852

上述错误信息的含义是,emp员工表的phone手机号字段的值重复了,因为在数据库表emp中已经有了13309090001这个手机号了,我们之前设计这张表时,为phone字段建议了唯一约束,所以该字段的值是不能重复的。

而当我们再将该员工的手机号也设置为 13309090001,就违反了唯一约束,此时就会报错。

我们来看一下出现异常之后,最终服务端给前端响应回来的数据长什么样。

image-20231206173723132
image-20231206173723132

响应回来的数据是一个JSON格式的数据。但这种JSON格式的数据还是我们开发规范当中所提到的统一响应结果Result吗?

显然并不是。由于返回的数据不符合开发规范,所以前端并不能解析出响应的JSON数据 。

接下来我们需要思考的是出现异常之后,当前案例项目的异常是怎么处理的?

  • 答案:没有做任何的异常处理
image-20231206173748901

当我们没有做任何的异常处理时,我们三层架构处理异常的方案:

  • Mapper接口在操作数据库的时候出错了,此时异常会往上抛(谁调用Mapper就抛给谁),会抛给service。
  • service 中也存在异常了,会抛给controller。
  • 而在controller当中,我们也没有做任何的异常处理,所以最终异常会再往上抛。最终抛给框架之后,框架就会返回一个JSON格式的数据,里面封装的就是错误的信息,但是框架返回的JSON格式的数据并不符合我们的开发规范。

全局异常处理器代码操作

全局异常处理器

我们该怎么样定义全局异常处理器?

  • 定义全局异常处理器非常简单,就是定义一个类,在类上加上一个注解@RestControllerAdvice,加上这个注解就代表我们定义了一个全局异常处理器。
  • 在全局异常处理器当中,需要定义一个方法来捕获异常,在这个方法上需要加上注解@ExceptionHandler。通过@ExceptionHandler注解当中的value属性来指定我们要捕获的是哪一类型的异常。

cn.yangeit.handler

@RestControllerAdvice
public class GlobalExceptionHandler {

    //处理异常
    @ExceptionHandler
    public Result ex(Exception e){//方法形参中指定能够处理的异常类型
        e.printStackTrace();//打印堆栈中的异常信息
        //捕获到异常之后,响应一个标准的Result
        return Result.error("对不起,操作失败,请联系管理员");
    }
}

@RestControllerAdvice = @ControllerAdvice + @ResponseBody

处理异常的方法返回值会转换为json后再响应给前端

重新启动SpringBoot服务,打开浏览器,再来测试一下 修改员工 这个操作,我们依然设置已存在的 "13309090001" 这个手机号:

image-20231206174112018
image-20231206174112018

此时,我们可以看到,出现异常之后,异常已经被全局异常处理器捕获了。然后返回的错误信息,被前端程序正常解析,然后提示出了对应的错误提示信息。

以上就是全局异常处理器的使用,主要涉及到两个注解:

  • @RestControllerAdvice //表示当前类为全局异常处理器
  • @ExceptionHandler //指定可以捕获哪种类型的异常进行处理

总结

课堂作业

  1. 根据上述提示完成统一异常捕捉类的书写🎤
  2. 拓展作业:完成提示用户具体时什么错误!,如提示xxxx号码重复了!!🚀

4. 员工信息统计

员工管理的增删改查功能我们已开发完成,接下来,我们再来完成员工信息统计的接口开发。 对于这些图形报表的开发,其实呢,都是基于现成的一些图形报表的组件开发的,比如:Echarts、HighCharts等。

而报表的制作,主要是前端人员开发,引入对应的组件(比如:ECharts)即可。 服务端开发人员仅为其提供数据即可。

官网:https://echarts.apache.org/zh/index.htmlopen in new window

image-20231206175132548
image-20231206175132548

4.1 职位统计

前言

image-20231206175006836

对于这类的图形报表,服务端要做的,就是为其提供数据即可。 我们可以通过官方的示例,看到提供的数据其实就是X轴展示的信息,和对应的数据。

image-20231206175538625
image-20231206175538625

相关信息

4.1.2 接口文档

1). 基本信息

请求路径:/report/empJobData

请求方式:GET

接口描述:统计各个职位的员工人数

详细接口情况:接口文档

为了封装上面需要给前端返回的数据,在pojo包下再创建一个实体类 JobOption,封装给前端返回的结果:

/**
 * 员工职位人数统计
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JobOption {
    private List jobList; //职位列表
    private List dataList; //人数列表
}

代码操作

代码实现

1). 定义ReportController,并添加方法。


@Slf4j
@RequestMapping("/report")
@RestController
public class ReportController {

    @Autowired
    private ReportService reportService;

    /**
     * 统计各个职位的员工人数
     */
    @GetMapping("/empJobData")
    public Result getEmpJobData(){
        log.info("统计各个职位的员工人数");
        JobOption jobOption = reportService.getEmpJobData();
        return Result.success(jobOption);
    }
}

2). 定义ReportService接口,并添加接口方法。

public interface ReportService {
    /**
     * 统计各个职位的员工人数
     * @return
     */
    JobOption getEmpJobData();
}

3). 定义ReportServiceImpl实现类,并实现方法

@Service
public class ReportServiceImpl implements ReportService {

    @Autowired
    private EmpMapper empMapper;
	
    @Override
    public JobOption getEmpJobData() {
     List<Map<String,Object>> list = empMapper.countEmpJobData();
        List<Object> jobList = list.stream().map(dataMap -> dataMap.get("pos")).collect(Collectors.toList());
        List<Object> dataList = list.stream().map(dataMap -> dataMap.get("total")).collect(Collectors.toList());
        return new JobOption(jobList, dataList);
    }
}

总结

课堂作业

  1. 根据提示,完成代码🎤

4.2 性别统计

性别统计

image-20231206183412541

对于这类的图形报表,服务端要做的,就是为其提供数据即可。 我们可以通过官方的示例,看到提供的数据就是一个json格式的数据。

image-20231206183531169
image-20231206183531169

相关信息

接口文档

1). 基本信息

请求路径:/report/empGenderData

请求方式:GET

接口描述:统计员工性别信息 详细接口情况:接口文档

代码操作

代码实现

1). 在ReportController,添加方法。

/**
 * 统计员工性别信息
 */
@GetMapping("/empGenderData")
public Result getEmpGenderData(){
    log.info("统计员工性别信息");
    List<Map> genderList = reportService.getEmpGenderData();
    return Result.success(genderList);
}

2). 在ReportService接口,添加接口方法。

/**
 * 统计员工性别信息
 */
List<Map> getEmpGenderData();

3). 在ReportServiceImpl实现类,实现方法

@Override
public List<Map> getEmpGenderData() {
    return empMapper.countEmpGenderData();
}