SpringBoot2项目常用功能

SpringBoot2

1. 文件的上传

1.1 单文件上传
1.1.1 建立upload.html
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-type" content="text/html; charset=UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
    <title>单文件上传</title>
</head>
<body>
<form method="post" action="/upload" enctype="multipart/form-data">
    <input type="file" name="file"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>

需要注意的是:默认情况下,enctype的值是application/x-www-form-urlencoded,不能用于文件上传,只有使用了multipart/form-data,才能完整的传递文件数据。

application/x-www-form-urlencoded不是不能上传文件,是只能上传文本格式的文件,multipart/form-data是将文件以二进制的形式上传,这样可以实现多种类型的文件上传。

1.1.2 后端文件上传代码
 @PostMapping("/upload")
    @ResponseBody
    public String upload(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {//判断文件是否为空
            return "上传失败,请选择文件";
        }
        //file.getSize()判断文件大小	
        //获取文件名字
        String fileName = file.getOriginalFilename();
        //获取文件的后缀名
        String suffixName = fileName.substring(fileName.lastIndexOf("."));
        //重新随机取名
        fileName = UUID.randomUUID() + suffixName;       
        //存放文件位置
        String filePath = "/Users/itinypocket/workspace/temp/";
        //文件路径
        File dest = new File(filePath + fileName);
        try {
            file.transferTo(dest);
            LOGGER.info("上传成功");
            return "上传成功";
        } catch (IOException e) {
            LOGGER.error(e.toString(), e);
        }
        return "上传失败!";
    }
1.2 文件下载
 //文件下载相关代码
    @RequestMapping("/download")
    public String downloadFile(HttpServletRequest request, HttpServletResponse response) {
        String fileName = "1.text";// 设置文件名,根据业务需要替换成要下载的文件名
        if (fileName != null) {
            //设置下载文件路径
            String realPath = "G:/Java/learn/src/main/resources/static/";
            File file = new File(realPath , fileName);
            //文件存在的话返回“true”,否则就是返回“false”
            if (file.exists()) {
                // 设置强制下载不打开
                response.setContentType("application/force-download");
                // 设置文件名
                response.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
                byte[] buffer = new byte[1024];
                //声明文件流
                FileInputStream fis = null;
                //声明缓存流,BufferedInputStream继承于FileInputStream
                BufferedInputStream bis = null;
                try {
                    //创建文件输出流对象
                    fis = new FileInputStream(file);
                    //创建缓存输出流对象
                    bis = new BufferedInputStream(fis);

                    //getOutputStream()返回的对象,可以回送字符数据,也可以回送字节数据
                    OutputStream os = response.getOutputStream();

                    // read(buffer)方法返回的是实际读取的的字节数  ,将所读取的字节存到buffer中
                    int i = bis.read(buffer);
                    while (i != -1) {
                        //写入输出流
                        os.write(buffer, 0, i);
                        i = bis.read(buffer);
                    }
                    System.out.println("success");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //关闭文件流
                    if (bis != null) {
                        try {
                            bis.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    //关闭缓存流
                    if (fis != null) {
                        try {
                            fis.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        return null;
    }

前端代码

<a href="/download">下载</a>
1.3 多文件上传
@PostMapping("/uploadAll")
    @ResponseBody
    public String uploadAll(@RequestParam MultipartFile[] files){

        for (int i=0;i< files.length;i++){

            MultipartFile file=files[i];
            //判断上传的文件是不是空
            if(file.isEmpty()){
                return "第"+i+"文件上传失败";
            }
            //获取文件名字
            String fileName=file.getOriginalFilename();
            //获取文件名后缀
            String suffixName = fileName.substring(fileName.lastIndexOf("."));
            //重新获取一个文件名
            fileName= UUID.randomUUID()+suffixName;
            String filePath="G:/Java/learn/src/main/resources/static/";
            //储存路径加文件名得到绝对路径
            File dest = new File(filePath + fileName);
            try {
                //上传文件
                file.transferTo(dest);
                log.info("sdsdfs");

            } catch (IOException e) {

                log.error("上传异常");
                return "第"+i+"文件上传失败";

            }


        }

            return "上传成功";
    }

前端代码

<form method="post" enctype="multipart/form-data" action="/uploadAll">
    <input type="file" name="files">
    <input type="file" name="files">
    <input type="submit">
</form>

2. 全局异常配置

2.1 全局异常

@ControllerAdvice 如果是返回json数据 则用 RestControllerAdvice,就可以不加 @ResponseBody

捕获全局异常,处理所有不可知的异常

@ExceptionHandler(value=Exception.class)

处理异常类

 1 @RestControllerAdvice
 2 public class CustomExtHandler {
 3 
 4     private static final Logger LOG = LoggerFactory.getLogger(CustomExtHandler.class);
 5 
 6     
 7     //捕获全局异常,处理所有不可知的异常
 8     @ExceptionHandler(value=Exception.class)
 9     Object handleException(Exception e,HttpServletRequest request){
10         LOG.error("url {}, msg {}",request.getRequestURL(), e.getMessage()); 
11         Map<String, Object> map = new HashMap<>();
12             map.put("code", 100);
13             map.put("msg", e.getMessage());
14             map.put("url", request.getRequestURL());
15             return map;
16     }
17     
18 }
2.2 自定义异常类型

自定义异常类型MyException

复制代码

 1 /*
 2  * 功能描述: 建立自定义异常类 -- 继承运行异常最高类
 3  * 
 4  * */
 5 
 6 public class MyException extends RuntimeException{
 7     
 8     public MyException(String code, String msg){
 9         this.code = code;
10         this.msg = msg;
11     }
12     
13     private String code;
14     private String msg;
15     
16     public String getCode() {
17         return code;
18     }
19     public void setCode(String code) {
20         this.code = code;
21     }
22 
23     public String getMsg() {
24         return msg;
25     }
26 
27     public void setMsg(String msg) {
28         this.msg = msg;
29     }
30 
31 }

修改CustomExtHandler

1 @RestControllerAdvice
 2 public class CustomExtHandler {
 3 
 4     private static final Logger LOG = LoggerFactory.getLogger(CustomExtHandler.class);
 5 
 6     
 7     //捕获全局异常,处理所有不可知的异常
 8     @ExceptionHandler(value=Exception.class)
 9     Object handleException(Exception e,HttpServletRequest request){
10         LOG.error("url {}, msg {}",request.getRequestURL(), e.getMessage()); 
11         Map<String, Object> map = new HashMap<>();
12             map.put("code", 100);
13             map.put("msg", e.getMessage());
14             map.put("url", request.getRequestURL());
15             return map;
16     }
17 
18     /**
19      * 功能描述: 处理自定义异常类
20      * @return
21      * 
22      */
23     @ExceptionHandler(value = MyException.class)
24     Object handleMyException(MyException e, HttpServletRequest request){
25         //进行页面跳转
26 //        ModelAndView modelAndView = new ModelAndView();
27 //        modelAndView.setViewName("error.html");
28 //        modelAndView.addObject("msg", e.getMessage());
29 //        return modelAndView;
30         
31         //f返回json数据
32         Map<String, Object> map = new HashMap<>();
33         map.put("code", e.getCode());
34         map.put("msg", e.getMessage());
35         map.put("url", request.getRequestURL());
36         return map;
37     }
38 }

模拟一个异常实现页面跳转

1 /**
2  * 功能描述: 模拟自定义异常
3  * @return
4  */
5 @RequestMapping(value = "/api/v1/myext")  
6 public Object myext() {
7     throw new MyException("500", "my ext异常");
8 }
9     

最后梳理顺序:

首先,建立自定义异常类 MyException,继承于RuntimeException。如果出异常,在@RestControllerAdvice注释下的CustomExtHandler里面捕获,根据异常种类进行处理。

异常处理的官网地址: https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-error-handling

3. 过滤器

3.1 过滤器简介
  • 过滤器依赖Servlet容器,属于Servlet规范的一部分。
  • 在实现上基于Servlet容器的函数回调,可以对几乎所有请求进行过滤。
  • Filter的生命周期由Servlet容器管理。

过滤器执行流程
在这里插入图片描述

3.2 过滤器的作用和方法
3.2.1过滤器的作用
  • Filter使用户可以改变一个 request和修改一个response.
  • Filter 不是一个servlet,它不能产生一个response,它能够在一个request到达servlet之前预处理 request,也可以在离开 servlet时处理response.
  • 换种说法,filter其实是一个”servlet chaining”(servlet 链).
  • 通俗点说法filter相当于加油站,request是条路,response是条路,
  • 目的地是servlet,这个加油站设在什么地方对什么数据操作可以由你来控制。
3.2.2 过滤器的方法

init(FilterConfig fConfig)

  • init()方法用来初始化过滤器,可以在init()方法中获取Filter中的初始化参数。
@Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //用来获取Filter中初始化的参数
    }

doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

  • doFilter()方法完成过滤操作。当请求发过来的时候,过滤器将执行doFilter方法。
  • 在HttpServletRequest 执行doFilter()之前,根据需要检查 HttpServletRequest ,同时也可以修改HttpServletRequest 请求头和数据。
  • 在HttpServletResponse 执行doFilter()之后,根据需要检查 HttpServletResponse ,同时也可以修改HttpServletResponse响应头和数据。
 @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("进行过滤以及逻辑判断操作");
        filterChain.doFilter(servletRequest,servletResponse);
    }

destroy()

  • Filter对象创建后会驻留在内存,当web应用移除或服务器停止时调用destroy()方法进行销毁。在Web容器卸载 Filter 对象之前被调用。destroy()方法在Filter的生命周期中仅执行一次。通过destroy()方法,可以释放过滤器占用的资源。
@Override
    public void destroy() {
        //销毁Filter对象,Filter生命周期结束
    }
3.3 过滤器配置使用方式一
  1. 创建过滤器类,实现接口Filter,过滤全部请求
@Order(1) // 指定过滤器的执行顺序
@WebFilter(filterName = "CostTimeFilter", urlPatterns = "/*" , initParams = {
        @WebInitParam(name = "URL", value = "http://localhost:8080")})
public class CostTimeFilter implements Filter {
    private String url;
    /**
     * filter对象只会创建一次,init方法也只会执行一次。
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.url = filterConfig.getInitParameter("URL");
        System.out.println("Filter init...");
    }

    /**
     * 主要的业务代码编写方法
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    	long start = System.currentTimeMillis();
        System.out.println(start + " Filter doFilter before...");
        filterChain.doFilter(servletRequest, servletResponse);
    	long end = System.currentTimeMillis();
        System.out.println(end + "Filter doFilter after...");
    }

    /**
     * 在销毁Filter时自动调用。
     */
    @Override
    public void destroy() {
        System.out.println("Filter destroy...");
    }
}
  1. 控制器层请求
@RestController
public class IndexContoller {
    @RequestMapping("/query")
    public void query() throws Exception {
        System.out.println("IndexContoller query...");
    }
}
  1. 启动主类上加@ServletComponentScan注解
  • 注:@ServletComponentScan注解的含义在SpringBootApplication(启动类)上使用@ServletComponentScan注解后,Servlet、Filter(过滤器)、Listener(监听器)可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册,无需其他代码!
@SpringBootApplication
@ServletComponentScan // 扫描 Servlet 相关的组件
public class SpringBootFilterApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootFilterApplication.class, args);
    }
}
3.4 过滤器配置方式二
  1. 创建过滤器,实现Filter接口
public class CostTimeFilter implements Filter {
    private String url;
    /**
     * filter对象只会创建一次,init方法也只会执行一次。
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.url = filterConfig.getInitParameter("URL");
        System.out.println("Filter init...");
    }

    /**
     * 主要的业务代码编写方法
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    	long start = System.currentTimeMillis();
        System.out.println(start + " Filter doFilter before...");
        filterChain.doFilter(servletRequest, servletResponse);
    	long end = System.currentTimeMillis();
        System.out.println(end + "Filter doFilter after...");
    }

    /**
     * 在销毁Filter时自动调用。
     */
    @Override
    public void destroy() {
        System.out.println("Filter destroy...");
    }
}
  1. 配置过滤器
@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean registFilter() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new CostTimeFilter());
        registration.addUrlPatterns("/*");
        registration.setName("CostTimeFilter");
        registration.setOrder(1);
        return registration;
    }
}

4. 监听器

4.1监听器的作用

listener是servlet规范中定义的一种特殊类。用于监听servletContext、HttpSession和servletRequest等域对象的创建和销毁事件。监听域对象的属性发生修改的事件。用于在事件发生前、发生后做一些必要的处理。其主要可用于以下方面:1、统计在线人数和在线用户2、系统启动时加载初始化信息3、统计网站访问量4、记录用户访问路径。

4.2 常用的监听器有
  • ServletContextListener
  • HttpSessionListener
  • ServletRequestListener
4.3 监听器使用
4.3.1 通过代码方式注入过滤器
@Bean
public ServletListenerRegistrationBean servletListenerRegistrationBean(){
ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean();
servletListenerRegistrationBean.setListener(new IndexListener());
return servletListenerRegistrationBean;
}

IndexListener.Java监听类:

package com.example.Listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class IndexListener implements ServletContextListener{
@Override
public void contextDestroyed(ServletContextEvent arg0) {
System.out.println("IndexListener contextDestroyed method");
}
@Override
public void contextInitialized(ServletContextEvent arg0) {
System.out.println("IndexListener contextInitialized method");
}
}
4.3.2 通过注解方式注入过滤器

IndexListener2.Java监听类

package com.example.Listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class IndexListener2 implements ServletContextListener{
@Override
public void contextDestroyed(ServletContextEvent arg0) {
System.out.println("IndexListener2 contextDestroyed method");
}
@Override
public void contextInitialized(ServletContextEvent arg0) {
System.out.println("IndexListener2 contextInitialized method");
}
}

把注解加到入口处启动即可

@SpringBootApplication
@ServletComponentScan
public class SpringBootSimpleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootSimpleApplication.class, args);
}
}

5. 拦截器

5.1拦截器的作用

拦截器:Interceptor 在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。比如日志,安全等。一般拦截器方法都是通过动态代理的方式实现。可以通过它来进行权限验证,或者判断用户是否登陆,或者是像12306 判断当前时间是否是购票时间。

5.2拦截器的使用

自定义拦截器

实现接口HandlerInterceptor,HandlerInterceptor接口有三个方法:

  • preHandle():调用Controller某个方法之前
  • postHandler():Controller之后调用,视图渲染之前,如果控制器Controller出现异常,则不会执行该方法。
  • afterCompletion():不管有没有异常,这个方法都会被调用,用于资源清理
public class MyInterceptor implements HandlerInterceptor{

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println("==========jin  ru pre  handle ====");
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        
        System.out.println(username +","+password);
        
        if("xiongda".equals(username) && "123456".equals(password)) {
            return true;//放行
        }else {
            return false;//拦截
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("================= jin ru  after ===============");
    }

}

实现配置类

拦截器配置类class上使用要注解@Configuration,这里要实现接口WebMvcConfigurer。

@Configuration
public class WebMvcConfig  implements WebMvcConfigurer{

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor())
            .addPathPatterns("/**")//拦截所有请求
            .excludePathPatterns("/static/login.html");
    }      
}

6. 整合redis框架

6.1 redis简介
  • Redis是一个非关系型数据库,具有很高的存取性能,一般用作缓存数据库,减少正常存储数据库的压力。
  • Redis可以存储键与5种不同数据结构类型之间的映射,这5种数据结构类型分别为String(字符串)、List(列表)、Set(集合)、Hash(散列)和 Zset(有序集合)。
    在这里插入图片描述
6.2 RedisTemplate

Spring封装了RedisTemplate对象来进行对Redis的各种操作,它支持所有的Redis原生的api。RedisTemplate位于spring-data-redis包下。RedisTemplate提供了redis各种操作、异常处理及序列化,支持发布订阅,并对spring 3.1 cache进行了实现。
在这里插入图片描述

注意: RedisTemplate是一个key和value都是泛型的模板类,一般情况下key为String类型,如:RedisTemplate<String,Object>。

此外,如果没特殊情况,切勿定义成RedisTemplate<Object, Object>,否则根据里氏替换原则,使用的时候会造成类型错误 。

spring-data-redis针对jedis提供了如下功能:

  • 连接池自动管理,提供了一个高度封装的“RedisTemplate”类
  • 针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口
  • ValueOperations:简单K-V操作
  • SetOperations:set类型数据操作
  • ZSetOperations:zset类型数据操作
  • HashOperations:针对map类型的数据操作
  • ListOperations:针对list类型的数据操作

RedisTemplate中定义了对5种数据结构操作

redisTemplate.opsForValue();//操作字符串
redisTemplate.opsForHash();//操作hash
redisTemplate.opsForList();//操作list
redisTemplate.opsForSet();//操作set
redisTemplate.opsForZSet();//操作有序set
6.3 RedisTemplate常用方法

对key模糊查询

stringRedisTemplate.keys("KEY_CAMERA_STATUS_$*")
6.3.1 String类型

判断是否有key所对应的值,有则返回true,没有则返回false

redisTemplate.hasKey(key)

有则取出key值所对应的值

redisTemplate.opsForValue().get(key)

删除单个key值

redisTemplate.delete(key)

批量删除key

redisTemplate.delete(keys) //其中keys:Collection<K> keys

将当前传入的key值序列化为byte[]类型

redisTemplate.dump(key)

设置过期时间

public Boolean expire(String key, long timeout, TimeUnit unit) {
    return redisTemplate.expire(key, timeout, unit);
 }
 public Boolean expireAt(String key, Date date) {
    return redisTemplate.expireAt(key, date);
  }

查找匹配的key值,返回一个Set集合类型

public Set<String> getPatternKey(String pattern) {
    return redisTemplate.keys(pattern);
}

修改redis中key的名称

 public void renameKey(String oldKey, String newKey) {
    redisTemplate.rename(oldKey, newKey);
}

返回传入key所存储的值的类型

public DataType getKeyType(String key) {
    return redisTemplate.type(key);
}

如果旧值存在时,将旧值改为新值

public Boolean renameOldKeyIfAbsent(String oldKey, String newKey) {
    return redisTemplate.renameIfAbsent(oldKey, newKey);
}

从redis中随机取出一个key

redisTemplate.randomKey()

返回当前key所对应的剩余过期时间

 public Long getExpire(String key) {
    return redisTemplate.getExpire(key);
}

返回剩余过期时间并且指定时间单位

public Long getExpire(String key, TimeUnit unit) {
    return redisTemplate.getExpire(key, unit);
}

将key持久化保存

public Boolean persistKey(String key) {
    return redisTemplate.persist(key);
}

将当前数据库的key移动到指定redis中数据库当中

public Boolean moveToDbIndex(String key, int dbIndex) {
    return redisTemplate.move(key, dbIndex);
}

设置当前的key以及value值

redisTemplate.opsForValue().set(key, value)

设置当前的key以及value值并且设置过期时间

redisTemplate.opsForValue().set(key, value, timeout, unit)

返回key中字符串的子字符

public String getCharacterRange(String key, long start, long end) {
    return redisTemplate.opsForValue().get(key, start, end);
}

将旧的key设置为value,并且返回旧的key

public String setKeyAsValue(String key, String value) {
    return redisTemplate.opsForValue().getAndSet(key, value);
}

批量获取值

 public List<String> multiGet(Collection<String> keys) {
    return redisTemplate.opsForValue().multiGet(keys);
 }

在原有的值基础上新增字符串到末尾

redisTemplate.opsForValue().append(key, value)

以增量的方式将double值存储在变量中

 public Double incrByDouble(String key, double increment) {
    return redisTemplate.opsForValue().increment(key, increment);
 }

通过increment(K key, long delta)方法以增量方式存储long值(正值则自增,负值则自减)

public Long incrBy(String key, long increment) {
    return redisTemplate.opsForValue().increment(key, increment);
}

如果对应的map集合名称不存在,则添加否则不做修改

Map valueMap = new HashMap();  
valueMap.put("valueMap1","map1");  
valueMap.put("valueMap2","map2");  
valueMap.put("valueMap3","map3");  
redisTemplate.opsForValue().multiSetIfAbsent(valueMap); 

设置map集合到redis

Map valueMap = new HashMap();  
valueMap.put("valueMap1","map1");  
valueMap.put("valueMap2","map2");  
valueMap.put("valueMap3","map3");  
redisTemplate.opsForValue().multiSet(valueMap);  

获取字符串的长度

redisTemplate.opsForValue().size(key)

用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始

redisTemplate.opsForValue().set(key, value, offset)

重新设置key对应的值,如果存在返回false,否则返回true

redisTemplate.opsForValue().setIfAbsent(key, value)

将值 value 关联到 key,并将 key 的过期时间设为 timeout

redisTemplate.opsForValue().set(key, value, timeout, unit)

将二进制第offset位值变为value

redisTemplate.opsForValue().setBit(key, offset, value)

对key所储存的字符串值,获取指定偏移量上的位(bit)

redisTemplate.opsForValue().getBit(key, offset)
6.3.2 Hash类型

Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
Redis 中每个 hash 可以存储 2^32 - 1 键值对(40多亿)。

获取变量中的指定map键是否有值,如果存在该map键则获取值,没有则返回null。

redisTemplate.opsForHash().get(key, field)

获取变量中的键值对

public Map<Object, Object> hGetAll(String key) {
    return redisTemplate.opsForHash().entries(key);
}

新增hashMap值

redisTemplate.opsForHash().put(key, hashKey, value)

以map集合的形式添加键值对

public void hPutAll(String key, Map<String, String> maps) {
    redisTemplate.opsForHash().putAll(key, maps);
}

仅当hashKey不存在时才设置

public Boolean hashPutIfAbsent(String key, String hashKey, String value) {
    return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
}

删除一个或者多个hash表字段

public Long hashDelete(String key, Object... fields) {
    return redisTemplate.opsForHash().delete(key, fields);
}

查看hash表中指定字段是否存在

public boolean hashExists(String key, String field) {
    return redisTemplate.opsForHash().hasKey(key, field);
}

给哈希表key中的指定字段的整数值加上增量increment

public Long hashIncrBy(String key, Object field, long increment) {
    return redisTemplate.opsForHash().increment(key, field, increment);
}

获取所有hash表中字段

redisTemplate.opsForHash().keys(key)

获取hash表中字段的数量

redisTemplate.opsForHash().size(key)

获取hash表中存在的所有的值

public List<Object> hValues(String key) {
    return redisTemplate.opsForHash().values(key);
}

匹配获取键值对,ScanOptions.NONE为获取全部键对

public Cursor<Entry<Object, Object>> hashScan(String key, ScanOptions options) {
    return redisTemplate.opsForHash().scan(key, options);
}
6.3.3 List类型

通过索引获取列表中的元素

redisTemplate.opsForList().index(key, index)

获取列表指定范围内的元素(start开始位置, 0是开始位置,end 结束位置, -1返回所有)

redisTemplate.opsForList().range(key, start, end)

存储在list的头部,即添加一个就把它放在最前面的索引处

redisTemplate.opsForList().leftPush(key, value)

把多个值存入List中(value可以是多个值,也可以是一个Collection value)

redisTemplate.opsForList().leftPushAll(key, value)

List存在的时候再加入

redisTemplate.opsForList().leftPushIfPresent(key, value)

如果pivot处值存在则在pivot前面添加

redisTemplate.opsForList().leftPush(key, pivot, value)

按照先进先出的顺序来添加(value可以是多个值,或者是Collection var2)

redisTemplate.opsForList().rightPush(key, value)
redisTemplate.opsForList().rightPushAll(key, value)

在pivot元素的右边添加值

redisTemplate.opsForList().rightPush(key, pivot, value)

设置指定索引处元素的值

redisTemplate.opsForList().set(key, index, value)

移除并获取列表中第一个元素(如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止)

redisTemplate.opsForList().leftPop(key)
redisTemplate.opsForList().leftPop(key, timeout, unit)

移除并获取列表最后一个元素

redisTemplate.opsForList().rightPop(key)
redisTemplate.opsForList().rightPop(key, timeout, unit)

从一个队列的右边弹出一个元素并将这个元素放入另一个指定队列的最左边

redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey)
redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey, timeout, unit)

删除集合中值等于value的元素(index=0, 删除所有值等于value的元素; index>0, 从头部开始删除第一个值等于value的元素; index<0, 从尾部开始删除第一个值等于value的元素)

redisTemplate.opsForList().remove(key, index, value)

将List列表进行剪裁

redisTemplate.opsForList().trim(key, start, end)

获取当前key的List列表长度

redisTemplate.opsForList().size(key)

Set类型

添加元素

redisTemplate.opsForSet().add(key, values)

移除元素(单个值、多个值)

redisTemplate.opsForSet().remove(key, values)

删除并且返回一个随机的元素

redisTemplate.opsForSet().pop(key)

获取集合的大小

redisTemplate.opsForSet().size(key)

判断集合是否包含value

redisTemplate.opsForSet().isMember(key, value)

获取两个集合的交集(key对应的无序集合与otherKey对应的无序集合求交集)

redisTemplate.opsForSet().intersect(key, otherKey)

获取多个集合的交集(Collection var2)

redisTemplate.opsForSet().intersect(key, otherKeys)

key集合与otherKey集合的交集存储到destKey集合中(其中otherKey可以为单个值或者集合)

redisTemplate.opsForSet().intersectAndStore(key, otherKey, destKey)

key集合与多个集合的交集存储到destKey无序集合中

redisTemplate.opsForSet().intersectAndStore(key, otherKeys, destKey)

获取两个或者多个集合的并集(otherKeys可以为单个值或者是集合)

redisTemplate.opsForSet().union(key, otherKeys)

key集合与otherKey集合的并集存储到destKey中(otherKeys可以为单个值或者是集合)

redisTemplate.opsForSet().unionAndStore(key, otherKey, destKey)

获取两个或者多个集合的差集(otherKeys可以为单个值或者是集合)

redisTemplate.opsForSet().difference(key, otherKeys)

差集存储到destKey中(otherKeys可以为单个值或者集合)

redisTemplate.opsForSet().differenceAndStore(key, otherKey, destKey)

随机获取集合中的一个元素

redisTemplate.opsForSet().randomMember(key)

获取集合中的所有元素

redisTemplate.opsForSet().members(key)

随机获取集合中count个元素

redisTemplate.opsForSet().randomMembers(key, count)

获取多个key无序集合中的元素(去重),count表示个数

redisTemplate.opsForSet().distinctRandomMembers(key, count)

遍历set类似于Interator(ScanOptions.NONE为显示所有的)

redisTemplate.opsForSet().scan(key, options)

zSet类型

ZSetOperations提供了一系列方法对有序集合进行操作
添加元素(有序集合是按照元素的score值由小到大进行排列)

redisTemplate.opsForZSet().add(key, value, score)

删除对应的value,value可以为多个值

redisTemplate.opsForZSet().remove(key, values)

增加元素的score值,并返回增加后的值

redisTemplate.opsForZSet().incrementScore(key, value, delta)

返回元素在集合的排名,有序集合是按照元素的score值由小到大排列

redisTemplate.opsForZSet().rank(key, value)

返回元素在集合的排名,按元素的score值由大到小排列

redisTemplate.opsForZSet().reverseRank(key, value)

获取集合中给定区间的元素(start 开始位置,end 结束位置, -1查询所有)

redisTemplate.opsForZSet().reverseRangeWithScores(key, start,end)

按照Score值查询集合中的元素,结果从小到大排序

redisTemplate.opsForZSet().reverseRangeByScore(key, min, max)
redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, min, max)
//返回值为:Set<ZSetOperations.TypedTuple<V>>

从高到低的排序集中获取分数在最小和最大值之间的元素

redisTemplate.opsForZSet().reverseRangeByScore(key, min, max, start, end)

根据score值获取集合元素数量

redisTemplate.opsForZSet().count(key, min, max)

获取集合的大小

redisTemplate.opsForZSet().size(key)
redisTemplate.opsForZSet().zCard(key)

获取集合中key、value元素对应的score值

redisTemplate.opsForZSet().score(key, value)

移除指定索引位置处的成员

redisTemplate.opsForZSet().removeRange(key, start, end)

移除指定score范围的集合成员

redisTemplate.opsForZSet().removeRangeByScore(key, min, max)

获取key和otherKey的并集并存储在destKey中(其中otherKeys可以为单个字符串或者字符串集合)

redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey)

获取key和otherKey的交集并存储在destKey中(其中otherKeys可以为单个字符串或者字符串集合)

redisTemplate.opsForZSet().intersectAndStore(key, otherKey, destKey)

遍历集合(和iterator一模一样)

Cursor<TypedTuple<Object>> scan = opsForZSet.scan("test3", ScanOptions.NONE);
        while (scan.hasNext()){
            ZSetOperations.TypedTuple<Object> item = scan.next();
            System.out.println(item.getValue() + ":" + item.getScore());
        }
6.4 SpringBoot整合Redis

1、添加启动器

<!--redis-->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-data-redis</artifactId>
       </dependency>

2、配置连接信息

spring:
    redis:
      host: 127.0.0.1 
      port: 6379
      password: 123456
      jedis:
        pool:
          max-active: 8
          max-wait: -1
          max-idle: 500
          min-idle: 0
      lettuce:
        shutdown-timeout: 0

3、测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class Test_1{
    @Autowired
    private RedisTemplate<String,String>redisTemplate;

    @Test
    public void set(){
        redisTemplate.opsForValue().set("myKey","myValue");
        System.out.println(redisTemplate.opsForValue().get("myKey"));
    }
}

4、查看结果
在这里插入图片描述
redis序列化问题

7. 任务

7.1 定时任务
7.1.1 Quartz框架
  • 对于简单的任务处理可以用Spring的@Scheduled
  • 如果处理更复杂的情况,比如需要宕机恢复或者集群调度,那么Quartz是个不错的轻量级方案。

Quartz的模块
在这里插入图片描述

SchedulerFactory

SchedulerFactory负责初始化,读取配置文件,然后创建Scheduler

Scheduler

是中枢调度器,负责管理Trigger/JobDetail和3个调度线程

JobDetail

保存Job的元信息,包括类定义和设置。

Trigger

定义了何时触发任务,主要是两种SimpleTrigger和CronTigger,其他Tigger基本都可以通过这两种实现。Trigger还可以定义错过的任务如何处理。下表是说明:
在这里插入图片描述

Calendar

Calendar与Trigger相反,定义哪些时间是特例,不能执行任务。Calendar的优先级高于Trigger。

Job

负责定义任务所处理的逻辑,实现类需要实现org.quartz.Job接口

public interface Job {
void execute(JobExecutionContext context) throws JobExecutionException;
}
JobDetail

保存Job的元信息,包括类定义和设置

来源

7.1.2 cron表达式

Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式:

(1) Seconds Minutes Hours DayofMonth Month DayofWeek Year

(2) Seconds Minutes Hours DayofMonth Month DayofWeek

结构
  
  corn从左到右(用空格隔开):秒 分 小时 月份中的日期 月份 星期中的日期 年份

各字段的含义

在这里插入图片描述

(1)*:表示匹配该域的任意值。假如在Minutes域使用, 即表示每分钟都会触发事件。

(2)?:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。

(3)-:表示范围。例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次

(4)/:表示起始时间开始触发,然后每隔固定时间触发一次。例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次.

(5),:表示列出枚举值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。

(6)L:表示最后,只能出现在DayofWeek和DayofMonth域。如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。

(7)W:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份 。

(8)LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。

(9)#:用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。

常用表达式例子

(1)0 0 2 1 * ? * 表示在每月的1日的凌晨2点调整任务

(2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业

(3)0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作

(4)0 0 10,14,16 * * ? 每天上午10点,下午2点,4点

(5)0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时

(6)0 0 12 ? * WED 表示每个星期三中午12点

(7)0 0 12 * * ? 每天中午12点触发

(8)0 15 10 ? * * 每天上午10:15触发

(9)0 15 10 * * ? 每天上午10:15触发

(10)0 15 10 * * ? * 每天上午10:15触发

(11)0 15 10 * * ? 2005 2005年的每天上午10:15触发

(12)0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发

(13)0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发

(14)0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发

(15)0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发

(16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发

(17)0 15 10 ? * MON-FRI 周一至周五的上午10:15触发

(18)0 15 10 15 * ? 每月15日上午10:15触发

(19)0 15 10 L * ? 每月最后一日的上午10:15触发

(20)0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发

(21)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发

(22)0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发

定时任务的示例

7.2 异步任务

所谓异步任务,其实就是异步执行程序,有些时候遇到一些耗时的的任务,如果一直卡等待,肯定会影响其他程序的执行,所以就让这些程序需要以异步的方式去执行。

异步任务的实现

1. 使用注解@EnableAsync 开启异步调用方法

@SpringBootApplication
@EnableAsync
public class Application{
 
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

2. 创建异步执行类,定义@Component及@Async组件

在方法上@Async加上这个注解,就表示该方法是异步执行方法。在类上加这个注解,表示该类所以方法都是异步执行方法。

package com.example.async;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;

import java.util.concurrent.Future;


    @Component
    @Async("taskExecutor")
    public class AsyncTask {


        //获取异步结果
        public Future<String> task4() throws InterruptedException{
            long begin = System.currentTimeMillis();
            Thread.sleep(2000L);
            long end = System.currentTimeMillis();
            System.out.println("任务4耗时="+(end-begin));
            return new AsyncResult<String>("任务4");
        }


        public Future<String> task5() throws InterruptedException{
            long begin = System.currentTimeMillis();
            Thread.sleep(3000L);
            long end = System.currentTimeMillis();
            System.out.println("任务5耗时="+(end-begin));
            return new AsyncResult<String>("任务5");
        }

        public Future<String> task6() throws InterruptedException{
            long begin = System.currentTimeMillis();
            Thread.sleep(1000L);
            long end = System.currentTimeMillis();
            System.out.println("任务6耗时="+(end-begin));
            return new AsyncResult<String>("任务6");
        }


    }

3. 配置线程池

package com.example.async.config;

import java.util.concurrent.ThreadPoolExecutor;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * 线程池配置
 * @author zhh
 *
 */
@Configuration
@EnableAsync
public class ThreadPoolTaskConfig {

    private static final int corePoolSize = 10;       		// 核心线程数(默认线程数)线程池创建时候初始化的线程数
    private static final int maxPoolSize = 100;			    // 最大线程数 线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
    private static final int keepAliveTime = 10;			// 允许线程空闲时间(单位:默认为秒)当超过了核心线程之外的线程在空闲时间到达之后会被销毁
    private static final int queueCapacity = 200;			// 缓冲队列数 用来缓冲执行任务的队列
    private static final String threadNamePrefix = "Async-Service-"; // 线程池名前缀 方便我们定位处理任务所在的线程池

    @Bean("taskExecutor") // bean的名称,默认为首字母小写的方法名
    public ThreadPoolTaskExecutor taskExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveTime);
        executor.setThreadNamePrefix(threadNamePrefix);

        // 线程池对拒绝任务的处理策略 采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 初始化
        executor.initialize();
        return executor;
    }
}

4. JsonData工具类

package com.example.async;

import java.io.Serializable;

/**
 * 响应结果类
 * @author public
 * @date 2020/03/27
 */
public class JsonData implements Serializable {
    /**
     * 状态码 0 表示成功,1表示处理中,-1表示失败
     */
    private Integer code;
    /**
     * 数据
     */
    private Object data;
    /**
     * 描述
     */
    private String msg;

    public JsonData() {
    }

    public JsonData(Integer code, Object data, String msg) {
        this.code = code;
        this.data = data;
        this.msg = msg;
    }

    /**
     * 成功,传入数据
     * @return
     */
    public static JsonData buildSuccess() {
        return new JsonData(0, null, "操作成功。。。");
    }

    /**
     * 成功,传入数据
     * @param data
     * @return
     */
    public static JsonData buildSuccess(Object data) {
        return new JsonData(0, data, "加载成功。。。");
    }

    /**
     * 失败,传入描述信息
     * @param msg
     * @return
     */
    public static JsonData buildError(String msg) {
        return new JsonData(-1, null, msg);
    }

    /**
     * 失败,传入描述信息,状态码
     * @param msg
     * @param code
     * @return
     */
    public static JsonData buildError(String msg, Integer code) {
        return new JsonData(code, null, msg);
    }

    /**
     * 成功,传入数据,及描述信息
     * @param data
     * @param msg
     * @return
     */
    public static JsonData buildSuccess(Object data, String msg) {
        return new JsonData(0, data, msg);
    }

    /**
     * 成功,传入数据,及状态码
     * @param data
     * @param code
     * @return
     */
    public static JsonData buildSuccess(Object data, int code) {
        return new JsonData(code, data, null);
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    @Override
    public String toString() {
        return "JsonData [code=" + code + ", data=" + data + ", msg=" + msg
                + "]";
    }

}

5. 测试

package com.example.async.controller;

import java.util.concurrent.Future;

import com.example.async.AsyncTask;
import com.example.async.JsonData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController

public class UserController {

    @Autowired
    private AsyncTask task;

    @GetMapping("/async_task")
    public JsonData exeTask() throws InterruptedException{

        long begin = System.currentTimeMillis();

        Future<String> task4 = task.task4();
        Future<String> task5 = task.task5();
        Future<String> task6 = task.task6();

        //如果都执行往就可以跳出循环,isDone方法如果此任务完成,true
        for(;;){
            if (task4.isDone() && task5.isDone() && task6.isDone()) {
                break;
            }
        }

        long end = System.currentTimeMillis();
        long total = end-begin;
        System.out.println("执行总耗时="+total);
        return JsonData.buildSuccess(total);
    }
}

结果

任务6耗时=1000
任务4耗时=2000
任务5耗时=3000
执行总耗时=3001

8. 日志框架

8.1 logBack日志框架
8.1.1 LogBack介绍

基于Log4j基础上进行大量改良,不能单独使用,推荐配合日志框架SLF4J来使用。logback当前分为三个模块:logback-core,logback-access和logback-classic;logback-core是其他两个模块的基础模块。

8.1.2 LogBack配置

LogBack配置属性详解

<?xml version="1.0" encoding="UTF-8"?>
<!--
	logback.xml的基本配置信息都包含在configuration标签中,
	需要含有至少一个appender标签用于指定日志输出方式和输出格式,
	root标签为系统默认日志进程,通过level指定日志级别,
	通过appender-ref关联前面指定顶的日志输出方式。
 -->
<!-- 定义 每隔10秒中扫描该文件 -->
<configuration scan="true" scanPeriod="10 seconds" debug="true">
    <!--定义日志输出目录-->
    <property name="LOG_HOME" value="${catalina.home}/logs/loan"/>
    <!-- 控制台输出的日志格式 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!--   <pattern>[%d{yyyy-MM-dd HH:mm:ss,SSS\} %-5p] %-20c - %m%n</pattern> -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!-- 按照登录用户的userId产生日志 -->
    <appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">
        <discriminator>
            <Key>userId</Key>
            <DefaultValue>unknown</DefaultValue>
        </discriminator>
        <sift>
            <appender name="FILE-${userId}" class="ch.qos.logback.core.rolling.RollingFileAppender">
                <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                    <fileNamePattern>${LOG_HOME}/${userId}/${HOSTNAME}_%d{yyyyMMdd}.%i.log</fileNamePattern>
                    <!-- 根据日志文件按天回滚,保存时间为30天,30天之前的都将被清理掉 -->
                    <maxHistory>7</maxHistory>
                    <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                        <maxFileSize>20MB</maxFileSize>
                    </timeBasedFileNamingAndTriggeringPolicy>
                </rollingPolicy>
                <Append>false</Append>
                <encoder>
                    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
                    <charset>UTF-8</charset>
                </encoder>
            </appender>
        </sift>
    </appender>
    <!-- 输出error log 至统一日志文件中 -->
    <appender name="ERRORAPPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/error/${HOSTNAME}_%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 根据日志文件按天回滚,保存时间为30天,30天之前的都将被清理掉 -->
            <maxHistory>7</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>20MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 只打印ERROR日志 -->
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 设置日志(访问日志,系统日志)输出位置以及格式 -->
    <appender name="INTERFACE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${LOG_HOME}/interface/${HOSTNAME}_%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <!-- 根据日志文件按天回滚,保存时间为30天,30天之前的都将被清理掉 -->
            <maxHistory>7</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>20MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <pattern>[%date] [%thread] [%level] %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 只打印INFO日志 -->
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 输出URL访问日志 -->
    <appender name="URLLOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${LOG_HOME}/URL/${HOSTNAME}_%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <maxHistory>7</maxHistory>
            <!-- 日志大小 -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>20MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <pattern>[%thread] [%date] [%level] %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 只打印INFO日志 -->
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- access url -->
    <appender name="mq_biz_url" class="ch.qos.logback.classic.sift.SiftingAppender">
        <discriminator>
            <Key>SYS_FLAG</Key>
            <DefaultValue>unknown</DefaultValue>
        </discriminator>
        <sift>
            <appender name="FILE-${userId}" class="ch.qos.logback.core.rolling.RollingFileAppender">
                <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                    <fileNamePattern>${LOG_HOME}/access/${SYS_FLAG}_%d{yyyyMMdd}.%i.log</fileNamePattern>
                    <maxHistory>7</maxHistory>
                    <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                        <maxFileSize>20MB</maxFileSize>
                    </timeBasedFileNamingAndTriggeringPolicy>
                </rollingPolicy>
                <Append>false</Append>
                <encoder>
                    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %msg%n</pattern>
                    <charset>UTF-8</charset>
                </encoder>
            </appender>
        </sift>
    </appender>
    <!-- 审计URL日志 -->
    <appender name="ADUIT_URL_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/aduit_logs/${HOSTNAME}_%d{yyyyMMdd}.%i.log</fileNamePattern>
            <maxHistory>7</maxHistory>
            <!-- 日志大小 -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>20MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 只打印ERROR日志 -->
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 统一日志服务器 -->
<!--     <appender name="jinke_logs_udp" class="com.papertrailapp.logback.Syslog4jAppender"> -->
<!--         <layout class="com.jk.platform.core.common.JsonLoggerPatternLayout"> -->
<!--             <pattern>{{xdcore}} [%d{yyyy-MM-dd HH:mm:ss.SSS}][%p][%X{sessionId}][%X{traceId}][%X{cip}:%X{cPort}][%X{sip}:%X{sPort}][][%X{userId}][%t|%logger{36}|%M|%X{ctime}] - %message%n</pattern> -->
<!--         </layout> -->

<!--         <syslogConfig class="org.productivity.java.syslog4j.impl.net.udp.UDPNetSyslogConfig"> -->
<!--             <host>192.168.1.161</host> -->
<!--             <port>514</port> -->
<!--             <ident>xdcore-108</ident> -->

<!--             make logger synchronous for the tests -->
<!--             <threaded>false</threaded> -->
<!--         </syslogConfig> -->
<!--         <filter class="ch.qos.logback.classic.filter.LevelFilter"> -->
<!--             <level>WARN</level> -->
<!--             <onMatch>ACCEPT</onMatch> -->
<!--             <onMismatch>DENY</onMismatch> -->
<!--         </filter> -->
<!--     </appender> -->
    <!-- 设置异常单独打印输出 -->
    <logger name="com.jk" additivity="true">
        <level value="DEBUG"/>
        <appender-ref ref="ERRORAPPENDER"/>
    </logger>
    <logger name="com.jk.modules.platform.sysauth.session.JedisShiroSessionRepository" additivity="false">
        <level value="ERROR"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
        <appender-ref ref="ERRORAPPENDER"/>
    </logger>

    <!-- 设置过滤访问日志类路径 -->
    <logger name="com.jk.modules.platform.sysauth.interceptor.ResourceInterceptor" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="INTERFACE"/>
    </logger>
    <!-- 输出URL访问日志 -->
    <logger name="com.jk.modules.common.URLInterceptor" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="URLLOG"/>
    </logger>
    <!-- 输出MQ 消费客户端日志信息 -->
    <logger name="com.jk.modules.plaform.mq.consumer.DealInfoMsgListener" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="mq_biz_url"/>
    </logger>
    <logger name="com.jk.modules.platform.common.interceptor.AuditURLInterceptor" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="ADUIT_URL_APPENDER"/>
    </logger>
    <logger name="com.jk.modules.platform.sysauth.session" additivity="false">
        <level value="DEBUG"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
    </logger>
    <!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <!-- ++++++++++++++++++++++++++++ 第三方应用包日志配置 +++++++++++++++++++++++++++ -->
    <!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <!-- sql 的日志输出设置 -->
    <logger name="java.sql.Connection" additivity="false">
        <level value="DEBUG"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
    </logger>
    <logger name="java.sql.Statement" additivity="false">
        <level value="DEBUG"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
    </logger>
    <logger name="java.sql.PreparedStatement" additivity="false">
        <level value="DEBUG"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
    </logger>
    <logger name="com.alibaba.druid" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
    </logger>
    <!-- spring的日志输入设置 -->
    <logger name="org.springframework" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
    </logger>
    <logger name="org.mybatis.spring.mapper" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
    </logger>
    <!-- MyBatis日志输出设置 -->
    <logger name="org.apache.ibatis" level="DEBUG" additivity="false">
        <level value="DEBUG"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
    </logger>
    <logger name="org.apache.ibatis.io.ResolverUtil" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
    </logger>
    <!-- zookeeper日志输出设置 -->
    <logger name="org.apache.zookeeper" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
    </logger>
    <!-- dubbo日志输出设置 -->
    <logger name="com.alibaba.dubbo" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
    </logger>

    <logger name="org.apache.shiro.web.filter.mgt.DefaultFilterChainManager" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
    </logger>
    <logger name="org.apache.commons.beanutils.converters" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
    </logger>
    <logger name="pushlet" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
    </logger>
    <!-- show parameters for hibernate sql 专为 Hibernate 定制   -->
    <logger name="org.hibernate.cfg.HbmBinder" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
    </logger>
    <logger name="org.hibernate.hql" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
    </logger>
    <logger name="org.hibernate.loader" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
    </logger>
    <logger name="org.hibernate.cfg" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
    </logger>
    <logger name="org.hibernate.persister.collection.AbstractCollectionPersister" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
    </logger>
    <logger name="org.hibernate.persister.entity.AbstractEntityPersister" additivity="false">
        <level value="INFO"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
    </logger>
    <!--
    <logger name="org.hibernate.type.descriptor.sql.BasicBinder"  level="INFO" additivity="false">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="SIFT" />
    </logger>
    <logger name="org.hibernate.type.descriptor.sql.BasicExtractor"  level="INFO" additivity="false">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="SIFT" />
    </logger>
    <logger name="org.hibernate.SQL" level="INFO" additivity="false">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="SIFT" />
    </logger>
    <logger name="org.hibernate.engine.QueryParameters" level="INFO" additivity="false">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="SIFT" />
    </logger>
    <logger name="org.hibernate.engine.query.HQLQueryPlan" level="INFO" additivity="false">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="SIFT" />
    </logger>
    -->
    <root level="debug">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="SIFT"/>
<!--         <appender-ref ref="jinke_logs_udp"/> -->
    </root>
</configuration>
8.2 SLF4J
8.2.1 SLF4J框架简介

SLF4J不是具体的日志解决方案,它只服务于各种各样的日志系统。按照官方的说法,SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统。

实际上,SLF4J所提供的核心API是一些接口以及一个LoggerFactory的工厂类。从某种程度上,SLF4J有点类似JDBC,不过比JDBC更简单,在JDBC中,你需要指定驱动程序,而在使用SLF4J的时候,不需要在代码中或配置文件中指定你打算使用那个具体的日志系统。如同使用JDBC基本不用考虑具体数据库一样,SLF4J提供了统一的记录日志的接口,只要按照其提供的方法记录即可,最终日志的格式、记录级别、输出方式等通过具体日志系统的配置来实现,因此可以在应用中灵活切换日志系统。

8.2.2 SLF4J配置使用

首先maven构建项目,在pom.xml中添加下面代码。

 <dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>1.7.21</version>
</dependency>
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-log4j12</artifactId>
	<version>1.7.21</version>
</dependency>

最基本的配置

新建并配置log4j.properties

#定义输出级别和输出平台,控件台输出
log4j.rootLogger=debug, stdout
#设定stdout输出平台 org.apache.log4j.ConsoleAppender(控制台)
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
#org.apache.log4j.PatternLayout(可以灵活地指定布局模式)
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

示例

import java.util.Date;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
public class Test1 {
	
	protected static Logger logger = LoggerFactory.getLogger(Test1.class);
 
	public static void main(String[] args) {
 
		logger.debug("这是个测试时间{}"+new Date());
		
	}
 
}

注解使用

可以直接在类上使用注解@SLF4J来使用日志
@SLF4J相当于protected static Logger logger = LoggerFactory.getLogger(Test1.class);
不过使用该注解需要导入lombok插件

结果

这是个测试时间{}Tue Jan 03 13:50:39 CST 2017

输出级别的种类

ERROR 为严重错误 主要是程序的错误
WARN 为一般警告,比如session丢失
INFO 为一般要显示的信息,比如登录登出
DEBUG 为程序的调试信息

参考来源

9. 消息队列

9.1 消息队列概述

我们可以把消息队列比作是一个存放消息的容器,当我们需要使用消息的时候可以取出消息供自己使用。消息队列是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。目前使用较多的消息队列有ActiveMQ,RabbitMQ,Kafka,RocketMQ.

WebSocket

参考示例

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值