SpringBoot2.1.1微服务架构引入SpringCloudSecurity安全认证

1、在eureka服务器的pom.xml中引入依赖:

	<!-- Spring Cloud Security 依赖 -->
	<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

2、创建密码安全认证密码匹配规则类MyPasswordEncoder.java:

package com.zero4j.config;

import org.springframework.security.crypto.password.PasswordEncoder;

public class MyPasswordEncoder implements PasswordEncoder {

	@Override
    public String encode(CharSequence charSequence) {
        return charSequence.toString();
    }

    @Override
    public boolean matches(CharSequence charSequence, String s) {
        return s.equals(charSequence.toString());
    }
    
}

3、在eureka服务器中创建配置类SecurityConfig.java:

package com.zero4j.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
	@Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
    
    //@Autowired
    //BCryptPasswordEncoder passwordEncoder;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    	
    	//以前可以不指定PasswordEncoder,但是新的SpringBoot依赖的SpringCloudScurity需要了
    	//auth.inMemoryAuthentication().withUser("admin").password("123456").roles("ADMIN");
    	//这样,密码以明文的方式进行匹配
    	auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder()).withUser("admin").password("123456").roles("ADMIN");
    	//auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder.encode("123456")).roles("ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().ignoringAntMatchers("/eureka/**");
        super.configure(http);
    }
}

4、启动服务,访问eureka服务中心,使用admin:123456进行登录,成功进入eureka控制台

5、对微服务提供者的application.propertites说引用的eureka服务中心地址的前面加入“admin:123456@”,如:http\://admin:123456@localhost\:8761/eureka/ ,然后启动即可~

SpringBoot2.1.1使用SpringCloud的Feign调用Eureka微服务并开启Hystrix熔断机制

1、先在pom.xml中引入如下依赖:

	<!--Netflix Eureka依赖-->
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
	</dependency>
	<!--springcloud整合的openFeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

2、在pom.xml中的<project></project>之间增加以下关于springcloud的版本管理:

	<dependencyManagement>
		<dependencies>
			<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-dependencies</artifactId>
			<version>${spring-cloud.version}</version>
			<type>pom</type>
			<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

3、在application.propertites中增加如下配置:

#feign的配置,连接超时及读取超时配置
feign.client.config.default.connectTimeout=5000
feign.client.config.default.readTimeout=5000
feign.client.config.default.loggerLevel=basic
#开启熔断功能
feign.hystrix.enabled=true

4、在application启动入口增加@EnableFeignClients注解:

@EnableFeignClients
@ImportResource("classpath:hibernate.xml")
@SpringBootApplication
public class Zero4jApplication extends SpringBootServletInitializer{
	
	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
		return builder.sources(Zero4jApplication.class);
	}

	public static void main(String[] args) {
		SpringApplication.run(Zero4jApplication.class, args);
	}

}

5、创建FeignClient客户端:

package com.zero4j.controller.api.v1;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(value="zero4j-provider-area", fallback=AreasApiHystrixV1.class)
public interface AreasApiFeignClientV1 {

	@PatchMapping(value="api/v1/areas/{id}")
	String update(
			@PathVariable(value="id") String id,
			@RequestParam(value="token",required=false) String token,
			@RequestParam(value="name",required=false) String name,
			@RequestParam(value="parentId",required=false) String parentId,
			@RequestParam(value="hasChildren",required=false) Boolean hasChildren
		);

}

6、编写调用微服务的Controller:

package com.zero4j.controller.api.v1;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.json.JSONObject;

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import com.zero4j.model.area.service.AreaService;


@RestController
@RequestMapping("/api/v1/areas")
public class AreasApiControllerV1 {

	@Autowired
	private AreaService areaService;
	
	@Autowired
	private SessionFactory sessionFactory;
	
	@Autowired
	private RestTemplate restTemplate;
	
	@PatchMapping(value="/{id}")
	public ResponseEntity update(HttpServletRequest request,HttpServletResponse response,
			@PathVariable(value="id") String id,
			@RequestParam(required=false) String token,
			@RequestParam(required=false) String name,
			@RequestParam(required=false) String parentId,
			@RequestParam(required=false) Boolean hasChildren
		){
		
		return ResponseEntity.ok((String)areaApiFeignClient.update(id,token,name,parentId,hasChildren));
	}
	

}

7、编写熔断处理方法:

package com.zero4j.controller.api.v1;

import net.sf.json.JSONObject;

import org.springframework.stereotype.Component;

@Component
public class AreasApiHystrixV1 implements AreasApiFeignClientV1 {

	@Override
	public String update(String id, String token, String name, String parentId, Boolean hasChildren) {
		JSONObject out = new JSONObject();
		out.put("status", 400);
		out.put("message", "update服务异常");
		out.put("debug", "update服务异常");
		return out.toString();
	}

}

8、启动服务,访问对应的api,分别在微服务开启、关闭的时候调用API,查看结果即可。

微服务的技术栈

微服务条目 落地技术
服务开发 SpringBoot,Spring,SpringMVC
服务配置与管理 Netflix公司的Archaius、阿里的Diamond等
服务注册与发现 Eureka、Consul、Zookeeper等
服务调用 Rest、RPC、gRPC
服务熔断器 Hystrix、Envoy等
负载均衡 Ribbon、Nginx等
服务接口调用(客户端调用服务的简化工具) Feign等
消息队列 Kafka、RabbitMQ、ActiveMQ等
服务配置中心管理 SpringCloudConfig、Chef等
服务路由(API网关) Zuul等
服务监控 Zabbix、Nagios、Metrics、Specatator等
全链路追踪 Zipkin、Brave、Dapper等
服务部署 Docker、OpenStack、Kubernetes等
数据流操作开发包 SpringCloud Stream(封装与Redis,Rabbit,Kafka等发送接收消息)
事件消息总线 SpringCloud Bus

 

使用SpringBoot2.1.1配置SpringCloudConfig服务

配置中心服务器:

1、在pom.xml中加入如下依赖:

	        <dependency>
	        	<groupId>org.springframework.boot</groupId>
	        	<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!-- Spring Cloud Config - Server依赖 -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-config-server</artifactId>
			<version>2.1.1.RELEASE</version>
		</dependency>

注意高亮行,这个version需要和springboot的版本号一致。

2、在启动类中加入@EnableConfigServer注解

3、创建bootstrap.yml配置文件,本地配置如下:

#服务启动端口配置:
server:  
  port: 3000
  
#指定配置文件所在目录或地址
spring:
  profiles:
    active: native
  cloud:
    config:
      server:
        native:
          search-locations: classpath:/config/
          #search-locations: D:/Workspaces/zero4j/config/src/main/resources/config

4、在项目的resources(class)目录中创建config文件夹,并在里面新增application-dev.properties文件,内容如下:

server.address=127.0.0.1
server.port=8081
a.url=dev1233

5、启动并访问 http://localhost:3000/application-dev.properties,能看到

配置客户端:

1、在pom.xml中加入如下依赖:

		<dependency>
            		<groupId>org.springframework.boot</groupId>
            		<artifactId>spring-boot-starter-web</artifactId>
        	</dependency>

		<!-- Spring Cloud Config - Client依赖 -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-config-client</artifactId>
			<version>2.1.1.RELEASE</version>
		</dependency>

同样的,version那里切记要跟springboot的版本一致。

2、创建bootstrap.yml配置文件,配置如下:

#启动端口配置:
server:  
  port: 8080

# 指明配置服务中心的网址
spring:
  application:
    name: application
  cloud:
    config:
      label: master
      profile: dev
      uri: http://127.0.0.1:3000

3、创建测试用的controller

package com.zero4j.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

	@Value("${a.url}")
	private String datUrl;
	
	@GetMapping(value = "/test")
	public String hi(){
		return datUrl;
	}

}

4、启动并访问http://localhost:8081/test,便能看到client获取server的class中config里的配置文件对应的a.url配置

前端使用photoClick对用户上传图片进行裁切并转换为base64的示例

原作者的git项目地址:https://github.com/baijunjie/PhotoClip.js

示例代码:

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui" />
<title>PhotoClip</title>
<style>
body {
	margin: 0;
	text-align: center;
}
#clipArea {
	height: 300px;
}
#file,
#clipBtn {
	margin: 20px;
}
#view {
	margin: 0 auto;
	width: 200px;
	height: 200px;
	background-color: #666;
}
</style>
</head>
<body ontouchstart="">
<div id="clipArea"></div>
<input type="file" id="file">
<button id="clipBtn">截取</button>
<div id="view"></div>

<script src="js/hammer.min.js"></script>
<script src="js/iscroll-zoom-min.js"></script>
<script src="js/lrz.all.bundle.js"></script>

<script src="js/PhotoClip.js"></script>

<script>
	var pc = new PhotoClip('#clipArea', {
		size: [260, 260],
		outputSize: 640,
		//adaptive: ['60%', '80%'],
		file: '#file',
		view: '#view',
		ok: '#clipBtn',
		//img: 'img/mm.jpg',
		loadStart: function() {
			console.log('开始读取照片');
		},
		loadComplete: function() {
			console.log('照片读取完成');
		},
		done: function(dataURL) {
			console.log(dataURL);
		},
		fail: function(msg) {
			alert(msg);
		}
	});

	// 加载的图片必须要与本程序同源,否则无法截图
	pc.load('img/mm.jpg');

</script>
</body>
</html>

 

new PhotoClip( container [, options] )

构造函数有两个主要参数:

container

表示图片裁剪容器的选择器或者DOM对象。

options

配置选项,详细配置如下:

  • options.size

    type: Number|Array

    截取框大小。
    当值为数字时,表示截取框为宽高都等于该值的正方形。
    当值为数组时,数组中索引[0][1]所对应的值分别表示宽和高。
    默认值为 [100,100]

  • options.adaptive

    type: String|Array

    截取框自适应。设置该选项后,size 选项将会失效,此时 size 进用于计算截取框的宽高比例。
    当值为一个百分数字符串时,表示截取框的宽度百分比。
    当值为数组时,数组中索引 [0] 和 [1] 所对应的值分别表示宽和高的百分比。
    当宽或高有一项值未设置或值无效时,则该项会根据 size 选项中定义的宽高比例自适应。
    默认为 ''

  • options.outputSize

    type: Number|Array

    输出图像大小。
    当值为数字时,表示输出宽度,此时高度根据截取框比例自适应。
    当值为数组时,数组中索引 [0] 和 [1] 所对应的值分别表示宽和高,若宽或高有一项值无效,则会根据另一项等比自适应。
    默认值为[0,0],表示输出图像原始大小。

  • options.outputType

    type: String

    指定输出图片的类型,可选 ‘jpg’ 和 ‘png’ 两种种类型,默认为 ‘jpg’。

  • options.outputQuality

    type: Number

    图片输出质量,仅对 jpeg 格式的图片有效,取值 0 – 1,默认为0.8。(这个质量并不是图片的最终质量,而是在经过 lrz 插件压缩后的基础上输出的质量。相当于 outputQuality * lrzOption.quality

  • options.maxZoom

    type: Number

    图片的最大缩放比,默认为 1。

  • options.rotateFree

    type: Boolean

    是否启用图片自由旋转。由于安卓浏览器上存在性能问题,因此在安卓设备上默认关闭。

  • options.view

    type: String|HTMLElement

    显示截取后图像的容器的选择器或者DOM对象。如果有多个,可使用英文逗号隔开的选择器字符串,或者DOM对象数组。

  • options.file

    type: String|HTMLElement

    上传图片的 <input type="file"> 控件的选择器或者DOM对象。如果有多个,可使用英文逗号隔开的选择器字符串,或者DOM对象数组。

  • options.ok

    type: String|HTMLElement

    确认截图按钮的选择器或者DOM对象。如果有多个,可使用英文逗号隔开的选择器字符串,或者DOM对象数组。

  • options.img

    type: String

    需要裁剪图片的url地址。该参数表示当前立即开始读取图片,不需要使用 file 控件获取。注意,加载的图片必须要与本程序同源,如果图片跨域,则无法截图。

  • options.loadStart

    type: Function

    图片开始加载的回调函数。this 指向当前 PhotoClip 的实例对象,并将正在加载的 file 对象作为参数传入。(如果是使用非 file 的方式加载图片,则该参数为图片的 url)

  • options.loadComplete

    type: Function

    图片加载完成的回调函数。this 指向当前 PhotoClip 的实例对象,并将图片的 <img> 对象作为参数传入。

  • options.loadError

    type: Function

    图片加载失败的回调函数。this 指向当前 PhotoClip 的实例对象,并将错误信息作为第一个参数传入,如果还有其它错误对象或者信息会作为第二个参数传入。

  • options.done

    type: Function

    裁剪完成的回调函数。this 指向当前 PhotoClip 的实例对象,会将裁剪出的图像数据DataURL作为参数传入。

  • options.fail

    type: Function

    裁剪失败的回调函数。this 指向当前 PhotoClip 的实例对象,会将失败信息作为参数传入。

  • options.lrzOption

    type: Object

    lrz 压缩插件的配置参数。该压缩插件作用于图片从相册输出到移动设备浏览器过程中的压缩,配置的高低将直接关系到图片在移动设备上操作的流畅度。以下为子属性:

    • options.lrzOption.width

      type: Number

      图片最大不超过的宽度,默认为原图宽度,高度不设时会适应宽度。(由于安卓浏览器存在性能问题,所以默认值为 1000)

    • options.lrzOption.height

      type: Number

      图片最大不超过的高度,默认为原图高度,宽度不设时会适应高度。(由于安卓浏览器存在性能问题,所以默认值为 1000)

    • options.lrzOption.quality

      type: Number

      图片压缩质量,仅对 jpeg 格式的图片有效,取值 0 – 1,默认为0.7。(这个质量不是最终输出的质量,与 options.outputQuality 是相乘关系)

  • options.style

    type: Object

    样式配置。以下为子属性:

    • options.style.maskColor

      type: String

      遮罩层的颜色。默认为 'rgba(0,0,0,.5)'

    • options.style.maskBorder

      type: String

      遮罩层的 border 样式。默认为 '2px dashed #ddd'

    • options.style.jpgFillColor

      type: String

      当输出 jpg 格式时透明区域的填充色。默认为 '#fff'

  • options.errorMsg

    type: Object

    错误信息对象,包含各个阶段出错时的文字说明。以下为子属性:

    • options.errorMsg.noSupport

      type: String

      浏览器无法支持本插件。将会使用 alert 弹出该信息,若不希望弹出,可将该值置空。

    • options.errorMsg.imgError

      type: String

      使用 file 控件读取图片格式错误时的错误信息。将会在 loadError 回调的错误信息中输出。

    • options.errorMsg.imgHandleError

      type: String

      lrz 压缩插件处理图片失败时的错误信息。将会在 loadError 回调的错误信息中输出。

    • options.errorMsg.imgLoadError

      type: String

      图片加载出错时的错误信息。将会在 loadError 回调的错误信息中输出。

    • options.errorMsg.noImg

      type: String

      没有加载完成的图片时,执行截图操作时的错误信息。将会在 fail 回调的失败信息中输出。

    • options.errorMsg.clipError

      type: String

      截图出错时的错误信息。将会在 fail 回调的失败信息中输出。


 

完整demo在这里下载:photoclick-demo

CentOS 7 使用 yum 安装 RabbitMQ 3.6.6

1、安装erlang

到https://www.rabbitmq.com/releases/erlang/下载安装erlang的最大rpm版本,如果自行安装erlang会十分麻烦,我已经被坑过了

1.1、下载erlang

wget http://www.rabbitmq.com/releases/erlang/erlang-19.0.4-1.el7.centos.x86_64.rpm

1.2、安装erlang

rpm -ivh erlang-19.0.4-1.el7.centos.x86_64.rpm

1.3、测试erlang

输入erl,出现如下画面即为安装正确:

Erlang/OTP 19 [erts-8.0.3] [source] [64-bit] [async-threads:10] [hipe] [kernel-poll:false]
 
Eshell V8.0.3  (abort with ^G)
1>

2、安装RabbitMQ

注意不要再官网上安装最新的,安装最新的则需要安装更新版本的erlang,但是erlang能用rpm安装的最大版本只有19,所以我们在这里下载匹配的v3.6.6的RabbitMQ。

2.1、下载RabbitMQ

wget http://www.rabbitmq.com/releases/rabbitmq-server/v3.6.6/rabbitmq-server-3.6.6-1.el7.noarch.rpm

2.2、安装RabbitMQ

rpm -ivh rabbitmq-server-3.6.6-1.el7.noarch.rpm

如果提示:#错误:依赖检测失败:socat 被 rabbitmq-server-3.6.6-1.el7.noarch 需要

则使用以下命令先安装socat:

yum install socat -y

2.3、启动RabbitMQ

systemctl start rabbitmq-server    #启动
systemctl status rabbitmq-server   #状态
systemctl stop rabbitmq-server     #关闭

2.4、配置网页插件

rabbitmq-plugins enable rabbitmq_management #启用插件

2.5、配置访问账号密码和权限

rabbitmqctl add_user mq mq123   #添加用户mq,密码mq123
rabbitmqctl set_permissions -p / mq ".*" ".*" ".*"  #添加权限
rabbitmqctl set_user_tags mq administrator  #修改用户角色
 
 
#其它操作
rabbitmqctl delete_user Username  #删除一个用户
rabbitmqctl change_password Username Newpassword  #修改用户密码
rabbitmqctl list_users  #查看当前用户列表

2.6、登录网页端

http://ip:15672

输入刚刚设置的账号和密码即可。

springboot2.1.1+hibernate5.0.12开启数据库二级缓存

首先加入依赖到pom.xml:

		<!-- hibernnate对二级缓存的支持 -->
		<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-ehcache -->
		<dependency>
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-ehcache</artifactId>
		    <version>5.0.12.Final</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/net.sf.ehcache/ehcache -->
		<dependency>
		    <groupId>net.sf.ehcache</groupId>
		    <artifactId>ehcache</artifactId>
		    <version>2.10.6</version>
		</dependency>

然后在hibernate.xml配置文件中加入以下参数:

				<!-- hibernate5的二级缓存配置 -->
				<prop key="hibernate.cache.use_second_level_cache">true</prop>
				<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>

接着创建二级缓存配置文件ehcache.xml:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <diskStore path="java.io.tmpdir"/>
    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        overflowToDisk="true"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        diskPersistent="false"/>
</ehcache>

最后在需要开启缓存的pojo类中加入注解cache注解:

@Entity
@GenericGenerator(name="idGenerator", strategy="uuid")
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
public class Area {

	/**
	 * id:UUID生成法则
	 */
	@Id
	@GeneratedValue(generator="idGenerator")
	private String id;

配置完毕,启动项目,输出hibernate的sql执行语句,会发现第二次查询相同对象时不再执行查询语句。

使用jconsole远程监控tomcat的性能

1、修改tomcat的启动文件,在启动文件的CATALINA_OPTS(如果是daemon.sh启动则为JAVA_OPTS)变量中加上以下参数:

-Djava.rmi.server.hostname=[服务器IP] -Dcom.sun.management.jmxremote.port=[用于监控的端口] -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false

2、打开jconsole,输入服务器的IP和监控端口,账号密码为服务器的登录账号和密码,点连接即可。

Spring自定义注解(parameter)

1、新建注解接口VerifyAccount.java:

package com.zero4j.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented

public @interface VerifyAccount {

	String paramName() default "token";
	
	String permission() default "";
}

2、创建注解对应的切面类VerifyAccountAspect.java:

package com.zero4j.annotation;

import net.sf.json.JSONObject;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.support.MissingServletRequestPartException;

import com.zero4j.model.account.Account;
import com.zero4j.model.token.util.TokenStaticUtil;

@Aspect
@Component
public class VerifyAccountAspect implements HandlerMethodArgumentResolver{

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(VerifyAccount.class);
	}

	@Override
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
		
		String token = webRequest.getParameter("token");
		
		 JSONObject out = new JSONObject();
		
		if(token==null||token.equals("")){
			out.put("status", 401);
			out.put("message", "请先注册并登录后再进行此操作");
			out.put("debug", "缺少参数token或为空");
			//ResponseStaticUtil.write(response,out);
			System.out.println(out.toString());
			//return;
		}
		Account account = TokenStaticUtil.getAccount(token);
		if(account==null){
			out.put("status", 401);
			out.put("message", "请先注册并登录后再进行此操作");
			out.put("debug", "token对应的account为空");
			//ResponseStaticUtil.write(response,out);
			System.out.println(out.toString());
			throw new MissingServletRequestPartException("account");
			//return;
		}
		
		return account;
	}
	
}

注意要实现接口:HandlerMethodArgumentResolver

3、最后要在SpringMVC的配置java类中加入对应的代码:

package com.zero4j.config;

import java.util.List;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.zero4j.annotation.VerifyAccountAspect;

@Configuration
public class WebAppConfigurer implements WebMvcConfigurer {


	@Override
	public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
		
		resolvers.add(new VerifyAccountAspect());
		
		WebMvcConfigurer.super.addArgumentResolvers(resolvers);
	}

}

注意这行:resolvers.add(new VerifyAccountAspect());

Spring自定义注解(method)

先定义一个注解接口 VerifyToken.java :

package com.zero4j.annotation;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented

public @interface VerifyToken {

	//权限参数
	String permission() default "";
	
}

再在同目录中定义一个切面类VerifyTokenAspect.java:

package com.zero4j.annotation;

import java.util.Enumeration;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.json.JSONObject;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.zero4j.model.account.Account;
import com.zero4j.model.permission.util.PermissionStaticUtil;
import com.zero4j.model.token.util.TokenStaticUtil;
import com.zero4j.util.ResponseStaticUtil;

@Aspect
@Component
public class VerifyTokenAspect {

	@Pointcut("@annotation(com.zero4j.annotation.VerifyToken)")	//这是annotation对应类的所在位置,若目录不同,则填写完整路径,如:com.zero4j.annotation.VerifyToken
    private void pointcut(){
		
	}
	
	@Around("pointcut()&&@annotation(verifyToken)")
	public void around(ProceedingJoinPoint pjp, VerifyToken verifyToken) throws Throwable{
		
		//System.out.println("annotation执行前");
		
		//System.out.println("verifyToken.permission() = "+verifyToken.permission());
		
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        HttpServletResponse response = attributes.getResponse();
        
        String token = request.getParameter("token");
        
        JSONObject out = new JSONObject();
		
		if(token==null||token.equals("")){
			out.put("status", 401);
			out.put("message", "请先注册并登录后再进行此操作");
			out.put("debug", "缺少参数token或为空");
			ResponseStaticUtil.write(response,out);
			//System.out.println(out.toString());
			return;
		}
		Account account = TokenStaticUtil.getAccount(token);
		if(account==null){
			out.put("status", 401);
			out.put("message", "请先注册并登录后再进行此操作");
			out.put("debug", "token对应的account为空");
			ResponseStaticUtil.write(response,out);
			//System.out.println(out.toString());
			return;
		}
		
		if(!(verifyToken.permission()==null||verifyToken.permission().equals(""))){
			if(PermissionStaticUtil.verify(account.getId(), verifyToken.permission())==false){
				out.put("status", 403);
				out.put("message", "你无权进行本操作");
				out.put("debug", "你没有"+verifyToken.permission()+"权限");
				ResponseStaticUtil.write(response,out);
				//System.out.println(out.toString());
				return;
			}
		}
		
		if(out.size()==0){
			pjp.proceed();
		}

	}

}

最后在需要切入的地方加上注解即可:

	@VerifyToken(permission="adminLog_read")
	@RequestMapping(value="", method=RequestMethod.GET)
	public void list(HttpServletRequest request, HttpServletResponse response,
			@RequestParam(required=false) String token,
			@RequestParam(required=false) Integer offset,
			@RequestParam(required=false) Integer limit,
			@RequestParam(required=false) String startTime,
			@RequestParam(required=false) String endTime,
			@RequestParam(required=false) String accountNickname,
			@RequestParam(required=false) String accountMobile,
			@RequestParam(required=false) String description
		){
		
		........

		}

 

close