SpringBootWeb案例

YangeIT大约 45 分钟SpringBootPageHelper前后端协议SpringBootJSON·@ConfigurationProperties

SpringBootWeb案例

今日目标

  • 员工管理
    • 新增员工 ✏️
      • 文件上传(本地上传和oss上传) ✏️
    • 事务管理
    • 修改员工 ✏️

知识准备

  1. 能理解controller,service,mapper三个包的含义以及调用顺序
  2. 能完成新增,修改,删除,查询基本操作
  3. 能理解前后端交互是用过JSON格式传递数据的
  4. 能说出HTTP协议的特点,特别是请求和响应的组成部分

api文档

员工增删改查

1. 员工模块 ✏️

1.1 新增员工

新增员工

新增员工

前面我们已经实现了员工信息的条件分页查询。 接下来我们要实现的是新增员工的功能实现,页面原型如下:

image-20231203164759377

首先我们先完成"新增员工"的功能开发,而在新增员工中,需要添加头像后期讲解,而头像需要用到文件上传技术后期讲解。 当整个员工管理功能全部开发完成之后,我们再通过配置文件来优化一些内容。

image-20231203165015822

在添加员工信息时,录入的信息包括两个部分,一个部分是员工的基本信息; 另一个部分是员工的工作经历信息,那这两部分信息最终在录入完成后,点击 "保存" 按钮后都会提交到服务器端。

最终,员工的基本信息是要保存在员工表 emp 中的。 而 员工的工作经历信息,要保存在员工工作经历信息表 emp_expr 中的。

我们参照接口文档open in new window来开发新增员工功能

  • 基本信息

    • 请求路径:/emps
    • 请求方式:POST
    • 接口描述:该接口用于添加员工的信息
  • 请求参数

    • 参数格式application/json
    • 参数说明:
    名称类型是否必须备注
    usernamestring必须用户名
    namestring必须姓名
    gendernumber必须性别, 说明: 1 男, 2 女
    imagestring非必须图像
    deptIdnumber非必须部门id
    entryDatestring非必须入职日期
    jobnumber非必须职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管, 5 咨询师
    salarynumber非必须薪资
    exprListobject[]非必须工作经历列表
    |- companystring非必须所在公司
    |- jobstring非必须职位
    |- beginstring非必须开始时间
    |- endstring非必须结束时间
    • 请求数据样例:
{
"image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/2022-09-03-07-37-38222.jpg",
"username": "linpingzhi",
"name": "林平之",
"gender": 1,
"job": 1,
"entryDate": "2022-09-18",
"deptId": 1,
"phone": "18809091234",
"salary": 8000,
"exprList": [
    {
        "company": "百度科技股份有限公司",
        "job": "java开发",
        "begin": "2012-07-01",
        "end": "2019-03-03"
    },
    {
        "company": "阿里巴巴科技股份有限公司",
        "job": "架构师",
        "begin": "2019-03-15",
        "end": "2023-03-01"
    }
    ]
}
  • 响应数据

    • 参数格式application/json
    • 参数说明
    参数名类型是否必须备注
    codenumber必须响应码,1 代表成功,0 代表失败
    msgstring非必须提示信息
    dataobject非必须返回的数据
    • 响应数据样例:
  {
      "code":1,
      "msg":"success",
      "data":null
  }

代码操作

思路分析

思路分析

新增员工的具体的流程:

image-20231203165627940
image-20231203165627940

接口文档规定:

  • 请求路径:/emps
  • 请求方式:POST
  • 请求参数:Json格式数据
  • 响应数据:Json格式数据

问题1:如何限定请求方式是POST?

@PostMapping

问题2:怎么在controller中接收json格式的请求参数?

@RequestBody  //把前端传递的json数据填充到实体类中

准备工作

-- 员工工作经历表
create table emp_expr(
    id int unsigned primary key auto_increment comment 'ID, 主键',
    emp_id  int unsigned null comment '员工ID', -- 关联的是emp员工表的ID
    begin  date null comment '开始时间',
    end date null comment '结束时间',
    company varchar(50) null comment '公司名称',
    job varchar(50) null comment '职位'
) comment '工作经历';

🎯工作经历的EmpExpr实体类、Mapper接口和Mapper映射文件,以及Emp实体类的改造

准备的EmpExprMapper接口及映射配置文件EmpExprMapper.xml,并准备实体类接收前端传递的json格式的请求参数。

  1. EmpExprMapper接口
@Mapper
public interface EmpExprMapper {

}
  1. EmpExprMapper.xml 配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.yangeit.mapper.EmpExprMapper">

</mapper>

3). 需要在 Emp 员工实体类中增加属性 exprList 来封装工作经历数据。 最终完整代码如下:

/**
 * 工作经历
 */
@Data
public class EmpExpr {
    private Integer id; //ID
    private Integer empId; //员工ID
    private LocalDate begin; //开始时间
    private LocalDate end; //结束时间
    private String company; //公司名称
    private String job; //职位
}
@Data
public class Emp {
    private Integer id; //ID,主键
    private String username; //用户名
    private String password; //密码
    private String name; //姓名
    private Integer gender; //性别, 1:男, 2:女
    private String phone; //手机号
    private Integer job; //职位, 1:班主任,2:讲师,3:学工主管,4:教研主管,5:咨询师
    private Integer salary; //薪资
    private String image; //头像
    private LocalDate entryDate; //入职日期
    private Integer deptId; //关联的部门ID
    private LocalDateTime createTime; //创建时间
    private LocalDateTime updateTime; //修改时间

    //封装部门名称数
    private String deptName; //部门名称

    //封装员工工作经历信息
    private List<EmpExpr> exprList;
}

总结

课堂作业

  1. 截止到现在,学习了哪些动态sql标签?分别又什么作用?🎤

2. 事务管理 🍐

2.1 事务介绍

事务管理

目前我们实现的新增员工功能中,操作了两次数据库,执行了两次 insert 操作。

  • 第一次:保存员工的基本信息到 emp 表中。
  • 第二次:保存员工的工作经历信息到 emp_expr 表中。

如果说,保存员工的基本信息成功了,而保存员工的工作经历信息出错了,会发生什么现象呢?那接下来,我们来做一个测试 。 我们可以在代码中,人为在保存员工的service层的save方法中,构造一个错误:

image-20231203173905601
image-20231203173905601

那接下来,我们就重启服务,打开浏览器,来做一个测试:

image-20231203174210524

点击 “保存” 之后,提示 “系统接口异常”。

image-20231203174240731
image-20231203174240731

我们可以打开IDEA控制台看一下,报出的错误信息。 我们看到,保存了员工的基本信息之后,系统出现了异常。

image-20231203174317737
image-20231203174317737

我们再打开数据库,看看表结构中的数据是否正常。

1). emp 员工表中是有 Jerry 这条数据的。

image-20231203174422799
image-20231203174422799

2). emp_expr 表中没有改员工的工作经历信息。

image-20231203174534087

最终,我们看到,程序出现了异常 ,员工表 emp 数据保存成功了, 但是 emp_expr 员工工作经历信息表,数据保存失败了。 那是否允许这种情况发生呢?

  • 不允许
  • 因为这属于一个业务操作,如果保存员工信息成功了,保存工作经历信息失败了,就会造成数据库数据的不完整、不一致。

那如何解决这个问题呢? 这需要通过数据库中的事务来解决这个问题。

代码操作

事务控制主要三步操作:开启事务、提交事务/回滚事务。

  • 需要在这组操作执行之前,先开启事务 ( start transaction; / begin;)。
  • 所有操作如果全部都执行成功,则提交事务 ( commit; )。
  • 如果这组操作中,有任何一个操作执行失败,都应该回滚事务 ( rollback )。

那接下来,我们就可以将添加员工的业务操作,进行事务管理。 具体的SQL如下:

-- 开启事务
start transaction; -- 或者 begin;

-- 1. 保存员工基本信息
insert into emp values (39, 'Tom', '123456', '汤姆', 1, '13300001111', 1, 4000, '1.jpg', '2023-11-01', 1, now(), now());

-- 2. 保存员工的工作经历信息
insert into emp_expr(emp_id, begin, end, company, job) values (39,'2019-01-01', '2020-01-01', '百度', '开发'),                                                            					   (39,'2020-01-10', '2022-02-01', '阿里', '架构');

-- 提交事务(全部成功)
commit;

-- 回滚事务(有一个失败)
rollback;

事务管理的场景,是非常多的,比如:

  • 银行转账
  • 下单扣减库存

总结

课堂作业

  1. 事务有什么特点和作用?🎤

2.2 Spring事务管理 🍐 🍐

Spring事务管理

在上述实现的新增员工的功能中,一旦在保存员工基本信息后出现异常。 我们就会发现,员工信息保存成功,但是工作经历信息保存失败,造成了数据的不完整不一致。

image-20231203175516003

产生原因:

  • 先执行新增员工的操作,这步执行完毕,就已经往员工表 emp 插入了数据。
  • 执行 1/0 操作,抛出异常
  • 抛出异常之前,下面所有的代码都不会执行了,批量保存工作经历信息,这个操作也不会执行 。

此时就出现问题了,员工基本信息保存了,员工的工作经历信息未保存,业务操作前后数据不一致。

而要想保证操作前后,数据的一致性,就需要让新增员工中涉及到的两个业务操作,要么全部成功,要么全部失败 。

那我们如何,让这两个操作要么全部成功,要么全部失败呢 ?

那就可以通过事务 来实现,因为一个事务中的多个业务操作,要么全部成功,要么全部失败。

此时,我们就需要在新增员工功能中添加事务。

image-20231203183433579

在方法运行之前,开启事务,如果方法成功执行,就提交事务,如果方法执行的过程当中出现异常了,就回滚事务。

思考:开发中所有的业务操作,一旦我们要进行控制事务,是不是都是这样的套路?

答案:是的。

所以在spring框架当中就已经把事务控制的代码都已经封装好了,并不需要我们手动实现。我们使用了spring框架,我们只需要通过一个简单的注解@Transactional就搞定了。

代码操作

  1. @Transactional作用:就是在当前这个方法执行开始之前来开启事务,方法执行完毕之后提交事务。如果在这个方法执行的过程当中出现了异常,就会进行事务的回滚操作。

  2. @Transactional注解:我们一般会在业务层当中来控制事务,因为在业务层当中,一个业务功能可能会包含多个数据访问的操作。在业务层来控制事务,我们就可以将多个数据访问操作控制在一个事务范围内。

@Transactional注解书写位置:

  • 方法
    • 当前方法交给spring进行事务管理
    • 当前类中所有的方法都交由spring进行事务管理 (推荐)
  • 接口
    • 接口下所有的实现类当中所有的方法都交给spring 进行事务管理

接下来,我们就可以在业务方法delete上加上@Transactional来控制事务 。

@Transactional
@Override
public void save(Emp emp) {
    //1.补全基础属性
    emp.setCreateTime(LocalDateTime.now());
    emp.setUpdateTime(LocalDateTime.now());
    //2.保存员工基本信息
    empMapper.insert(emp);

    int i = 1/0;

    //3. 保存员工的工作经历信息 - 批量
    Integer empId = emp.getId();
    List<EmpExpr> exprList = emp.getExprList();
    if(!CollectionUtils.isEmpty(exprList)){
        exprList.forEach(empExpr -> empExpr.setEmpId(empId));
        empExprMapper.insertBatch(exprList);
    }
}
 








 









说明:可以在application.yml配置文件中开启事务管理日志,这样就可以在控制看到和事务相关的日志信息了

#spring事务管理日志
logging:
  level:
    org.springframework.jdbc.support.JdbcTransactionManager: debug

在业务功能上添加@Transactional注解进行事务管理后,我们重启SpringBoot服务,使用 Apifox测试:

image-20231203183834341
image-20231203183834341

添加Spring事务管理后,由于服务端程序引发了异常,所以事务进行回滚。

image-20231203183929282
image-20231203183929282

打开数据库,我们会看到 emp 表 与 emp_expr 表中都没有对应的数据信息,保证了数据的一致性、完整性。 🎯

总结一下:

  1. 在业务层类或者接口上、或者方法上,添加@Transactional注解进行事务管理
  2. 开启事务管理的日志,观察信息

事务进阶

前面我们通过spring事务管理注解@Transactional已经控制了业务层方法的事务。接下来我们要来详细的介绍一下@Transactional事务管理注解的使用细节。我们这里主要介绍@Transactional注解当中的两个常见的属性:

  1. 异常回滚的属性:rollbackFor
  2. 事务传播行为:propagation

我们先来学习下rollbackFor属性。

rollbackFor

我们在之前编写的业务方法上添加了@Transactional注解,来实现事务管理。

@Transactional
@Override
public void save(Emp emp) {
    //1.补全基础属性
    emp.setCreateTime(LocalDateTime.now());
    emp.setUpdateTime(LocalDateTime.now());
    //2.保存员工基本信息
    empMapper.insert(emp);
	
    int i = 1/0;
	
    //3. 保存员工的工作经历信息 - 批量
    Integer empId = emp.getId();
    List<EmpExpr> exprList = emp.getExprList();
    if(!CollectionUtils.isEmpty(exprList)){
        exprList.forEach(empExpr -> empExpr.setEmpId(empId));
        empExprMapper.insertBatch(exprList);
    }
}

以上业务功能save方法在运行时,会引发除0的算术运算异常(运行时异常),出现异常之后,由于我们在方法上加了@Transactional注解进行事务管理,所以发生异常会执行rollback回滚操作,从而保证事务操作前后数据是一致的。

下面我们在做一个测试,我们修改业务功能代码,在模拟异常的位置上直接抛出Exception异常(编译时异常)

@Transactional
@Override
public void save(Emp emp) {
    //1.补全基础属性
    emp.setCreateTime(LocalDateTime.now());
    emp.setUpdateTime(LocalDateTime.now());
    //2.保存员工基本信息
    empMapper.insert(emp);
	
    //模拟:异常发生
    if(true){
        throw new Exception("出现异常了~~~");
    }
	
    //3. 保存员工的工作经历信息 - 批量
    Integer empId = emp.getId();
    List<EmpExpr> exprList = emp.getExprList();
    if(!CollectionUtils.isEmpty(exprList)){
        exprList.forEach(empExpr -> empExpr.setEmpId(empId));
        empExprMapper.insertBatch(exprList);
    }
}










 
 
 









说明:在service中向上抛出一个Exception编译时异常之后,由于是controller调用service,所以在controller中要有异常处理代码,此时我们选择在controller中继续把异常向上抛。

重新启动服务后,打开Apifox进行测试,请求添加员工的接口:

image-20231203184536831
image-20231203184536831

通过Apifox返回的结果,我们看到抛出异常了。然后我们在回到IDEA的控制台来看一下。

image-20231203184739398
image-20231203184739398

我们看到数据库的事务居然提交了,并没有进行回滚。

通过以上测试可以得出一个结论:默认情况下,只有出现RuntimeException(运行时异常)才会回滚事务。

假如我们想让所有的异常都回滚,需要来配置@Transactional注解当中的rollbackFor属性,通过rollbackFor这个属性可以指定出现何种异常类型回滚事务。

@Transactional(rollbackFor = Exception.class)
@Override
public void save(Emp emp) throws Exception {
    //1.补全基础属性
    emp.setCreateTime(LocalDateTime.now());
    emp.setUpdateTime(LocalDateTime.now());
    //2.保存员工基本信息
    empMapper.insert(emp);
	
    //int i = 1/0;
    if(true){
        throw new Exception("出异常啦....");
    }
	
    //3. 保存员工的工作经历信息 - 批量
    Integer empId = emp.getId();
    List<EmpExpr> exprList = emp.getExprList();
    if(!CollectionUtils.isEmpty(exprList)){
        exprList.forEach(empExpr -> empExpr.setEmpId(empId));
        empExprMapper.insertBatch(exprList);
    }
}
 





















接下来我们重新启动服务,测试新增员工的操作:

image-20231203184536831
image-20231203184536831

控制台日志,可以看到因为出现了异常又进行了事务回滚

image-20231203185243709
image-20231203185243709

结论

  • 在Spring的事务管理中,默认只有运行时异常 RuntimeException才会回滚。
  • 如果还需要回滚指定类型的异常,可以通过rollbackFor属性来指定。

总结

课堂作业

  1. 事务的传播行为,你了解哪些知识点?在什么场景下会使用到 REQUIRES_NEW 🎤

2.3 事物传播行为之案例

事物传播行为之案例

接下来我们就通过一个案例来演示下事务传播行为propagation属性的使用。

**需求:**在新增员工信息时,无论是成功还是失败,都要记录操作日志。

步骤:

  1. 准备日志表 emp_log、实体类EmpLog、Mapper接口EmpLogMapper
  2. 在新增员工时记录日志

代码操作

准备工作:

1). 创建数据库表 emp_log 日志表:

-- 创建员工日志表
create table emp_log(
    id int unsigned primary key auto_increment comment 'ID, 主键',
    operate_time datetime comment '操作时间',
    info varchar(2000) comment '日志信息'
) comment '员工日志表';

2). 引入资料中提供的实体类:EmpLog

@Data
@NoArgsConstructor
@AllArgsConstructor
public class EmpLog {
    private Integer id; //ID
    private LocalDateTime operateTime; //操作时间
    private String info; //详细信息
}

3). 引入资料中提供的Mapper接口:EmpLogMapper

@Mapper
public interface EmpLogMapper {
	//插入日志
    @Insert("insert into emp_log (operate_time, info) values (#{operateTime}, #{info})")
    public void insert(EmpLog empLog);
}

4). 引入资料中提供的业务接口:EmpLogService

public interface EmpLogService {
	//记录新增员工日志
    public void insertLog(EmpLog empLog);
}

5). 引入资料中提供的业务实现类:EmpLogServiceImpl

@Service
public class EmpLogServiceImpl implements EmpLogService {

    @Autowired
    private EmpLogMapper empLogMapper;

    @Transactional
    @Override
    public void insertLog(EmpLog empLog) {
        empLogMapper.insert(empLog);
    }
}

总结

课堂作业

  1. 结合上述步骤,完成代码,体会事务传播行为的应用场景!!🎤

2.4 事务四大特性 🍐

事务四大特性

面试题:事务有哪些特性?

  • 原子性(Atomicity):事务是不可分割的最小单元,要么全部成功,要么全部失败。
  • 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
  • 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
  • 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。

事务的四大特性简称为:ACID

  • 原子性(Atomicity) :原子性是指事务包装的一组sql是一个不可分割的工作单元,事务中的操作要么全部成功,要么全部失败。

  • 一致性(Consistency):一个事务完成之后数据都必须处于一致性状态。

    • 如果事务成功的完成,那么数据库的所有变化将生效。
    • 如果事务执行出现错误,那么数据库的所有变化将会被回滚(撤销),返回到原始状态。
  • 隔离性(Isolation):多个用户并发的访问数据库时,一个用户的事务不能被其他用户的事务干扰,多个并发的事务之间要相互隔离。

    • 一个事务的成功或者失败对于其他的事务是没有影响。
  • 持久性(Durability):一个事务一旦被提交或回滚,它对数据库的改变将是永久性的,哪怕数据库发生异常,重启之后数据亦然存在。

3. 文件上传 🚩 ✏️ 🍐

3.1 本地文件上传-4

文件上传

  1. 新增员工功能中,还存在一个问题:没有头像(图片缺失),怎么解决?
image-20221216200653717
image-20221216200653717

使用文件上传,将图片存储到本地或者阿里云服务器中

需求1:下面我们来验证:删除form表单中enctype属性值,会是什么情况?

  1. 在IDEA中直接使用浏览器打开upload.html页面
image-20221216210643628
image-20221216210643628
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>上传文件</title>
</head>
<body>

    <form action="/upload" method="post" enctype="multipart/form-data">
        姓名: <input type="text" name="username"><br>
        年龄: <input type="text" name="age"><br>
        头像: <input type="file" name="image"><br>
        <input type="submit" value="提交">
    </form>

</body>
</html>

接下来,创建后端ploadController,接收前端传过来的图片

UploadController代码:

@Slf4j
@RestController
public class UploadController {

    @PostMapping("/upload")
    public Result upload(String username, Integer age, MultipartFile image)  {
        log.info("文件上传:{},{},{}",username,age,image);
        return Result.success();
    }

}





 






本地存储方式的不足

image-20220904200320964
image-20220904200320964

如果直接存储在服务器的磁盘目录中,存在以下缺点:

  • 不安全:磁盘如果损坏,所有的文件就会丢失
  • 容量有限:如果存储大量的图片,磁盘空间有限(磁盘不可能无限制扩容)
  • 无法直接访问

通常有两种解决方案:

  • 自己搭建存储服务器,如:fastDFS 、MinIO 实用
  • 使用现成的云服务,如:阿里云,腾讯云,华为云简单

总结

课堂作业

  1. 前端上传文件必须满足哪3个要素
  2. 为什么要使用UUID算法给图片重新取名字?🎤
  3. 本地存储的缺点有哪些?🎤
  4. 结合UUID练习一下本地存储

3.2 阿里云OSS

阿里云OSS

阿里云是阿里巴巴集团旗下全球领先的云计算公司,也是国内最大的云服务提供商 。

image-20221229093412464
image-20221229093412464

云服务指的就是通过互联网对外提供的各种各样的服务,比如像:语音服务、短信服务、邮件服务、视频直播服务、文字识别服务、对象存储服务等等。

当我们在项目开发时需要用到某个或某些服务,就不需要自己来开发了,可以直接使用阿里云提供好的这些现成服务就可以了。比如:在项目开发当中,我们要实现一个短信发送的功能,如果我们项目组自己实现,将会非常繁琐,因为你需要和各个运营商进行对接。而此时阿里云完成了和三大运营商对接,并对外提供了一个短信服务。我们项目组只需要调用阿里云提供的短信服务,就可以很方便的来发送短信了。这样就降低了我们项目的开发难度,同时也提高了项目的开发效率。(大白话:别人帮我们实现好了功能,我们只要调用即可)

云服务提供商给我们提供的软件服务通常是需要收取一部分费用的。、

阿里云oss对象存储使用步骤

第三方服务使用的通用思路,我们做一个简单介绍之后,接下来我们就来介绍一下我们当前要使用的阿里云oss对象存储服务具体的使用步骤。

image-20221229112451120
image-20221229112451120

Bucket:存储空间是用户用于存储对象(Object,就是文件)的容器,所有的对象都必须隶属于某个存储空间。

下面我们根据之前介绍的使用步骤,完成准备工作:

  1. 注册阿里云账户(注册完成后需要实名认证)
  2. 注册完账号之后,就可以登录阿里云
image-20220904201839857
image-20220904201839857
  1. 通过控制台找到对象存储OSS服务
image-20220904201932884
image-20220904201932884

如果是第一次访问,还需要开通对象存储服务OSS

image-20220904202537579
image-20220904202537579
image-20220904202618423
image-20220904202618423
  1. 开通OSS服务之后,就可以进入到阿里云对象存储的控制台
image-20220904201810832
image-20220904201810832
  1. 点击左侧的 "Bucket列表",创建一个Bucket
image-20220904202235180
image-20220904202235180

大家可以参照"资料\04. 阿里云oss"中提供的文档,开通阿里云OSS服务。点击查看阿里云文档

总结

课堂作业

  1. 阿里云对象存储OSS是什么?有什么用途?🎤
  2. Bucket是什么意思?有什么作用?🎤
  3. 开通阿里云账号,然后创建一个Bucket存储文件。✏️

3.3 阿里OSS入门和集成-5

OSS入门和集成

需求1:创建Bucket后,传入一张本地图片至OSS,然后通过返回的链接,直接访问图片

需求2:集成OSS到项目中

需求1:测试阿里云API

核心步骤

  1. 创建测试工程,引入依赖
  2. 准备测试类
  3. 获取AccessKeyId以及配置工具类AliOSSUtils

参考文档官方open in new window

(1)创建测试工程,引入依赖

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.15.1</version>
</dependency>

需求2:集成OSS到项目中

阿里云oss对象存储服务的准备工作以及入门程序我们都已经完成了,接下来我们就需要在案例当中集成oss对象存储服务,来存储和管理案例中上传的图片。

image-20221229170235632
image-20221229170235632

在新增员工的时候,上传员工的图像,而之所以需要上传员工的图像,是因为将来我们需要在系统页面当中访问并展示员工的图像。而要想完成这个操作,需要做两件事:

  1. 需要上传员工的图像,并把图像保存起来(存储到阿里云OSS)
  2. 访问员工图像(通过图像在阿里云OSS的存储地址访问图像)
    • OSS中的每一个文件都会分配一个访问的url,通过这个url就可以访问到存储在阿里云上的图片。所以需要把url返回给前端,这样前端就可以通过url获取到图像。

集成OSS到项目中代码实现:

引入阿里云OSS上传文件工具类(由官方的示例代码改造而来) 👇 👇

com.xx.utils 工具包

@Component // 表明当前类需要被spring进行实例化,放到ioc容器中
public class AliOSSUtils {
    private String endpoint = "https://oss-cn-shanghai.aliyuncs.com";
    private String accessKeyId = "LTAI5t9MZK8iq5T2Av5GLDxX";
    private String accessKeySecret = "C0IrHzKZGKqU8S7YQcevcotD3Zd5Tc";
    private String bucketName = "web-framework01";

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

        // 避免文件覆盖
        String originalFilename = multipartFile.getOriginalFilename();
        String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));

        //上传文件到 OSS
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        ossClient.putObject(bucketName, fileName, inputStream);

        //文件访问路径
        String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName;

        // 关闭ossClient
        ossClient.shutdown();
        return url;// 把上传到oss的路径返回
    }
}

总结

课堂作业

  1. 🚩 注册阿里云账号,开通Oss服务,完成上述服务,并且能够使用清晰的描述出集成oss流程。(可以使用流程图工具进行绘制)

4. 员工模块

4.1 删除员工-6

删除员工

image-20231206144352346

当我们勾选列表前面的复选框,然后点击 "批量删除" 按钮,就可以将这一批次的员工信息删除掉了。也可以只勾选一个复选框,仅删除一个员工信息。

问题:我们需要开发两个功能接口吗?一个删除单个员工,一个删除多个员工

答案:不需要。 只需要开发一个功能接口即可(删除多个员工包含只删除一个员工)

接口文档

删除员工

  • 基本信息

    • 请求路径:/emps
    • 请求方式:DELETE
    • 接口描述:该接口用于批量删除员工的数据信息
  • 请求参数

    • 参数格式:查询参数
    • 参数说明:
    参数名类型示例是否必须备注
    ids数组 array1,2,3必须员工的id数组
    • 请求参数样例:
    /emps?ids=1,2,3
    
  • 响应数据

    • 参数格式:application/json
    • 参数说明:
    参数名类型是否必须备注
    codenumber必须响应码,1 代表成功,0 代表失败
    msgstring非必须提示信息
    dataobject非必须返回的数据
    • 响应数据样例:
    {
        "code":1,
        "msg":"success",
        "data":null
    }
    

代码操作

image-20231206144546008

Controller接收参数

EmpController 中增加如下方法 delete ,来执行批量删除员工的操作。

方式一:在Controller方法中通过数组来接收

多个参数,默认可以将其封装到一个数组中,需要保证前端传递的参数名 与 方法形参名称保持一致。

/**
* 批量删除员工
*/
@DeleteMapping
public Result delete(Integer[] ids){
    log.info("批量删除部门: ids={} ", Arrays.asList(ids));
    return Result.success();
}

方式二:在Controller方法中通过集合来接收

也可以将其封装到一个List<Integer> 集合中,如果要将其封装到一个集合中,需要在集合前面加上 @RequestParam 注解。

/**
* 批量删除员工
*/
@DeleteMapping
public Result delete(@RequestParam List<Integer> ids){
    log.info("批量删除部门: ids={} ", ids);
    empService.deleteByIds(ids);
    return Result.success();
}

两种方式,选择其中一种就可以,我们一般推荐选择集合,因为基于集合操作其中的元素会更加方便。