(二期)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());
}
}