一. SpringBoot静态资源映射
1. 为什么要进行静态资源映射?
1.用于在Springboot项目中, 默认静态资源的存放目录为 :
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"
而在我们的项目中静态资源存放在
backend, front 目录中, 那么这个时候要想通过路径访问到静态资源, 就需要设置静态资源映射
2.在使用SSM开发时,SpringMVC拦截了静态资源,根据/pages/book.
2. 如何进行静态资源映射?
继承WebMvcConfigurationSupport,重写addResourceHandlers方法
代码如下:
@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
/**
* 设置静态资源映射
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("开始进行静态资源映射...");
//请求访问路径中带有/backend/** ,就会映射到类路径下的(也就是resources下) /backend/
registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
//请求访问路径中带有/front/** ,就会映射到类路径下的(也就是resources下) /front/
registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
}
}
二、登录逻辑分析
处理逻辑如下:
①. 将页面提交的密码password进行md5加密处理, 得到加密后的字符串
②. 根据页面提交的用户名username查询数据库中员工数据信息
③. 如果没有查询到, 则返回登录失败结果
④. 密码比对,如果不一致, 则返回登录失败结果
⑤. 查看员工状态,如果为已禁用状态,则返回员工已禁用结果
⑥. 登录成功,将员工id存入Session, 并返回登录成功结果
三、退出登录分析
//清理Session中保存的当前登录员工的id
request.getSession().removeAttribute("employee");
return R.success("退出成功");
四、拦截器拦截请求检查请求来源
现状:
用户如果不登录,直接访问系统首页面,照样可以正常访问。
要解决的问题:
只有登录成功后才可以访问系统中的页面,如果没有登录, 访问系统中的任何界面都直接跳转到登录页面。
具体实现逻辑:
A. 获取本次请求的URI
B. 判断本次请求, 是否需要登录, 才可以访问
C. 如果不需要,则直接放行
D. 判断登录状态,如果已登录,则直接放行
E. 如果未登录, 则返回未登录结果
代码实现:
urlPatterns= "/*" 表示拦截所有请求
AntPathMatcher Spring中提供的路径匹配器 ;
1. 定义登录校验过滤器
自定义一个过滤器 LoginCheckFilter 并实现 Filter 接口,在doFilter方法中完成校验的逻辑。
/**
* 检查用户是否已经完成登录
*/
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter{
//路径匹配器,支持通配符
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//1、获取本次请求的URI
String requestURI = request.getRequestURI();// /backend/index.html
log.info("拦截到请求:{}",requestURI);
//定义不需要处理的请求路径
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**",
"/common/**",
"/user/sendMsg",
"/user/login",
"/doc.html",
"/webjars/**",
"/swagger-resources",
"/v2/api-docs"
};
//2、判断本次请求是否需要处理
boolean check = check(urls, requestURI);
//3、如果不需要处理,则直接放行
if(check){
filterChain.doFilter(request,response);
return;
}
//4-1、判断登录状态,如果已登录,则直接放行
if(request.getSession().getAttribute("employee") != null){
log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
Long empId = (Long) request.getSession().getAttribute("employee");
BaseContext.setCurrentId(empId);
filterChain.doFilter(request,response);
return;
}
//4-2、判断登录状态,如果已登录,则直接放行
if(request.getSession().getAttribute("user") != null){
Long userId = (Long) request.getSession().getAttribute("user");
BaseContext.setCurrentId(userId);
filterChain.doFilter(request,response);
return;
}
//5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
return;
}
/**
* 路径匹配,检查本次请求是否需要放行
* @param urls
* @param requestURI
* @return
*/
public boolean check(String[] urls,String requestURI){
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if(match){
return true;
}
}
return false;
}
}
2. 开启组件扫描
需要在引导类上, 加上Servlet组件扫描的注解(@ServletComponentScan), 来扫描过滤器配置的@WebFilter注解
@Slf4j
@SpringBootApplication
@ServletComponentScan
public class ReggieApplication {
。。。
}
@ServletComponentScan 的作用:
在SpringBoot项目中, 在引导类/配置类上加了该注解后, 会自动扫描项目中(当前包及其子包下)的
@WebServlet , @WebFilter , @WebListener 注解, 自动注册Servlet的相关组件 ;
五、全局异常处理
异常处理关键字:try、catch、finally、throw、throws
全局异常处理的作用:可以将异常统一进行处理,不需要在可能发生异常的地方单独处理异常了。
1. 全局处理异常实现方法
1. 定义一个全局异常处理器
2. 在异常处理器上加上注解 @ControllerAdvice,可以通过属性annotations指定拦截哪一类的Controller方法。
3. 在异常处理器的方法上加上注解@ExceptionHandler 来指定拦截的是那一类型的异常。
- 代码
/**
* 全局异常处理
*/
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
/**
* 异常处理方法
* @return
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
log.error(ex.getMessage());
if(ex.getMessage().contains("Duplicate entry")){
String[] split = ex.getMessage().split(" ");
String msg = split[2] + "已存在";
return R.error(msg);
}
return R.error("未知错误");
}
@ExceptionHandler(CustomException.class)
public R<String> exceptionHandler(CustomException exception){
log.info(exception.getMessage());
return R.error(exception.getMessage());
}
}
注意:
@ControllerAdvice : 指定拦截那些类型的控制器;
@ResponseBody: 将方法的返回值 R 对象转换为json格式的数据, 响应给页面;
上述使用的两个注解, 也可以合并成为一个注解 @RestControllerAdvice
2. 自定义异常
自定义异常就是在遇到业务参数、操作异常的情况下,可以抛出我们自定义的异常类 抛出自定义异常后,会被异常处理器捕获,我们只需要在异常处理器中捕获这一类的异常,然后给页面返回对应的提示信息即可。
自定义异常步骤
1. 创建自定义异常
2. 在GlobalExceptionHandler中处理自定义异常
- 创建自定义异常
public class CustomException extends RuntimeException {
/**
* 自定义业务异常类
*/
public CustomException(String message){
super(message);
}
}
自定义异常一般
- 在全局异常处理器中增加方法,用于捕获我们自定义的异常 CustomException
/**
* 全局异常处理
*/
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
/**
* 自定义异常处理方法
* @param exception
* @return
*/
@ExceptionHandler(CustomException.class)
public R<String> exceptionHandler(CustomException exception){
log.info(exception.getMessage());
return R.error(exception.getMessage());
}
}
六、 Mybatis-plus实现分页
MybatisPlus要实现分页功能, 就需要用到MybatisPlus中提供的分页插件,
要使用分页插件,就要在配置类中声明分页插件的bean对象。
1.分页插件配置
导入依赖
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
2.分页查询实现
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
log.info("page = {},pageSize = {},name = {}" ,page,pageSize,name);
//构造分页构造器
Page pageInfo = new Page(page,pageSize);
//构造条件构造器
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper();
//添加过滤条件
queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
//添加排序条件
queryWrapper.orderByDesc(Employee::getUpdateTime);
//执行查询
employeeService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
}
分页类:Page
七、JavaScript处理Long型数字只能精确到前16位
JavaScript处理Long型数字只能精确到前16位,多出来的部分就会丢失精度。
1. 解决方案
只需要让js处理的ID数据类型为字符串类型即可, 这样就不会损失精度了。
由于在SpringMVC中, 将Controller方法返回值转换为json对象, 是通过jackson来实现的,
涉及到SpringMVC中的一个消息转换器MappingJackson2HttpMessageConverter,
所以我们要解决这个问题, 就需要对该消息转换器的功能进行拓展。
具体实现步骤:
- 提供对象转换器JacksonObjectMapper,基于Jackson进行Java对象到json数据的转换
- 在WebMvcConfig配置类中扩展Spring mvc的消息转换器,在此消息转换器中使用提供的对象 转换器进行Java对象到json数据的转换
对象转换器JacksonObjectMapper
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(BigInteger.class, ToStringSerializer.instance)
.addSerializer(Long.class, ToStringSerializer.instance)
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}
注意:
该自定义的对象转换器, 指定了在进行json数据序列化及反序列化时, LocalDateTime、LocalDate、LocalTime的
处理方式, 以及BigInteger及Long类型数据,直接转换为字符串。
在WebMvcConfig中重写方法extendMessageConverters
@Slf4j
@Configuration
@EnableSwagger2
@EnableKnife4j
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转化器...");
//创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用Jackson将java对象转换位json
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将上方消息转换器追加到mvc框架的转换器集合中
converters.set(0,messageConverter);
}
}
八、Mybatis Plus提供公共字段自动填充功能
1. 问题:
在新增员工时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工时需要设置修改时间、修改人等字段。
这些字段属于公共字段,也就是也就是在我们的系统中很多表中都会有这些字段,在很多业务方法中,都要去设置这些
字段。
2. 解决方案
Mybatis Plus公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,
使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。
3. 实现步骤
1、在实体类的属性上加入@TableField注解,指定自动填充的策略。
2、按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口。
-
实体类的属性上加入@TableField注解,指定自动填充的策略。
FieldFill.INSERT: 插入时填充该属性值 FieldFill.INSERT_UPDATE: 插入/更新时填充该属性值
-
按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现
MetaObjectHandler接口
/**
* 自定义元数据对象处理器
*/
@Component
@Slf4j
public class MyMetaObjecthandler implements MetaObjectHandler {
/**
* 插入操作,自动填充
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自动填充[insert]...");
log.info(metaObject.toString());
//第一个参数为列名,第二个参数为要设置的值
metaObject.setValue("createTime",LocalDateTime.now());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("createUser",BaseContext.getCurrentId());
metaObject.setValue("updateUser",BaseContext.getCurrentId());
}
/**
* 更新操作,自动填充
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充[update]...");
log.info(metaObject.toString());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("updateUser",BaseContext.getCurrentId());
}
}
4. ThreadLocal的使用
问题:
当我们对用户信息进行修改时,我们需要将修改人的信息保存到数据库,如果用Mybatis-plus的公共字段填充的话,
是无法从类中获取到session,并获取登录人ID值的,那么就需要用到ThreadLocal。
通过ThreadLocal解决该问题
ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,
ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,
而不会影响其它线程所对应的副本。
ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,
线程外则不能访问当前线程对应的值。
ThreadLocal常用方法:
A. public void set(T value) : 设置当前线程的线程局部变量的值
B. public T get() : 返回当前线程所对应的线程局部变量的值
C. public void remove() : 删除当前线程所对应的线程局部变量的值
客户端发送的每次http请求,对应的在服务端都会分配一个新的线程来处理,在处理过程中涉及到下
面类中的方法都属于相同的一个线程:
1). LoginCheckFilter的doFilter方法
2). EmployeeController的update方法
3). MyMetaObjectHandler的updateFill方法
那么我们就可以通过ThreadLocal的set方法,在Filter类中,进行设置登录的ID,
在MyMetaObjectHandler的updateFill方法中,通过ThreadLocal的get方法获取id,并将ID设置到公共字段。
5. ThreadLocal解决公共字段插入实现步骤
1). 编写BaseContext工具类,基于ThreadLocal封装的工具类
2). 在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
在放行前设置id
3). 在MyMetaObjectHandler的方法中调用BaseContext获取登录用户的id
- BaseContext工具类
/**
* 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
*/
public class BaseContext {
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
/**
* 设置值
* @param id
*/
public static void setCurrentId(Long id){
threadLocal.set(id);
}
/**
* 获取值
* @return
*/
public static Long getCurrentId(){
return threadLocal.get();
}
}
-
在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
...
-
在MyMetaObjectHandler的方法中调用BaseContext获取登录用户的id
/**
* 自定义元数据对象处理器
*/
@Component
@Slf4j
public class MyMetaObjecthandler implements MetaObjectHandler {
/**
* 插入操作,自动填充
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自动填充[insert]...");
log.info(metaObject.toString());
metaObject.setValue("createTime",LocalDateTime.now());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("createUser",BaseContext.getCurrentId());
metaObject.setValue("updateUser",BaseContext.getCurrentId());
}
/**
* 更新操作,自动填充
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充[update]...");
log.info(metaObject.toString());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("updateUser",BaseContext.getCurrentId());
}
}
九、文件上传下载
1. 文件上传
文件上传,也称为upload,是指将本地图片、视频、音频等文件上传到服务器上,可以供其他用户浏览或下载的过程。文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能。
| 文件上传时,对页面的form表单有如下要求:表单属性 | 取值 | 说明 |
|---|---|---|
| method | post | 必须选择post方式提交 |
| enctype | multipart/form-data | 采用multipart格式上传文件 |
| type | file | 使用input的file控件上传 |
1. 前端介绍
1). 简单html页面表单
<form method="post" action="/common/upload" enctype="multipart/form-data">
<input name="myFile" type="file" />
<input type="submit" value="提交" />
</form>
2). ElementUI中提供的upload上传组件
2. 服务端介绍
服务端要接收客户端页面上传的文件,通常都会使用Apache的两个组件:
commons-fileupload
commons-io
Spring框架在spring-web包中对文件上传进行了封装,
大大简化了服务端代码我们只需要在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件
服务端实现文件上传的步骤:
在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件
/**
* 文件上传
* @param file
* @return
*/
@PostMapping("/upload")
public R<String> upload(MultipartFile file){
System.out.println(file);
return R.success(fileName);
}
2. 文件下载
文件下载,也称为download,是指将文件从服务器传输到本地计算机的过程。通过浏览器进行文件下载,通常有两种表现形式:
1). 以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录
2). 直接在浏览器中打开
通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程
3. 服务端实现
1). application.yml
需要在application.yml中定义文件存储路径
reggie:\
path: D:\img\
2). 编写Controller
编写文件上传的方法, 通过MultipartFile类型的参数即可接收上传的文件,
方法形参的名称需要与页面的file域的name属性一致
上传逻辑:
1). 获取文件的原始文件名, 通过原始文件名获取文件后缀
2). 通过UUID重新声明文件名, 文件名称重复造成文件覆盖
3). 创建文件存放目录
4). 将上传的临时文件转存到指定位置
下载逻辑:
在 Controller 中定义方法download,并接收页面传递的参数name,
然后读取图片文件的数据,然后以流的形式写回浏览器。
1). 定义输入流,通过输入流读取文件内容
2). 通过response对象,获取到输出流
3). 通过response对象设置响应数据格式(image/jpeg)
4). 通过输入流读取文件数据,然后通过上述的输出流写回浏览器
5). 关闭资源
代码如下:
@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {
@Value("${reggie.path}")
private String basePath;
/**
* 文件上传
* @param file
* @return
*/
@PostMapping("/upload")
public R<String> upload(MultipartFile file){
//file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除
log.info(file.toString());
//原始文件名
String originalFilename = file.getOriginalFilename();
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
//使用UUID重新生成文件名,防止文件名称重复造成文件覆盖
String filename = UUID.randomUUID().toString() + suffix;
//创建一个目录对象
File dir = new File(basePath);
if(!dir.exists()){
//目录不存在,创建目录
dir.mkdirs();
}
try {
//将临时文件转存到指定位置
file.transferTo(new File(basePath + filename));
} catch (IOException e) {
e.printStackTrace();
}
return R.success(filename);
}
/**
* 文件下载
*/
@GetMapping("/download")
public void download(String name, HttpServletResponse response){
try {
//输入流,通过输入流读取文件内容
FileInputStream inputStream = new FileInputStream(basePath + name);
//输出流,通过输出流将文件写回浏览器
ServletOutputStream outputStream = response.getOutputStream();
//设置返回给客户端浏览器数据的类型
response.setContentType("image/jpeg");
byte[] bytes = new byte[1024];
int len = 0;
while ((len = inputStream.read(bytes)) != -1){
outputStream.write(bytes,0,len);
outputStream.flush();
}
//关闭流
outputStream.close();
inputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
十、 实体模型
| 实体模 型 | 描述 |
|---|---|
| DTO | Data Transfer Object(数据传输对象),一般用于展示层与服务层之间的数据传 输。 |
| Entity | 最常用实体类,基本和数据表一一对应,一个实体类对应一张表。 |
| VO | Value Object(值对象), 主要用于封装前端页面展示的数据对象,用一个VO对象 来封装整个页面展示所需要的对象数据 |
| PO | Persistant Object(持久层对象), 是ORM(Objevt Relational Mapping) 框架中Entity,PO属性和数据库中表的字段形成一一对应关系 |
十一、 SpringBoot开启事务
1. 什么时候开事务?
一个方法中有多次修改,保存,删除的操作时,就需要开事务,保证事务的原子性,一致性。
2. 开启事务步骤
1.在service方法中添加注解@Transactional来控制事务
2.在引导类上加注解 @EnableTransactionManagement
十二、 通用类
通用结果类,服务端响应的所有结果最终都会包装成此种类型返回给前端页面。
@Data
@ApiModel("返回结果")
public class R<T> implements Serializable {
private Integer code; //编码:1成功,0和其它数字为失败
private String msg; //错误信息
private T data; //数据
private Map map = new HashMap(); //动态数据
public static<T> R<T> success(T object){
R<T> r = new R<>();
r.data = object;
r.code = 1;
return r;
}
public static<T> R<T> error(String msg){
R<T> r = new R<T>();
r.code = 0;
r.msg = msg;
return r;
}
public R<T> add(String key, Object value) {
this.map.put(key, value);
return this;
}
}
十三、VO,BO,PO,DO,DTO区别
1. PO是持久化对象,对应的就是数据库中的一条记录,建包的时候都是写pojo 或者 entity
2. DTO是数据传输对象,就是你在controller层返回给前端的对象,也就是服务层和展示层之间传输的对象
3. VO是由前端将DTO进一步处理,然后响应到页面的数据对象
就相当于,我是前端,我从ajax回调函数中,拿到返回对象DTO,这个DTO对象中有很多数据,
我从里面拿我想要的数据在进行整合成vo对象,显示到页面上
4. BO是业务对象,也就是说你的每一个PO都代表不同的记录,BO相当于多个PO的集合
比如一个用户的访问服务器网页的ip地址是一个po,一个用户的用户信息是po,
BO相当于将这一个用户的所有行为信息和个人信息整合起来的业务对象,整合起来这样可以在代码中方便操作的。
问题就来了?是DTO和BO都可以整合PO对象,那么有什么区别呢?
解答:
BO中可能有一些我们不想返回给前端的用户隐私数据,
BO更多的是对内进行操作使用,而DTO就是将BO中不想给前端返回的数据字段进行删减,
然后就行返回,所以DTO是对外服务的,是数据传输对象。