(二期)9、renren-fast项目解读(二)
- 简洁(Compact)
- 自包含(Self-contained)
- 头部(header)
- 载荷(payload)
- 签证(signature)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
{ "alg": "HS256", "typ": "JWT"}
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
载荷(payload)
- 标准中注册的声明
- 公共的声明
- 私有的声明
payload-标准中注册的声明 (建议但不强制使用) :
- iss
- sub
- aud
- exp
- nbf
- iat
- jti
payload-公共的声明 :
payload-私有的声明 :
{"name":"Free码农","age":"28","org":"今日头条"}
eyJvcmciOiLku4rml6XlpLTmnaEiLCJuYW1lIjoiRnJlZeeggeWGnCIsImV4cCI6MTUxNDM1NjEwMywiaWF0IjoxNTE0MzU2MDQzLCJhZ2UiOiIyOCJ9
签证(signature)
49UF72vSkj-sA4aHHiYN5eoZ9Nb4w5Vb45PsLF7x_NY
第一步
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>#renren-fast用的是0.7.0版本
</dependency>
第二步
@ConfigurationProperties(prefix = "renren.jwt")
@Component
public class JwtUtils {
private Logger logger = LoggerFactory.getLogger(getClass());
private String secret;
private long expire;
private String header;
/**
* 生成jwt token
*/
public String generateToken(long userId) {
Date nowDate = new Date();
//过期时间
Date expireDate = new Date(nowDate.getTime() + expire * 1000);
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(userId+"")
.setIssuedAt(nowDate)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Claims getClaimByToken(String token) {
try {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
logger.debug("validate is token error ", e);
return null;
}
}
/**
* token是否过期
* @return true:过期
*/
public boolean isTokenExpired(Date expiration) {
return expiration.before(new Date());
}
....getter、setter
}
第三步、
/**
* app登录效验
* @author chenshun
* @email sunlightcs@gmail.com
* @date 2017/9/23 14:30
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Login {
}
第四步
/**
* 登录
*/
@PostMapping("login")
@ApiOperation("登录")
public R login(@RequestBody LoginForm form){
//表单校验
ValidatorUtils.validateEntity(form);
//用户登录
long userId = userService.login(form);
//生成token
String token = jwtUtils.generateToken(userId);
Map<String, Object> map = new HashMap<>();
map.put("token", token);
map.put("expire", jwtUtils.getExpire());
return R.ok(map);
}
第五步、
/**
* 权限(Token)验证
* @author chenshun
* @email sunlightcs@gmail.com
* @date 2017-03-23 15:38
*/
@Component
public class AuthorizationInterceptor extends HandlerInterceptorAdapter {
@Autowired
private JwtUtils jwtUtils;
public static final String USER_KEY = "userId";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Login annotation;
if(handler instanceof HandlerMethod) {
annotation = ((HandlerMethod) handler).getMethodAnnotation(Login.class);
}else{
return true;
}
if(annotation == null){
return true;
}
//获取用户凭证
String token = request.getHeader(jwtUtils.getHeader());
if(StringUtils.isBlank(token)){
token = request.getParameter(jwtUtils.getHeader());
}
//凭证为空
if(StringUtils.isBlank(token)){
throw new RRException(jwtUtils.getHeader() + "不能为空", HttpStatus.UNAUTHORIZED.value());
}
Claims claims = jwtUtils.getClaimByToken(token);
if(claims == null || jwtUtils.isTokenExpired(claims.getExpiration())){
throw new RRException(jwtUtils.getHeader() + "失效,请重新登录", HttpStatus.UNAUTHORIZED.value());
}
//设置userId到request里,后续根据userId,获取用户信息
request.setAttribute(USER_KEY, Long.parseLong(claims.getSubject()));
return true;
}
}
/**
* 登录用户信息
*
* @author chenshun
* @email sunlightcs@gmail.com
* @date 2017-03-23 20:39
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {
}
@RestController
@RequestMapping("/app")
@Api("APP测试接口")
public class AppTestController {
@Login
@GetMapping("userInfo")
@ApiOperation("获取用户信息")
public R userInfo(@LoginUser UserEntity user){
return R.ok().put("user", user);
}
}
/**
* 有@LoginUser注解的方法参数,注入当前登录用户
* @author chenshun
* @email sunlightcs@gmail.com
* @date 2017-03-23 22:02
*/
@Component
public class LoginUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Autowired
private UserService userService;
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().isAssignableFrom(UserEntity.class) && parameter.hasParameterAnnotation(LoginUser.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container,
NativeWebRequest request, WebDataBinderFactory factory) throws Exception {
//获取用户ID
Object object = request.getAttribute(AuthorizationInterceptor.USER_KEY, RequestAttributes.SCOPE_REQUEST);
if(object == null){
return null;
}
//获取用户信息
UserEntity user = userService.selectById((Long)object);
return user;
}
}
/**
* MVC配置
*
* @author chenshun
* @email sunlightcs@gmail.com
* @date 2017-04-20 22:30
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private LoginUserHandlerMethodArgumentResolver loginUserHandlerMethodArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(loginUserHandlerMethodArgumentResolver);
}
}
* Abstract {@link javax.sql.DataSource} implementation that routes {@link #getConnection()}
* calls to one of various target DataSources based on a lookup key. The latter is usually
* (but not necessarily) determined through some thread-bound transaction context.
通过这我们知道可以实现:
实现多数据源
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--mybatis plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
@MapperScan("com.example.mapper")
@SpringBootApplication
public class DatasourceDemoApplication {
public static void main(String[] args) {
SpringApplication.run(DatasourceDemoApplication.class, args);
}
}
# DataSource Config
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/renren_fast
username: root
password: admin
@RunWith(SpringRunner.class)
@SpringBootTest
public class DatasourceDemoApplicationTests {
@Autowired
SysUserService userService;
@Test
public void contextLoads() {
SysUser user = userService.getById(1);
System.out.println(user.toString());
}
}
步骤1,在spring boot中,增加多数据源的配置
步骤2,扩展Spring的AbstractRoutingDataSource抽象类,
AbstractRoutingDataSource中的抽象方法determineCurrentLookupKey是实现多数据
源的核心,并对该方法进行Override
步骤3,配置DataSource,指定数据源的信息
步骤4,通过注解,实现多数据源
步骤5、配置加上(exclude={DataSourceAutoConfiguration.class})
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
druid:
first:
url: jdbc:mysql://localhost:3306/renren_fast?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8
username: root
password: admin
second:
url: jdbc:mysql://localhost:3306/renren_fast2?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8
username: root
password: admin
/**
* 配置多数据源
* @author chenshun
* @email sunlightcs@gmail.com
* @date 2017/8/19 0:41
*/
@Configuration
public class DynamicDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.druid.first")
public DataSource firstDataSource(){
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.druid.second")
public DataSource secondDataSource(){
return DruidDataSourceBuilder.create().build();
}
}
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
@Import({DynamicDataSourceConfig.class})
/**
* 动态数据源
* determineCurrentLookupKey()决定使用哪个数据源
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return null;
}
}
/**
* 动态数据源
* determineCurrentLookupKey()决定使用哪个数据源
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return getDataSource();
}
/*
*ThreadLocal 用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。
* 也就是说 ThreadLocal 可以为每个线程创建一个【单独的变量副本】
* 相当于线程的 private static 类型变量。
*/
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSource(String dataSource) {
contextHolder.set(dataSource);
}
public static String getDataSource() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}
/**
* 决定使用哪个数据源之前需要把多个数据源的信息以及默认数据源信息配置好
* @param defaultTargetDataSource
* @param targetDataSources
*/
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Bean
@Primary
public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceNames.FIRST, firstDataSource);
targetDataSources.put(DataSourceNames.SECOND, secondDataSource);
return new DynamicDataSource(firstDataSource, targetDataSources);
}
@Primary 优先考虑,优先考虑被注解的对象注入。
/**
* 增加多数据源,在此配置
*
* @author chenshun
* @email sunlightcs@gmail.com
* @date 2017/8/18 23:46
*/
public interface DataSourceNames {
String FIRST = "first";
String SECOND = "second";
}
注解:
/**
* 多数据源注解
* @author chenshun
* @email sunlightcs@gmail.com
* @date 2017/9/16 22:16
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String name() default "";
}
切面处理逻辑:
/**
* 多数据源,切面处理类
* @author chenshun
* @email sunlightcs@gmail.com
* @date 2017/9/16 22:20
*/
@Aspect
@Component
public class DataSourceAspect implements Ordered {
protected Logger logger = LoggerFactory.getLogger(getClass());
@Pointcut("@annotation(com.example.datasource.DataSource)")
public void dataSourcePointCut() {
}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DataSource ds = method.getAnnotation(DataSource.class);
if(ds == null){
DynamicDataSource.setDataSource(DataSourceNames.FIRST);
logger.debug("set datasource is " + DataSourceNames.FIRST);
}else {
DynamicDataSource.setDataSource(ds.name());
logger.debug("set datasource is " + ds.name());
}
try {
return point.proceed();
} finally {
DynamicDataSource.clearDataSource();
logger.debug("clean datasource");
}
}
@Override
public int getOrder() {
return 1;
}
}
public interface SysUserService extends IService<SysUser> {
SysUser findUserByFirstDb(long id);
SysUser findUserBySecondDb(long id);
}
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
@Override
public SysUser findUserByFirstDb(long id) {
return this.baseMapper.selectById(id);
}
@DataSource(name = DataSourceNames.SECOND)
@Override
public SysUser findUserBySecondDb(long id) {
return this.baseMapper.selectById(id);
}
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class DatasourceDemoApplicationTests {
@Autowired
SysUserService userService;
@Test
public void contextLoads() {
SysUser user = userService.getById(1);
System.out.println(user.toString());
}
@Test
public void test() {
SysUser user = userService.findUserByFirstDb(1);
System.out.println("第one个数据库---------》" + user.toString());
SysUser user2 = userService.findUserBySecondDb(1);
System.out.println("第二个数据库---------》" + user2.toString());
}
}