项目环境搭建
- 资料
- 接口文档在Day10
三层架构
- controller : 负责接受请求 ,处理响应
- service : 逻辑处理 ,为了增强程序灵活性 ,方便层与层之间解耦 ,我们会采用面向接口的方式 ,还需要准备业务层的接口 ,mapper本身就是一个接口
- mapper : 数据访问操作
环境搭建
部门实体类
员工实体类
数据库和实体类字段名一致问题
第一种解决办法
-
实体类当中的属性与表中的字段是一一对应的 ,在实体类中 ,采用的是驼峰命名 ,表中的字段是下划线风格
-
也就是在配置文件里面开启驼峰命名法
mybatis默认是属性名和数据库字段名一一对应的,即
数据库表列:user_name
实体类属性:user_name
但是java中一般使用驼峰命名
数据库表列:user_name
实体类属性:userName
在Springboot中,可以通过设置map-underscore-to-camel-case属性为true来开启驼峰功能。
第二种解决办法
1、@TableField(value = "email")//指定数据库表中字段名
如果数据库和实体类的字段名不一致,可以使用@TableField注解指定数据库表中字段名。
2、@TableField(exist = false)//数据库表中不存在的数据,在实体类中指定。
如果数据库表中不存在字段,在实体类中使用@TableField注解指定。
例如:数据库表中没有address字段,可以在该字段上方使用@TableField(exist = false)来指定。
3、@TableField(select = false)//查询时不返回该字段的值
如果不想被查询出来该字段,可以使用@TableField注解来隐藏该字段的查询结果。
例如:不想被查出来password字段的值,就可以使用@TableField注解。
开发规范
-
restful是一种软件架构风格 是一种约定 ,不是规定 ,可以打破
-
模块中功能通常使用复数 ,也就是加s的格式来描述 ,表示此类资源 ,而非单个资源 ,比如 : users ,emps ,books
-
返回格式
开发流程
部门管理
查询部门
- 流程
- 以后调试不用往控制台sout ,直接可以使用日志
问题代码 : 下面的代码使用put ,delete ,get等方法都可以访问 ,不符合我们的要求
-
@RestController里面有@ResponseBody注解
代码改进
- 因为controller需要调用service ,所以我们直接面向接口编程 ,注入一个部门Service的接口对象
前后端联调
- 对于前端的请求 ,URL里面的api ,就表示要访问Tomcat服务器的8080端口 ,这个是前端规定好的
- 也就是如果我们修改了后端的接口 ,那么前端就不会正常访问到接口 ,这都源于对于接口文档的遵守
删除部门
mapper接口中的方法
新增部门
- 注意补充属性 ,前端在页面中需要用户输入的是部门名称 ,但是其实还返回了时间数据 ,也就是需要我们来补充时间属性 ,接受时间参数
DeptServiceImpl.add()
- dept表有四个字段 ,id不需要插入 ,加入约束之后 ,出现蓝色标识
- 表中最后那个字段是要反馈给前端页面展示的 ,最后操作时间
- dept表中name字段 ,name’也是一个key
- 注意新增部门的sql ,传过来的是对象 ,但是sql中的参数可以直接写属性名字
修改部门
- 还是注意sql语句的写法 ,传过来的是部门对象 ,但是可以直接写对象里面的属性
员工管理
分页查询
select * from emp limit 0,5;
0不是页数 ,是根据页数算出来的
表示从原数据库的第一页的第0条数据(索引为1)开始查 ,一共返回5条数据
-
起始索引计算公式 起始索引 = (页码-1)*每页展示记录数
-
前端要进行分页查询 ,返回页码和每页记录数 ,后端需要返回结果 ,结果封装在实体类里面 ,
- 注意pageBean实体类的属性名字要和接口文档对应 ,因为这个属性名是要交给前端解析的 ,他和其他的实体类不同 ,这个实体类是要被返回给前端的
mapper
controller
contoller层加上getMapping ,注意默认参数可以使用@requestParms(default指定)
service
插件 PageHelper
- 引入依赖
<!--pageHelper分页查询依赖-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
mapper
service
controller不用改
条件分页查询
- 上面在sql中使用了concat连接字符串 ,目的是将来传参的时候还可以使用#{}
controller
时间类型用的是LocalDate ,注解是@LocalDateFormat ,后面的格式pattern是接口文档中给出的
mapper
service ,注意Short大写
删除员工
- 批量删除使用的是in关键字
mapper.xml
新增员工
- 有一条要求就是默认密码是123456 ,这个默认密码我们不需要前端传递 ,只需要在数据库写个默认值就可以了
- 接口文档中的规定 ,以json格式传递给后端
- 思路
- 注意事项
页面上的归属部门 ,是前端根据后端反馈的数据查询出来的 ,我们可以看到刷新页面 ,出现两个请求 ,第二个depts就是前端请求查看部门
文件上传
本地存储
步骤
- 前端三要素里面的enctype是传输文件的格式 ,普通默认的编码格式是不适合传输大型二进制数据
- 前端页面
-
注意表单项上的名称要和方法中形参的名称一致才可以正确接收 ,但是如果表单项上的名字和方法中形参名字不一样 ,可以加一个注解
-
文件上传 ,提交方式是post
-
后台接收文件使用api :MultiPartFile
-
MultiPartFile中的方法
debug断点调试
- 第一步 : 在要断点调试的地方打上断点
- 第二步 : 启动debug
- 第三步 : 打开网页选择文件并且点击提交
- 第四步 : 后台服务器已经接收到前端请求并出现调试页面
- 页面反馈三个临时文件 ,因为表单中就有三项 ,所以这里也是三个临时文件
- 第五步 : 放行
- 只要这次请求响应完成 ,临时文件会被自动删除 ,所以除了接收文件 ,我们还需要把文件保存起来,保存就需要用到api
- 规定文件存储路径 ,注意后面的两条杠
@Slf4j
@RestController
public class UploadController {
@PostMapping("/upload")
public Result upload(String username , Integer age , MultipartFile image) throws Exception {
log.info("文件上传:{},{},{}",username,age,image);
//获取原始文件名
String originalFilename = image.getOriginalFilename();
//将文件存储在服务器的磁盘目录中,后面需要指定文件名
image.transferTo(new File("D:\\testCode\\image\\"+originalFilename));
return Result.success();
}
}
代码演示
- 调用api ,transferTo ,会把接受到的磁盘文件转存到某一个磁盘文件里面 ,里面需要什么对象就给什么对象
- postman测试文件上传 ,选择file
生成随机文件名(UUID)
- 方式一
public String file( MultipartFile file, HttpServletRequest request) throws IOException {
//获取文件保存位置的的绝对路径,实际开发中,建议直接使用一个服务器的绝对地址
String realPath = request.getServletContext().getRealPath("/WEB-INF/upload/");
String originalFilename = file.getOriginalFilename();
//获取上传文件的后缀名
String substring = originalFilename.substring(originalFilename.lastIndexOf("."));
// 获取服务器中存储的文件名
String s = UUID.randomUUID().toString().replaceAll("-", "") + substring;
// 创建文件实例
File file1 = new File(realPath, s);
// 如果文件目录不存在,创建目录
if(file1.getParentFile().exists()){
file1.getParentFile().mkdirs();
System.out.println("创建目录"+file1);
}
// 写入文件
file.transferTo(file1);
- 方式二
@Slf4j
@RestController
public class UploadController {
@PostMapping("/upload")
public Result upload(String username , Integer age , MultipartFile image) throws Exception {
log.info("文件上传:{},{},{}",username,age,image);
//获取原始文件名
String originalFilename = image.getOriginalFilename();
//构造唯一的文件名
//表示先获取文件名 ,然后找到最后一个点所处的位置(是一个索引),因为最后一个点后面是文件的扩展名
// ,然后从这个地方开始截取,直到字符串的尾部
int index = originalFilename.lastIndexOf(".");
String extname = originalFilename.substring(index);
String newFileName = UUID.randomUUID().toString()+extname;
log.info("获取到的新的文件名: {}",newFileName);
//将文件存储在服务器的磁盘目录中,后面需要指定文件名
image.transferTo(new File("D:\\testCode\\image\\"+newFileName));
return Result.success();
}
}
文件大小过大报错
- 当上传一个超过1兆的图片时候发现报错
- 解决办法 : 在yml文件中配置
问题及解决
-
本地存储的图片没办法在网页上被访问到
-
本地中保存大量图片 ,占据很大空间
-
本地磁盘如果坏了 ,数据全部丢失
-
可以自己搭建文件存储服务 ,FastDFS分布式文件系统 ,MinIO对象存储服务搭建集群解决问题
-
云服务 : 阿里云 腾讯云 百度云 华为云
完整代码
upload.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>上传文件</title>
</head>
<body>
<!--action指的是表单往哪里提交 ,method指的是表单的提交方式是post ,enctype是来设置表单的编码格式-->
<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>
//UploadController
import com.itheima.pojo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.util.UUID;
@Slf4j
@RestController
public class UploadController {
//本地存储
@PostMapping("/upload")
public Result upload(String username , Integer age , MultipartFile image) throws Exception {
log.info("文件上传:{},{},{}",username,age,image);
//获取原始文件名
String originalFilename = image.getOriginalFilename();
//构造唯一的文件名
//表示先获取文件名 ,然后找到最后一个点所处的位置(是一个索引),因为最后一个点后面是文件的扩展名
// ,然后从这个地方开始截取,直到字符串的尾部
int index = originalFilename.lastIndexOf(".");
String extname = originalFilename.substring(index);
String newFileName = UUID.randomUUID().toString()+extname;
log.info("获取到的新的文件名: {}",newFileName);
//将文件存储在服务器的磁盘目录中,后面需要指定文件名
image.transferTo(new File("D:\\testCode\\image\\"+newFileName));
return Result.success();
}
}
阿里云OSS
步骤
- 通用思路
- 使用步骤
AccessKey ID
LTAI5t73p3RUbGNX8yyMQEPG
AccessKey Secret
9MnecnXDtSafObvshHq1ddDBZb6l9a
- 获取AccessKey并创建
- 获取SDK文档 ,点击SDK示例
- 复制代码到自己的项目里面
- 节点地址
配置访问凭证
- 配置长期访问凭证
集成
- 给工具类上方添加@Component注解
utils.AliOSSUtils
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.UUID;
/**
* 阿里云 OSS 工具类
*/
@Component
public class AliOSSUtils {
private String endpoint = "https://oss-cn-beijing.aliyuncs.com";
private String accessKeyId = "LTAI5t73p3RUbGNX8yyMQEPG";
private String accessKeySecret = "9MnecnXDtSafObvshHq1ddDBZb6l9a";
private String bucketName = "web-tliasbucket";
/**
* 实现上传图片到OSS
*/
public String upload(MultipartFile file) throws IOException {
// 获取上传的文件的输入流
InputStream inputStream = file.getInputStream();
// 避免文件覆盖
String originalFilename = file.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的路径返回
}
}
- 拼接访问路径
//文件访问路径
String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName;
//就是加上bucket名字 ,后面再跟上文件的名称
- 接口文档中上传文件参数信息
- 注意方法的形参名字要和接口文档上面的参数名称image保持一致
UploadController
@Autowired
private AliOSSUtils aliOSSUtils;
//阿里云存储
@PostMapping("/upload")
public Result upload(MultipartFile image) throws Exception {
log.info("文件上传, 文件名{}",image.getOriginalFilename());
//调用阿里云OSS工具类进行文件上传
String url = aliOSSUtils.upload(image);
log.info("文件上传完成 ,文件访问的url: {}",url);
return Result.success(url);
}
- postman测试
- 前后端联调
选择图片后 ,自动回显
修改员工
流程
- 流程
点击编辑之后 ,先要通过这条数据的id将结果查询出来 ,然后展示在表单页面上 ,这个过程就叫查询回显
修改数据之后 ,需要更新数据库信息
数据回显
修改员工
-
补充信息如下
-
更新员工信息的时候CreateTime不需要更新 ,只需要更新UpdateTime
-
动态sql ,只需要更新被修改过的字段值 ,没有修改的就不需要更新
- 这里我们没有对gender判断是不是空字符串 ,是因为Gender不可能为空字符串 ,他是一个Short类型
- mybatis动态sql使用xml文件实现
<!-- 修改 -->
<update id="update">
update emp
<set>
<if test="username != null and username != ''">
username = #{username},
</if>
<if test="password != null and password != ''">
password = #{password},
</if>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="gender != null">
gender = #{gender},
</if>
<if test="image != null and image != ''">
image = #{image},
</if>
<if test="job != null">
job = #{job},
</if>
<if test="entrydate != null">
entrydate = #{entrydate},
</if>
<if test="deptId != null">
dept_id = #{deptId},
</if>
<if test="updateTime != null">
update_time = #{updateTime}
</if>
</set>
where id = #{id}
</update>
- 测试
配置文件
properties
yml
@ConfigurationProperties
- 实现配置类中的属性对实体类实现自动注入 ,但前提是实体类的属性名和配置文件中的后缀名保持一致
- 还需要指定前缀 ,在@ConfigurationProperties(prefix=“aliyun.oss”)注解里指定