1. liugh-parent源码研究参考

1.1. 前言

  • 这也是个开源的springboot脚手架项目,这里研究记录一些该框架写的比较好的代码段和功能
  • 脚手架地址

1.2. 功能

1.2.1. 当前用户

  • 这里它用了注解切面进行登录用户的统一注入入口参数,这个做法可以进行参考,不需要在需要使用到登录用户的地方用对象去取了
import com.liugh.annotation.CurrentUser;
import com.liugh.exception.UnauthorizedException;
import com.liugh.entity.User;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer; /**
* 增加方法注入,将含有 @CurrentUser 注解的方法参数注入当前登录用户
* @author liugh
* @since 2018-05-03
*/
public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().isAssignableFrom(User.class)
&& parameter.hasParameterAnnotation(CurrentUser.class);
} @Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
User user = (User) webRequest.getAttribute("currentUser", RequestAttributes.SCOPE_REQUEST);
if (user == null) {
throw new UnauthorizedException("获取用户信息失败");
}
return user;
}
}
/**
* 身份认证异常
* @author liugh
* @since 2018-05-06
*/
public class UnauthorizedException extends RuntimeException {
public UnauthorizedException(String msg) {
super(msg);
} public UnauthorizedException() {
super();
}
}
/**
* 在Controller的方法参数中使用此注解,该方法在映射时会注入当前登录的User对象
* @author : liugh
* @date : 2018/05/08
*/
@Target(ElementType.PARAMETER) // 可用在方法的参数上
@Retention(RetentionPolicy.RUNTIME) // 运行时有效
public @interface CurrentUser {
}
  • 注入解析对象
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import java.util.List; /**
* @author liugh
* @since 2018-05-03
*/
@Configuration
public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
super.addInterceptors(registry);
} @Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(currentUserMethodArgumentResolver());
super.addArgumentResolvers(argumentResolvers);
} @Bean
public CurrentUserMethodArgumentResolver currentUserMethodArgumentResolver() {
return new CurrentUserMethodArgumentResolver();
}
}

1.2.2. 令牌桶

  • 这是一种限流思路,令牌桶算法请自行百度,这里也不是从零实现,用到了guava中的RateLimiter限流器
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.RateLimiter;
import com.liugh.annotation.AccessLimit;
import com.liugh.base.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit; /**
* 限流切面
* Created by liugh on 2018/10/12.
*/
@Slf4j
public class AccessLimitAspect extends AbstractAspectManager{ public AccessLimitAspect(AspectApi aspectApi){
super(aspectApi);
} @Override
public Object doHandlerAspect(ProceedingJoinPoint pjp, Method method)throws Throwable {
super.doHandlerAspect(pjp,method);
execute(pjp,method);
return null;
} //添加速率.保证是单例的
private static RateLimiter rateLimiter = RateLimiter.create(1000);
//使用url做为key,存放令牌桶 防止每次重新创建令牌桶
private static Map<String, RateLimiter> limitMap = Maps.newConcurrentMap(); @Override
public Object execute(ProceedingJoinPoint pjp,Method method) throws Throwable{
AccessLimit lxRateLimit = method.getAnnotation(AccessLimit.class);
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 或者url(存在map集合的key)
String url = request.getRequestURI();
if (!limitMap.containsKey(url)) {
// 创建令牌桶
rateLimiter = RateLimiter.create(lxRateLimit.perSecond());
limitMap.put(url, rateLimiter);
log.info("<<================= 请求{},创建令牌桶,容量{} 成功!!!",url,lxRateLimit.perSecond());
}
rateLimiter = limitMap.get(url);
if (!rateLimiter.tryAcquire(lxRateLimit.timeOut(), lxRateLimit.timeOutUnit())) {//获取令牌
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
log.info("Error ---时间:{},获取令牌失败.", sdf.format(new Date()));
throw new BusinessException("服务器繁忙,请稍后再试!");
}
return null;
}
}
  • 切面
import com.liugh.annotation.AccessLimit;
import com.liugh.annotation.Log;
import com.liugh.annotation.ParamXssPass;
import com.liugh.annotation.ValidationParam;
import com.liugh.aspect.*;
import com.liugh.util.ComUtil;
import com.liugh.util.StringUtil;
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.context.annotation.Configuration; import java.lang.reflect.Method; /**
* 切面:防止xss攻击 记录log 参数验证
* @author liugh
* @since 2018-05-03
*/
@Aspect
@Configuration
public class ControllerAspect { @Pointcut("execution(* com.liugh.controller..*(..)) ")
public void aspect() {
} @Around(value = "aspect()")
public Object validationPoint(ProceedingJoinPoint pjp)throws Throwable{
Method method = currentMethod(pjp,pjp.getSignature().getName());
//创建被装饰者
AspectApiImpl aspectApi = new AspectApiImpl();
//是否需要验证参数
if (!ComUtil.isEmpty(StringUtil.getMethodAnnotationOne(method, ValidationParam.class.getSimpleName()))) {
new ValidationParamAspect(aspectApi).doHandlerAspect(pjp,method);
}
//是否需要限流
if (method.isAnnotationPresent(AccessLimit.class)) {
new AccessLimitAspect(aspectApi).doHandlerAspect(pjp,method);
}
//是否需要拦截xss攻击
if(method.isAnnotationPresent( ParamXssPass.class )){
new ParamXssPassAspect(aspectApi).doHandlerAspect(pjp,method);
}
//是否需要记录日志
if(method.isAnnotationPresent(Log.class)){
return new RecordLogAspect(aspectApi).doHandlerAspect(pjp,method);
}
return pjp.proceed(pjp.getArgs());
} /**
* 获取目标类的所有方法,找到当前要执行的方法
*/
private Method currentMethod ( ProceedingJoinPoint joinPoint , String methodName ) {
Method[] methods = joinPoint.getTarget().getClass().getMethods();
Method resultMethod = null;
for ( Method method : methods ) {
if ( method.getName().equals( methodName ) ) {
resultMethod = method;
break;
}
}
return resultMethod;
} }
  • 上面的代码还包含了参数验证,xss攻击拦截,日志记录,这些比较常用功能,感兴趣的自行下载代码浏览细节

1.2.3. 异步日志记录

  • 日志的异步记录,这是个好思路,日志记录不影响主任务,可以改成异步加快速度
/**
* 线程池配置、启用异步
*
* @author liugh
*
*/
//开启异步
@EnableAsync(proxyTargetClass = true)
@Configuration
public class AsycTaskExecutorConfig { @Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//核心线程数
taskExecutor.setCorePoolSize(50);
//最大线程数
taskExecutor.setMaxPoolSize(100);
return taskExecutor;
}
}
  • 日志切面
import java.lang.reflect.Method;
import java.util.Map; import com.alibaba.fastjson.JSONObject;
import com.liugh.annotation.Log;
import com.liugh.service.SpringContextBeanService;
import com.liugh.entity.OperationLog;
import com.liugh.service.IOperationLogService;
import com.liugh.util.ComUtil;
import com.liugh.util.JWTUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; /**
* 记录日志切面
* @author liugh
* @since on 2018/5/10.
*/
public class RecordLogAspect extends AbstractAspectManager { public RecordLogAspect(AspectApi aspectApi){
super(aspectApi);
} @Override
public Object doHandlerAspect(ProceedingJoinPoint pjp, Method method) throws Throwable{
super.doHandlerAspect(pjp,method);
return execute(pjp,method);
} private Logger logger = LoggerFactory.getLogger(RecordLogAspect.class); @Override
@Async
protected Object execute(ProceedingJoinPoint pjp, Method method) throws Throwable{
Log log = method.getAnnotation( Log.class );
// 异常日志信息
String actionLog = null;
StackTraceElement[] stackTrace =null;
// 是否执行异常
boolean isException = false;
// 接收时间戳
long endTime;
// 开始时间戳
long operationTime = System.currentTimeMillis();
try {
return pjp.proceed(pjp.getArgs());
} catch ( Throwable throwable ) {
isException = true;
actionLog = throwable.getMessage();
stackTrace = throwable.getStackTrace();
throw throwable;
} finally {
// 日志处理
logHandle( pjp , method , log , actionLog , operationTime , isException,stackTrace );
}
} private void logHandle (ProceedingJoinPoint joinPoint ,
Method method ,
Log log ,
String actionLog ,
long startTime ,
boolean isException,
StackTraceElement[] stackTrace) {
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
IOperationLogService operationLogService = SpringContextBeanService.getBean(IOperationLogService.class);
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
String authorization = request.getHeader("Authorization");
OperationLog operationLog = new OperationLog();
if(!ComUtil.isEmpty(authorization)){
String userNo = JWTUtil.getUserNo(authorization);
operationLog.setUserNo(userNo);
}
operationLog.setIp(getIpAddress(request));
operationLog.setClassName(joinPoint.getTarget().getClass().getName() );
operationLog.setCreateTime(startTime);
operationLog.setLogDescription(log.description());
operationLog.setModelName(log.modelName());
operationLog.setAction(log.action());
if(isException){
StringBuilder sb = new StringBuilder();
sb.append(actionLog+" ");
for (int i = 0; i < stackTrace.length; i++) {
sb.append(stackTrace[i]+" ");
}
operationLog.setMessage(sb.toString());
}
operationLog.setMethodName(method.getName());
operationLog.setSucceed(isException == true ? 2:1);
Object[] args = joinPoint.getArgs();
StringBuilder sb = new StringBuilder();
boolean isJoint = false;
for (int i = 0; i < args.length; i++) {
if(args[i] instanceof JSONObject){
JSONObject parse = (JSONObject)JSONObject.parse(args[i].toString());
if(!ComUtil.isEmpty(parse.getString("password"))){
parse.put("password","*******");
}
if(!ComUtil.isEmpty(parse.getString("rePassword"))){
parse.put("rePassword","*******");
}
if(!ComUtil.isEmpty(parse.getString("oldPassword"))){
parse.put("oldPassword","*******");
}
operationLog.setActionArgs(parse.toString());
}else if(args[i] instanceof String
|| args[i] instanceof Long
|| args[i] instanceof Integer
|| args[i] instanceof Double
|| args[i] instanceof Float
|| args[i] instanceof Byte
|| args[i] instanceof Short
|| args[i] instanceof Character){
isJoint=true;
}
else if(args[i] instanceof String []
|| args[i] instanceof Long []
|| args[i] instanceof Integer []
|| args[i] instanceof Double []
|| args[i] instanceof Float []
|| args[i] instanceof Byte []
|| args[i] instanceof Short []
|| args[i] instanceof Character []){
Object[] strs = (Object[])args[i];
StringBuilder sbArray =new StringBuilder();
sbArray.append("[");
for (Object str:strs) {
sbArray.append(str.toString()+",");
}
sbArray.deleteCharAt(sbArray.length()-1);
sbArray.append("]");
operationLog.setActionArgs(sbArray.toString());
}else {
continue;
}
}
if(isJoint){
Map<String, String[]> parameterMap = request.getParameterMap();
for (String key:parameterMap.keySet()) {
String[] strings = parameterMap.get(key);
for (String str:strings) {
sb.append(key+"="+str+"&");
}
}
operationLog.setActionArgs(sb.deleteCharAt(sb.length()-1).toString());
}
logger.info("执行方法信息:"+JSONObject.toJSON(operationLog));
operationLogService.insert(operationLog);
} private String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip+":"+request.getRemotePort();
}
}

1.2.4. 启动初始化扫描

  • 启动时扫描对应包,然后做相应处理,这里是把对应接口记录后用于后续pass,也就是通过@Pass不认证处理
import com.liugh.annotation.Pass;
import com.liugh.base.Constant;
import com.liugh.util.ComUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*; import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile; /**
* @author liugh
* @Since 2018-05-10
*/
@Component
//日志打印 log.info
@Slf4j
public class MyCommandLineRunner implements CommandLineRunner { @Value("${controller.scanPackage}")
private String scanPackage; @Override
public void run(String... args) throws Exception {
doScanner(scanPackage);
Set<String> urlAndMethodSet =new HashSet<>();
for (String aClassName:Constant.METHOD_URL_SET) {
Class<?> clazz = Class.forName(aClassName);
String baseUrl = "";
String[] classUrl ={};
if(!ComUtil.isEmpty(clazz.getAnnotation(RequestMapping.class))){
classUrl=clazz.getAnnotation(RequestMapping.class).value();
}
Method[] methods = clazz.getMethods();
for (Method method:methods) {
if(method.isAnnotationPresent(Pass.class)){
String [] methodUrl = null;
StringBuilder sb =new StringBuilder();
if(!ComUtil.isEmpty(method.getAnnotation(PostMapping.class))){
methodUrl=method.getAnnotation(PostMapping.class).value();
if(ComUtil.isEmpty(methodUrl)){
methodUrl=method.getAnnotation(PostMapping.class).path();
}
baseUrl=getRequestUrl(classUrl, methodUrl, sb,"POST");
}else if(!ComUtil.isEmpty(method.getAnnotation(GetMapping.class))){
methodUrl=method.getAnnotation(GetMapping.class).value();
if(ComUtil.isEmpty(methodUrl)){
methodUrl=method.getAnnotation(GetMapping.class).path();
}
baseUrl=getRequestUrl(classUrl, methodUrl, sb,"GET");
}else if(!ComUtil.isEmpty(method.getAnnotation(DeleteMapping.class))){
methodUrl=method.getAnnotation(DeleteMapping.class).value();
if(ComUtil.isEmpty(methodUrl)){
methodUrl=method.getAnnotation(DeleteMapping.class).path();
}
baseUrl=getRequestUrl(classUrl, methodUrl, sb,"DELETE");
}else if(!ComUtil.isEmpty(method.getAnnotation(PutMapping.class))){
methodUrl=method.getAnnotation(PutMapping.class).value();
if(ComUtil.isEmpty(methodUrl)){
methodUrl=method.getAnnotation(PutMapping.class).path();
}
baseUrl=getRequestUrl(classUrl, methodUrl, sb,"PUT");
}else {
methodUrl=method.getAnnotation(RequestMapping.class).value();
baseUrl=getRequestUrl(classUrl, methodUrl, sb,RequestMapping.class.getSimpleName());
}
if(!ComUtil.isEmpty(baseUrl)){
urlAndMethodSet.add(baseUrl);
}
}
}
}
Constant.METHOD_URL_SET=urlAndMethodSet;
log.info("@Pass:"+urlAndMethodSet);
} private String getRequestUrl(String[] classUrl, String[] methodUrl, StringBuilder sb,String requestName) {
sb.append("/api/v1");
if(!ComUtil.isEmpty(classUrl)){
for (String url:classUrl) {
sb.append(url+"/");
}
}
for (String url:methodUrl) {
sb.append(url);
}
if(sb.toString().endsWith("/")){
sb.deleteCharAt(sb.length()-1);
}
return sb.toString().replaceAll("/+", "/")+":--:"+requestName;
} private void doScanner(String packageName) {
//把所有的.替换成/
URL url =this.getClass().getClassLoader().getResource(packageName.replaceAll("\\.", "/"));
// 是否循环迭代
if(StringUtils.countMatches(url.getFile(), ".jar")>0){
boolean recursive=true;
JarFile jar;
// 获取jar
try {
jar = ((JarURLConnection) url.openConnection())
.getJarFile();
// 从此jar包 得到一个枚举类
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
JarEntry entry = entries.nextElement();
String name = entry.getName();
// 如果是以/开头的
if (name.charAt(0) == '/') {
// 获取后面的字符串
name = name.substring(1);
}
// 如果前半部分和定义的包名相同
if (name.startsWith(packageName.replaceAll("\\.","/"))) {
int idx = name.lastIndexOf('/');
// 如果以"/"结尾 是一个包
if (idx != -1) {
// 获取包名 把"/"替换成"."
packageName = name.substring(0, idx)
.replace('/', '.');
}
// 如果可以迭代下去 并且是一个包
if ((idx != -1) || recursive) {
// 如果是一个.class文件 而且不是目录
if (name.endsWith(".class")
&& !entry.isDirectory()) {
// 去掉后面的".class" 获取真正的类名
String className = name.substring(
packageName.length() + 1, name
.length() - 6);
try {
// 添加到classes
Constant.METHOD_URL_SET.add(Class
.forName(packageName + '.'
+ className).getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
return;
} catch (IOException e) {
e.printStackTrace();
}
}
File dir = new File(url.getFile());
for (File file : dir.listFiles()) {
if(file.isDirectory()){
//递归读取包
doScanner(packageName+"."+file.getName());
}else{
String className =packageName +"." +file.getName().replace(".class", "");
Constant.METHOD_URL_SET.add(className);
}
}
} }

1.2.5. redis缓存用法

  • 缓存wiselyKeyGenerator具体用法,之前没了解到这个的具体用法,这次知道了
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import java.lang.reflect.Method; /**
* @author liugh
* @since on 2018/5/11.
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport { /*定义缓存数据 key 生成策略的bean
包名+类名+方法名+所有参数
*/
@Bean("wiselyKeyGenerator")
public KeyGenerator wiselyKeyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getSimpleName()+":");
sb.append(method.getName()+":");
for (Object obj : params) {
sb.append(obj.toString()+":");
}
return sb.deleteCharAt(sb.length()-1).toString();
}
}; } /*要启用spring缓存支持,需创建一个 CacheManager的 bean,CacheManager 接口有很多实现,这里Redis 的集成,用 RedisCacheManager这个实现类
Redis 不是应用的共享内存,它只是一个内存服务器,就像 MySql 似的,
我们需要将应用连接到它并使用某种“语言”进行交互,因此我们还需要一个连接工厂以及一个 Spring 和 Redis 对话要用的 RedisTemplate,
这些都是 Redis 缓存所必需的配置,把它们都放在自定义的 CachingConfigurerSupport 中
*/
@Bean
public CacheManager cacheManager(
@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
// cacheManager.setDefaultExpiration(60);//设置缓存保留时间(seconds)
return cacheManager;
}
// @Bean springboot 2.0
// public CacheManager cacheManager(
// @SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {
// // 初始化缓存管理器,在这里我们可以缓存的整体过期时间什么的,我这里默认没有配置
// RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager
// .RedisCacheManagerBuilder
// .fromConnectionFactory(jedisConnectionFactory);
// return builder.build();
// } //1.项目启动时此方法先被注册成bean被spring管理
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
  • 使用
    @Override
//redis方法级别的缓存,需要做缓存打开改注解即可
@Cacheable(value = "UserToRole",keyGenerator="wiselyKeyGenerator")
public List<Menu> selectByIds(List<Integer> permissionIds) {
EntityWrapper<Menu> ew = new EntityWrapper<>();
ew.in("menu_id", permissionIds);
return this.selectList(ew);
}

springboot脚手架liugh-parent源码研究参考的更多相关文章

  1. 从源码研究如何不重启Springboot项目实现redis配置动态切换

    上一篇Websocket的续篇暂时还没有动手写,这篇算是插播吧.今天讲讲不重启项目动态切换redis服务. 背景 多个项目或微服务场景下,各个项目都需要配置redis数据源.但是,每当运维搞事时(修改 ...

  2. zepto源码研究 - zepto.js - 1

    简要:网上已经有很多人已经将zepto的源码研究得很细致了,但我还是想写下zepto源码系列,将别人的东西和自己的想法写下来以加深印象也是自娱自乐,文章中可能有许多错误,望有人不吝指出,烦请赐教. 首 ...

  3. underscore.js源码研究(6)

    概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...

  4. 阿里sentinel源码研究深入

    1. 阿里sentinel源码研究深入 1.1. 前言 昨天已经把sentinel成功部署到线上环境,可参考我上篇博文,该走的坑也都走了一遍,已经可以初步使用它的限流和降级功能,根据我目前的实践,限流 ...

  5. OAuth2学习及DotNetOpenAuth部分源码研究

    OAuth2学习及DotNetOpenAuth部分源码研究 在上篇文章中我研究了OpenId及DotNetOpenAuth的相关应用,这一篇继续研究OAuth2. 一.什么是OAuth2 OAuth是 ...

  6. Android开源项目 Universal imageloader 源码研究之Lru算法

    https://github.com/nostra13/Android-Universal-Image-Loader universal imageloader 源码研究之Lru算法 LRU - Le ...

  7. dubbo源码研究(一)

    1. dubbo源码研究(一) 1.1. dubbo启动加载过程 我们知道,现在流行注解方式,用spring管理服务,dubbo最常用的就是@Reference和@Service了,那么我首先找到这两 ...

  8. 【JavaScript】$.extend使用心得及源码研究

    最近写多了js的面向对象编程,用$.extend写继承写得很顺手.但是在使用过程中发现有几个问题. 1.深拷贝 $.extend默认是浅拷贝,这意味着在继承复杂对象时,对象中内嵌的对象无法被拷贝到. ...

  9. list源码4(参考STL源码--侯捷):transfer、splice、merge、reverse、sort

    list源码1(参考STL源码--侯捷):list节点.迭代器.数据结构 list源码2(参考STL源码--侯捷):constructor.push_back.insert list源码3(参考STL ...

随机推荐

  1. NOIP 2014 比例简化

    洛谷 P2118 比例简化 洛谷传送门 JDOJ 2892: [NOIP2014]比例简化 T2 JDOJ传送门 Description 在社交媒体上,经常会看到针对某一个观点同意与否的民意调查以及结 ...

  2. Centos7安装MySQL(多图)

    文章目录 一.在线安装1.替换网易yum源2.清理缓存3.下载rpm文件4.安装MySQL数据库二.本地安装1.上传MySQL安装包2.安装依赖的程序包3.卸载mariadb程序包4.安装MySQL程 ...

  3. windows10下Docker开启nginx服务访问页面没有反应

    网址不要输入localhost,而是输入docker quickstart terminal登陆成功后给你的ip地址.

  4. Native Clojure with GraalVM

    转自:https://www.innoq.com/en/blog/native-clojure-and-graalvm/ GraalVM is a fascinating piece of techn ...

  5. GLIBC中的库函数fflush究竟做了什么?

    目录 目录 1 1. 库函数fflush原型 1 2. FILE结构体 1 3. fflush函数实现 2 4. fclose函数实现 4 附1:强弱函数名 5 附2:属性__visibility__ ...

  6. 关于微信订阅号里自动回复里的a链接的问题

    前阵子做了一个微信订阅号的活动,然后发现一个问题:就是回复内容里的a标签微信没有解析出来,而是这样 正常应该是这样: 具体出现这种情况的手机有: 魅族的型号是:M1 metal小米的型号是:MI 5X ...

  7. Redis Zrevrank 命令

    Redis Zrevrank 命令返回有序集中成员的排名.其中有序集成员按分数值递减(从大到小)排序. 排名以 0 为底,也就是说, 分数值最大的成员排名为 0 . 使用 ZRANK 命令可以获得成员 ...

  8. jenkins pipeline使用方式

    pipeline 使用 使用groovy的一种DSL语言,流程控制 pipeline脚本同其他脚本语言一样,从上到下顺序执行,它的流程控制取决于Groovy表达式,为jenkins用户提供了更巨大的灵 ...

  9. 【bat】判断字符串是否包含某字符串

    @echo off set a=55544333 set c=6666dfsfds set b=44 echo %a%| findstr %b% >nul && ( echo % ...

  10. [转帖]k8s 基本使用(下)

    k8s 基本使用(下) https://www.jianshu.com/p/116ce601a60f 如果你没有看过上篇的话,推荐阅读完 k8s 基本使用(上)后再阅读本篇内容. kubectl cr ...