dokcerfile 之缓存 node_modules

如果直接拷贝代码到 /app 目录,由于每次编译时,代码都会改变,所以都需要重新下载依赖。

如果将依赖下载提到前面,就可以利用docker编译的缓存机制,达到加速编译的效果。

如果 package.json 改变,则会重新下载依赖。

Dockerfile 文件

FROM node:16.15.0 as build
# 将依赖文件拷贝到 tmp 文件夹
COPY ./package.json /tmp/package.json
# 下载依赖
RUN npm config set registry https://registry.npm.taobao.org && cd /tmp && npm install
# 将依赖拷贝到 /app
RUN mkdir /app && cp -a /tmp/node_modules /app/
WORKDIR /app
COPY ./ /app
RUN npm run build

FROM nginx
RUN mkdir /app
COPY --from=build /app/dist /app
COPY nginx.conf /etc/nginx/nginx.conf

nginx.conf 文件

user  nginx;
worker_processes  1;
error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;
events {
  worker_connections  1024;
}
http {
  include       /etc/nginx/mime.types;
  default_type  application/octet-stream;
  log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
  access_log  /var/log/nginx/access.log  main;
  sendfile        on;
  keepalive_timeout  65;
  server {
    listen       80;
    server_name  localhost;
    location / {
      root   /app;
      index  index.html;
      try_files $uri $uri/ /index.html;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
      root   /usr/share/nginx/html;
    }
  }
}

@Scheduled注解

@Scheduled注解是Spring Boot提供的用于定时任务控制的注解,主要用于控制任务在某个指定时间执行,或者每隔一段时间执行。注意需要配合@EnableScheduling使用。原文地址

@Scheduled主要有三种配置执行时间的方式:

  • cron
  • fixedRate
  • fixedDelay

cron

cron@Scheduled的一个参数,是一个字符串,以5个空格隔开,只允许6个域(注意不是7个,7个直接会报错),分别表示秒、分、时、日、月、周。

cron 通配符
通配符
示例
@Scheduled(cron = "0 * * * 1 SAT") //每年的1月的所有周六的所有0秒时间执行
@Scheduled(cron = "0 0 0 1 Jan ?") //每年的1月的1日的0时0分0秒执行

cron支持占位符,若在配置文件中有

cron = 2 2 2 2 2 ?

@Scheduled(cron = "${cron}")

表示每年的二月二号的两时两分两秒执行。

fixedRate

fixedRate表示自上一次执行时间之后多长时间执行,以毫秒为单位。

@Scheduled(fixedRate = 1000 * 5)

自上次执行之后5秒再执行。

fixedRateString

以字符串的参数传递值,并且支持从配置中读取,如:

# 表示2秒间隔。
@Scheduled(fixedRateStirng="${interval}")

fixedDelay

fixedDelayfixedRate有点类似,不过fixedRate是上一次开始之后计时,fixedDelay是上一次结束之后计时,也就是说,fixedDelay表示上一次执行完毕之后多长时间执行,单位也是毫秒。

@Scheduled(fixedDelay = 1000 * 3600 * 12) //上一次执行完毕后半天后再次执行

fixedDelayString

以字符串的参数传递值,并且支持从配置中读取,如:

@Scheduled(fixedDelayString = "${fixedDelay}")

initialDelay

initialDelay表示首次延迟多长时间后执行,单位毫秒,之后按照cron/fixedRate/fixedRateString/fixedDelay/fixedDelayString指定的规则执行,需要指定其中一个规则。

@Scheduled(initialDelay=1000,fixedRate=1000) //首次运行延迟1s

initialDelayString

initialDelay类似,不过是字符串,支持占位符。

@Scheduled(initialDelayString = "${initialDelay}",cron = "0 0 0 14 4 ?") 
//按照配置文件initialDelay指定的时间首次延迟,并于每年4月14日0时0分0秒执行

spring boot 定时任务

使用 springboot 创建定时任务大概有三种方法

  1. 使用注解 @Scheduled
  2. 使用接口 SchedulingConfigurer
  3. 使用注解设定多线程定时任务

使用注解

基于注解@Scheduled默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响。

通过 fixedDelay 参数指定间隔

package cn.codeo.scheduled.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

import java.time.LocalDateTime;

@Configuration
@EnableScheduling
public class DemoScheduled {
    @Scheduled(fixedDelay = 5000)
    private void print(){
        System.out.println(LocalDateTime.now());
    }
}

程序将输出以下结果

2022-03-13T19:29:28.478
2022-03-13T19:29:33.486
2022-03-13T19:29:38.492
2022-03-13T19:29:43.500

通过 cron 设置定时执行 什么是 cron ?

cron 的参数 <秒> <分> <时> <天> <月> <周>

? 号表示忽略这个参数只能用于

package cn.codeo.scheduled.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

import java.time.LocalDateTime;

@Configuration
@EnableScheduling
public class DemoScheduled {
//    @Scheduled(fixedDelay = 5000)
    @Scheduled(cron = "*/5 * * * * ?")
    private void print(){
        System.out.println(LocalDateTime.now());
    }
}

程序会输出以下结果

2022-03-13T19:44:35.043
2022-03-13T19:44:40.016
2022-03-13T19:44:45.004
2022-03-13T19:44:50.007

使用接口 SchedulingConfigurer

需要继承 SchedulingConfigurer 并实现 configureTasks 方法

样例代码

package cn.codeo.scheduled.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

import java.time.LocalDateTime;

@Configuration
@EnableScheduling
public class Demo2Scheduled implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.addCronTask(()->System.out.println(LocalDateTime.now()),"*/5 * * * * *");
    }
}

样例输出

2022-03-13T19:58:25.014
2022-03-13T19:58:30.001
2022-03-13T19:58:35.014
2022-03-13T19:58:40.007

使用异步

@Scheduled 配合 @Async 注解可以实现异步任务

Spring中的IOC和DI

IOC是Inversion of Control的缩写,多数书籍翻译成“控制反转”。

DI是依赖注入。

IOC 和 DI 是一个东西,只是说法不同。

Spring IOC的基本概念


控制反转(IoC)是一个比较抽象的概念,它主要用来消减计算机程序的耦合问题,是Spring框架的核心。
依赖注入(DI)是IoC的另外一种说法,只是从不同的角度描述相同的概念。

IOC 的背景

我们都知道,在采用面向对象方法设计的软件系统中,它的底层实现都是由N个对象组成的,所有的对象通过彼此的合作,最终实现系统的业务逻辑。

软件系统中耦合的对象

  如果我们打开机械式手表的后盖,就会看到与上面类似的情形,各个齿轮分别带动时针、分针和秒针顺时针旋转,从而在表盘上产生正确的时间。图1中描述的就是这样的一个齿轮组,它拥有多个独立的齿轮,这些齿轮相互啮合在一起,协同工作,共同完成某项任务。我们可以看到,在这样的齿轮组中,如果有一个齿轮出了问题,就可能会影响到整个齿轮组的正常运转。

  齿轮组中齿轮之间的啮合关系,与软件系统中对象之间的耦合关系非常相似。对象之间的耦合关系是无法避免的,也是必要的,这是协同工作的基础。现在,伴随着工业级应用的规模越来越庞大,对象之间的依赖关系也越来越复杂,经常会出现对象之间的多重依赖性关系,因此,架构师和设计师对于系统的分析和设计,将面临更大的挑战。对象之间耦合度过高的系统,必然会出现牵一发而动全身的情形。

对象之间的依赖关系

耦合关系不仅会出现在对象与对象之间,也会出现在软件系统的各模块之间,以及软件系统和硬件系统之间。如何降低系统之间、模块之间和对象之间的耦合度,是软件工程永远追求的目标之一。为了解决对象之间的耦合度过高的问题,软件专家Michael Mattson 1996年提出了IOC理论,用来实现对象之间的“解耦”,目前这个理论已经被成功地应用到实践当中。

什么是IOC

  IOC是Inversion of Control的缩写,多数书籍翻译成“控制反转”。

  1996年,Michael Mattson在一篇有关探讨面向对象框架的文章中,首先提出了IOC 这个概念。对于面向对象设计及编程的基本思想,前面我们已经讲了很多了,不再赘述,简单来说就是把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实现对外部是透明的,从而降低了解决问题的复杂度,而且可以灵活地被重用和扩展。

IOC解耦过程

大家看到了吧,由于引进了中间位置的“第三方”,也就是IOC容器,使得A、B、C、D这4个对象没有了耦合关系,齿轮之间的传动全部依靠“第三方”了,全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合剂”的由来。

我们再来做个试验:把上图中间的IOC容器拿掉,然后再来看看这套系统:

拿掉IOC容器后的系统

 我们现在看到的画面,就是我们要实现整个系统所需要完成的全部内容。这时候,A、B、C、D这4个对象之间已经没有了耦合关系,彼此毫无联系,这样的话,当你在实现A的时候,根本无须再去考虑B、C和D了,对象之间的依赖关系已经降低到了最低程度。所以,如果真能实现IOC容器,对于系统开发而言,这将是一件多么美好的事情,参与开发的每一成员只要实现自己的类就可以了,跟别人没有任何关系!

    我们再来看看,控制反转(IOC)到底为什么要起这么个名字?我们来对比一下:

    软件系统在没有引入IOC容器之前,如图1所示,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。

    软件系统在引入IOC容器之后,这种情形就完全改变了,如图3所示,由于IOC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。

    通过前后的对比,我们不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。

参考文章

[1] 浅谈IOC–说清楚IOC是什么

springboot 的过滤器和拦截器

过滤器(Filter)和拦截器(Interceptor)的区别

  • spring的拦截器和servlet的过滤器有相似之处,都是AOP思想的体现,都可以实现权限检查,日志记录,不同的是
  • 适用范围不同:Filter是Servlet容器规定的,只能使用在servlet容器中,而拦截器的使用范围就大得多
  • 使用的资源不同:拦截器是属于spring的一个组件,因此可以使用spring的所有资源,对象,如service对象,数据源,事务控制等,而过滤器就不行
  • 深度不同:Filter还在servlet前后起作用。而拦截器能够深入到方法前后,异常抛出前后,因此拦截器具有更大的弹性,所有在spring框架中应该优先使用拦截器。
    通过调试可以发现,拦截器的执行过程是在过滤器的doFilter中执行的,过滤器的初始化会在项目启动时执行。

过滤器(Filter)

springboot下过滤器的使用有两种形式:

注解形式

  1. 创建一个Filter,并使用WebFilter注解进行修饰,表示该类是一个Filter,以便于启动类进行扫描的时候确认。
  2. 然后在启动类上添加注解@ServletComponentScan,该注解用于自动扫描指定包下(默认是与启动类同包下)的WebFilter/WebServlet/WebListener等特殊类。
package cn.codeon.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter(urlPatterns = "/*",filterName = "customFilter")
public class CustomFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("filter");
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

bean 的方式

package cn.codeon.filter;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import java.io.IOException;

@Component
@Order(1)
public class CustomFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("filter");
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

拦截器(Interceptor)

工作原理

一个拦截器,只有preHandle方法返回true,postHandle、afterCompletion才有可能被执行;如果preHandle方法返回false,则该拦截器的postHandle、afterCompletion必然不会被执行。拦截器不是Filter,却实现了Filter的功能,其原理在于:

  • 所有的拦截器(Interceptor)和处理器(Handler)都注册在HandlerMapping中。
  • Spring MVC中所有的请求都是由DispatcherServlet分发的。
  • 当请求进入DispatcherServlet.doDispatch()时候,首先会得到处理该请求的Handler(即Controller中对应的方法)以及所有拦截该请求的拦截器。拦截器就是在这里被调用开始工作的。

工作流程

正常流程:

Interceptor.preHandle >> Controller处理请求 >> Interceptor.postHandle >> 渲染视图view >> Interceptor.afterCompletion

中断流程:

如果在Interceptor1.preHandle中报错或返回false ,那么接下来的流程就会被中断,但注意被执行过的拦截器的afterCompletion仍然会执行。下图为Interceptor1.preHandle返回false的情况:

前置拦截器2 preHandle: 用户名:null

前置拦截器1 preHandle: 请求的uri为:http://localhost:8010/user/353434

拦截器2 afterCompletion:

应用场景

限制访问次数

记录用户IP访问次数,第一次访问时在redis中创建一个有效时长1秒的key,当第二次访问时key值+1,当值大于等于5时在redis中创建一个5分钟的key,当拦截器查询到reids中有当前IP的key值时返回false限制用户请求接口

  1. 日志记录,可以记录请求信息的日志,以便进行信息监控、信息统计等。
  2. 权限检查:如登陆检测,进入处理器检测是否登陆,如果没有直接返回到登陆页面。
  3. 性能监控:典型的是慢日志。 

使用方法

  • 类要实现Spring 的HandlerInterceptor 接口。
  • 类继承实现了HandlerInterceptor 接口的类,例如:已经提供的实现了HandlerInterceptor 接口的抽象类HandlerInterceptorAdapter。
public interface HandlerInterceptor {
 
   /**
     * 预处理回调方法,实现处理器的预处理(如检查登陆),第三个参数为响应的处理器,自定义Controller
     * 返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断
(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;
   */
    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception;
 
   /**
     * 后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以
通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。
   */
    void postHandle(
            HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception;
 
   /**
    * 整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输
出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中
   */
    void afterCompletion(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception;
 
 
}

方法作用

  • preHandle:在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制、权限校验等处理;
  • postHandle:在业务处理器处理请求执行完成后,生成视图之前执行。后处理(调用了Service并返回ModelAndView,但未进行页面渲染),有机会修改ModelAndView (这个博主就基本不怎么用了);
  • afterCompletion:在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面);

Spring Boot 整合 Apache Shiro

Apache Shiro™是一个功能强大且易于使用的 Java 安全框架,它执行身份验证、授权、加密和会话管理。借助 Shiro 易于理解的 API,您可以快速轻松地保护任何应用程序——从最小的移动应用程序到最大的 Web 和企业应用程序。

https://shiro.apache.org/

官方文档说明:https://shiro.apache.org/spring-boot.html

在开发的时候,发现其登录功能是直接将用户信息存储到Session中, 过于简陋,应该有一个合适的工具来做这件事。

思考过 SpringSecrity 并不是很适合我的口味。故想考虑使用另一款,Shiro。

Shiro三大核心组件

Subject: 即当前用户,在权限管理的应用程序里往往需要知道谁能够操作什么,谁拥有操作该程序的权利,shiro中则需要通过Subject来提供基础的当前用户信息,Subject 不仅仅代表某个用户,与当前应用交互的任何东西都是Subject,如网络爬虫等。所有的Subject都要绑定到SecurityManager上,与Subject的交互实际上是被转换为与SecurityManager的交互。

SecurityManager: 即所有Subject的管理者,这是Shiro框架的核心组件,可以把他看做是一个Shiro框架的全局管理组件,用于调度各种Shiro框架的服务。作用类似于SpringMVC中的DispatcherServlet,用于拦截所有请求并进行处理。

Realm: Realm是用户的信息认证器和用户的权限人证器,我们需要自己来实现Realm来自定义的管理我们自己系统内部的权限规则。SecurityManager要验证用户,需要从Realm中获取用户。可以把Realm看做是数据源

安装依赖
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-spring-boot-web-starter</artifactId>
  <version>1.9.0</version>
</dependency>
编写配置类
package cn.codeon.config;

import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ShiroConfig {
    @Bean
    public Realm realm() {
        SimpleAccountRealm realm = new SimpleAccountRealm();

        realm.addAccount("root", "root");

        return realm;
    }

    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
        chainDefinition.addPathDefinition("/**", "anon"); // all paths are managed via annotations

        // or allow authentication, but NOT require it.
        // chainDefinition.addPathDefinition("/**", "authc");
        return chainDefinition;
    }
}

除了 SimpleAccountRealm 你也可以自己实现 Realm

直接实现 Realm 接口可能既耗时又容易出错。大多数人选择继承 AuthorizingRealm 抽象类,而不是从头开始。该类实现了常见的身份验证和授权工作流程,以节省您的时间和精力。

内置过滤器

dockerfile 打包 springboot 项目

需要 application-pro.yml 文件

# 使用 docker 编译 java 项目
docker run --rm -it \
-v `pwd`/.m2:/root/.m2 \
-v `pwd`:/app \
-w /app maven:3.6.3-jdk-11 \
mvn clean package -Dmaven.test.skip=true

COPY target/dreamer*.jar /app.jar 中的*是通配符 详细说明https://docs.docker.com/engine/reference/builder/#copy

FROM java:8

MAINTAINER 自由如风 <1517607397@qq.com>

COPY target/dreamer*.jar /app.jar

# 设置生产模式
ENV SPRING_PROFILES_ACTIVE pro

EXPOSE 8081

ENTRYPOINT java -jar /app.jar

dockerfile 打包 Vue 项目

FROM node:14.17.6 AS builder

COPY . /app

WORKDIR /app

# 设置镜像,安装依赖,编译
RUN npm config set registry https://registry.npm.taobao.org/ && \
    npm install && npm run build

FROM nginx

COPY --from=builder  /app/dist /app

COPY ./nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

nginx 相关配置查阅 vuecli官网

如果不需要使用 docker 调用 npm 打包,直接打包静态资源,可以这样。

FROM nginx

MAINTAINER 自由如风 <1517607397@qq.com>

COPY ./dist /app

COPY ./docker/nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

注解

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。

作用在代码的注解是

  • @Override – 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
  • @Deprecated – 标记过时方法。如果使用该方法,会报编译警告。
  • @SuppressWarnings – 指示编译器去忽略注解中声明的警告。

作用在其他注解的注解(或者说 元注解)是:

  • @Retention – 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
  • @Documented – 标记这些注解是否包含在用户文档中。
  • @Target – 标记这个注解应该是哪种 Java 成员。
  • @Inherited – 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

从 Java 7 开始,额外添加了 3 个注解:

  • @SafeVarargs – Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
  • @FunctionalInterface – Java 8 开始支持,标识一个匿名函数或函数式接口。
  • @Repeatable – Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

@target

翻译成中文为 目标,表示注解可以附加的目标

  • @Target(ElementType.TYPE)——接口、类、枚举、注解
  • @Target(ElementType.FIELD)——字段、枚举的常量
  • @Target(ElementType.METHOD)——方法
  • @Target(ElementType.PARAMETER)——方法参数
  • @Target(ElementType.CONSTRUCTOR) ——构造函数
  • @Target(ElementType.LOCAL_VARIABLE)——局部变量
  • @Target(ElementType.ANNOTATION_TYPE)——注解
  • @Target(ElementType.PACKAGE)——包,用于记录java文件的package信息

@Retention

翻译中文的意思是保留,这里可以解释为注解的生命周期

  • RetentionPolicy.RUNTIME  : 注解保留在程序运行期间。
  • RetentionPolicy.SOURCE  : 注解只保留在源文件中。
  • RetentionPolicy.CLASS  : 注解保留在class文件中,在加载到JVM虚拟机时丢弃。

@Documented

注解表明这个注解应该被 javadoc工具记录,没啥用。

maven 阿里云镜像地址

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
    <mirrors>
        <mirror>
            <id>alimaven</id>
            <mirrorOf>central</mirrorOf>
            <name>aliyun maven</name>
            <url>http://maven.aliyun.com/nexus/content/repositories/central/</url>
        </mirror>
    </mirrors>
</settings >