文章目录

  • 项目总结
  • 后端基础配置
    • 1、MybatisPlus
    • 2、JWT
    • 3、Redis
    • 4、ThreadLocal
    • 5、拦截器
    • 6、线程池
    • 7、AOP
      • 日志
      • 缓存
    • 8、七牛云
    • 9、SpringSecurity

项目总结

SpringBoot+MybatisPlus+Redis+Vue+SpringSecurity 前后端分离个人博客

  • 采用前后端分离,前端提供接口,后端根据接口开发,加上后台管理系统
  • 采用MybatisPlus优化简化sql,简化代码
  • 采用JWT来存储用户信息,将token放到Redis中,防止过多session对服务端造成性能问题,将token放到请求头中,下次请求需要用户信息的接口直接访问redis中,避免与数据库过多交互。并且采用ThreadLocal保存用户信息,在登陆成功后存入用户,使线程全局私有用户信息,比如写文章的时候直接从ThreadLocal中拿取用户信息,并且要及时移除用户信息,避免内存泄露。
  • 采用拦截器,拦截需要登陆访问的接口
  • 采用线程池,更新阅读次数,主要是防止更新阻塞其他的读操作,因为更新操作有锁,性能就会比较低,所以更新阅读数扔到线程池中去执行,这样不会影响主线程的操作了。
  • 采用AOP实现缓存和日志功能,在接口上加上缓存减少与数据库的交互,在接口上加上日志,在我们排错的时候可以快速定位
  • 采用七牛云来存放我们的静态资源,加快博客的访问速度,降低我们自身应用服务器的带宽消耗
  • 采用SpringSecurity 来实现后台管理系统的认证和授权,来对用户的统一管理

后端基础配置

1、MybatisPlus

配置

spring.datasource.url=jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=UTF-8&serverTimeZone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Drivermybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 与数据库操作的时候自动在表前面加上ms_
mybatis-plus.global-config.db-config.table-prefix=ms_
@Configuration
//扫包,将此包下的接口生成代理实现类,并且注册到spring容器中
@MapperScan("com.liu.blog.dao.mapper")
public class MybatisPlusConfig {//分页插件@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor());return interceptor;}
}
  • 当我们使用MybatisPlus的插入方法,如果数据库采用的自增id,那么需要在实体id加上@TableId(value ="id", type = IdType.AUTO),不然他会按照mybatis-plus方式设置的主键

  • 可以使用LambdaQueryWrapper条件构造器来替换QueryWrapper,前者可以跟好的使用lambda方法传参

  • LambdaQueryWrapper<Tag> tagLambdaQueryWrapper = new LambdaQueryWrapper<>();
    tagLambdaQueryWrapper.eq(Tag::getId,id);
    tagLambdaQueryWrapper.last("limit "+1); // 表示在最后加上 limit 1;
    ======================================================
    QueryWrapper<Tag> tagQueryWrapper = new QueryWrapper<>();
    tagQueryWrapper.eq("id",id);
    tagQueryWrapper.last("limit "+1);
  • 多表查询需要自定义sql

select id ,tag_name from ms_tag where id in(select tag_id from(select tag_id  from ms_article_tag group by tag_id order by count(tag_id)  desc limit 2) as aliasA)
# 这里as aliasA 和select tag_id from 是关键,相当于把查询结果当作一个新表去查询,
# 当然多表查询也可以采用map映射
  • 按多个属性递减
select FROM_UNIXTIME(create_date/1000,'%Y') as year,FROM_UNIXTIME(create_date/1000,'%m') as month,count(*) as count from ms_article group by year,month order by year desc,month desc
# order by year desc,month desc
  • 赋值两个对象,BeanUtils.copyProperties(article,articleVo),将article与articleVo属性类型相同的复制给articleVo

    不同的属性类型,比如vo中id是String,则要手动articleVo.setId(String.valueOf(article.getId()))

    String.valueOf(article.getId()) // 可以避免空指针异常。article.getId().toString不能
    
  • 当参数时集合的时候

    # 根据tagIds数组中的id返回其id对应的List<Tag>
    <select id="findTagsByTagIds" parameterType="list" resultType="com.liu.blog.dao.pojo.Tag">select id,tag_name as tagName from ms_tagwhere id in<foreach collection="tagIds" item="tagId" separator="," open="(" close=")">#{tagId}</foreach>
    </select>
    

2、JWT

登陆成功创建token,存放我们的用户信息,替换session,避免过多session对服务器造成压力

public class JWTUtils {// 设置秘钥,用于加密解密private static final String jwtToken = "123456liu!@#$$";// 根据用户id创建tokenpublic static String createToken(Long userId){// 将用户id以map的形式封装,取的时候好取Map<String,Object> claims = new HashMap<>();claims.put("userId",userId);JwtBuilder jwtBuilder = Jwts.builder().signWith(SignatureAlgorithm.HS256, jwtToken) // 签发算法,秘钥为jwtToken.setClaims(claims) // body数据,要唯一,自行设置.setIssuedAt(new Date()) // 设置签发时间.setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 60 * 1000));// 一天的有效时间String token = jwtBuilder.compact();// 将所有的拼接成最终的tokenreturn token;}public static Map<String, Object> checkToken(String token){try {Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token);return (Map<String, Object>) parse.getBody();}catch (Exception e){e.printStackTrace();}return null;}// 检测token是否好使// @Testpublic void parse(){String token = createToken(12l);Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token);Map<String, Object> body = (Map<String, Object>) parse.getBody();System.out.println(body.get("userId"));System.out.println(parse);/*** 12* header={alg=HS256},body={exp=1639296423, userId=12, iat=1638407390},* signature=MYtDVcNccgWgEHROEG3nLW1jLfQzyWjCdiLtW4UnDs4      */}}

使用

String token = JWTUtils.createToken(sysUser.getId());

3、Redis

登陆或注册成功,存放我们的token和对应的用户到redis中,这样下次请求获取当前用户的接口的时候传入token,根据token去redis中查询,减少与数据库的交互,比如拦截器中传入token判断是否有用户信息,这时候就直接去redis中判断,并且创建用户、创建token、存放redis时原子操作,所以可以加事务在类上加:@Transactional 注解即可

配置

# redis的接口配置
spring.redis.host=localhost
spring.redis.port=6379
// 这里我们可以将token和user存到redis中,下次再请求token的时候,可以直接从redis中获取user,不用再解析token然后再去数据库中取
redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS);

退出登陆的时候移除即可

redisTemplate.delete("TOKEN_"+token);

4、ThreadLocal

存放我们的用户,线程隔离,比如当我们写文章,先要被拦截,然后登陆后就会将user放到ThreadLocal中,使此次线程全局共享这个user,比如写好文章将文章参数传到后端此时就可以直接去ThreadLocal中拿到user来创建文章。

/*** @author ljy* @version 1.0.0* @ClassName 全局的user信息* @Description TODO* @createTime 2021年12月02日 13:46:00*/
public class UserThreadLocal {private UserThreadLocal(){}//线程变量隔离private static final ThreadLocal<SysUser> LOCAL = new ThreadLocal<>();public static void put(SysUser sysUser){LOCAL.set(sysUser);}public static SysUser get(){return LOCAL.get();}public static void remove(){LOCAL.remove();}
}
UserThreadLocal.put(sysUser);

5、拦截器

主要是为了拦截一些需要登陆过后才能操作的接口,比如写文章。评论等

配置

@Configuration
public class WebMVCConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addCorsMappings(CorsRegistry registry) {//跨域配置,前后端分离端口不同,所以要配置registry.addMapping("/**").allowedOrigins("http://localhost:8080");//        registry.addMapping("/**")
//                .allowedOriginPatterns("*")
//                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
//                .allowCredentials(true)
//                .maxAge(3600)
//                .allowedHeaders("*");}@Overridepublic void addInterceptors(InterceptorRegistry registry) {//拦截test接口,后续实际遇到需要拦截的接口时,再配置为真正的拦截接口registry.addInterceptor(loginInterceptor).addPathPatterns("/test").addPathPatterns("/comments/create/change") //评论前要先登录.addPathPatterns("/articles/publish"); // 写文章前也要登录}}

使用:自动执行,当访问我们拦截的接口

@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {@Autowiredprivate LoginService loginService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//在执行controller方法(Handler)之前进行执行/*** 1. 需要判断 请求的接口路径 是否为 HandlerMethod (controller方法)* 2. 判断 token是否为空,如果为空 未登录* 3. 如果token 不为空,登录验证 loginService checkToken* 4. 如果认证成功 放行即可*/if (!(handler instanceof HandlerMethod)){//handler 可能是 RequestResourceHandler springboot 程序 访问静态资源 默认去classpath下的static目录去查询return true;}String token = request.getHeader("Authorization");log.info("=================request start===========================");String requestURI = request.getRequestURI();log.info("request uri:{}",requestURI);log.info("request method:{}",request.getMethod());log.info("token:{}", token);log.info("=================request end===========================");if (StringUtils.isBlank(token)){Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");response.setContentType("application/json;charset=utf-8");response.getWriter().print(JSON.toJSONString(result));return false;}SysUser sysUser = loginService.checkToken(token);if (sysUser == null){Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");response.setContentType("application/json;charset=utf-8");response.getWriter().print(JSON.toJSONString(result));return false;}//登录验证成功,放行//我希望在controller中 直接获取用户的信息 怎么获取?UserThreadLocal.put(sysUser);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//如果不删除 ThreadLocal中用完的信息 会有内存泄漏的风险UserThreadLocal.remove();}
}

6、线程池

当我们查询某一篇文章时,阅读数要相应增加,但是更新操作会加锁阻塞读操作,这样就会影响文章详情的响应,所以把更新操作交给线程池来做,这样就不会影响主线程的查询文章详情

/*** @author ljy1999* @version 1.0.0* @ClassName 线程池来更新阅读次数* @Description TODO* @createTime 2021年12月02日 14:50:00*/
@Configuration
@EnableAsync
public class ThreadPoolConfig {@Bean("taskExecutor")public Executor asyncServiceExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 设置核心线程数executor.setCorePoolSize(5);// 设置最大线程数executor.setMaxPoolSize(20);//配置队列大小executor.setQueueCapacity(Integer.MAX_VALUE);// 设置线程活跃时间(秒)executor.setKeepAliveSeconds(60);// 设置默认线程名称executor.setThreadNamePrefix("小刘博客项目");// 等待所有任务结束后再关闭线程池executor.setWaitForTasksToCompleteOnShutdown(true);//执行初始化executor.initialize();return executor;}
}
/*** @author ljy1999* @version 1.0.0* @ClassName ThreadService.java* @Description TODO* @createTime 2021年12月02日 14:51:00*/
@Service
public class ThreadService {//期望此操作在线程池 执行 不会影响原有的主线程@Async("taskExecutor")public void updateArticleViewCount(ArticleMapper articleMapper, Article article){// 修改文章Article articleUpdate = new Article();// 阅读加一articleUpdate.setViewCounts(article.getViewCounts() + 1);// 使用LambdaQueryWrapper,在eq中就可以直接用Article::getId形式,而不用去知道数据库中的字段是什么LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(Article::getId,article.getId());//设置一个ViewCounts 为了在多线程的环境下 线程安全queryWrapper.eq(Article::getViewCounts,article.getViewCounts());// 这个时候文章阅读已经被修改  update article set view_count=100 where view_count=99 and id=11articleMapper.update(articleUpdate,queryWrapper);
/*        try {//睡眠5秒 证明不会影响主线程的使用Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}*/}
}
//查看完文章了,新增阅读数,有没有问题呢?
//查看完文章之后,本应该直接返回数据了,这时候做了一个更新操作,更新时加写锁,阻塞其他的读操作,性能就会比较低
// 更新 增加了此次接口的 耗时 如果一旦更新出问题,不能影响 查看文章的操作
//线程池  可以把更新操作 扔到线程池中去执行,和主线程就不相关了
threadService.updateArticleViewCount(articleMapper,article);

7、AOP

日志

创建注解

/*** @author ljy* @version 1.0.0* @Description 日志注解* @createTime 2021年12月02日 22:39:00*/
//Type 代表可以放在类上面 Method 代表可以放在方法上
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented  // 这三个注解是固定的
public @interface LogAnnotation {// 模块名称 默认为空String module() default "";// 操作名称String operator() default "";
}

配置注解

/*** @author ljy* @version 1.0.0* @Description 日志切面* @createTime 2021年12月02日 22:40:00*/
@Aspect //切面 定义了通知和切点的关系
@Component
@Slf4j // 记录日志
public class LogAspect {// 定义切点 :com.liu.blog.common.aop.LogAnnotation// 切点是这个注解 就表示这个注解加到哪 哪就是切点@Pointcut("@annotation(com.liu.blog.common.aop.LogAnnotation)")public void logPointCut() {}// 通知类 标识切点logPointCut// 环绕通知@Around("logPointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {long beginTime = System.currentTimeMillis(); // 记录开始时间Object result = point.proceed();        //执行原有方法//执行时长(毫秒)long time = System.currentTimeMillis() - beginTime;//保存日志recordLog(point, time);return result;}// 记录日志private void recordLog(ProceedingJoinPoint joinPoint, long time) {// 拿到我们的方法,拿到对应的LogAnnotation注解MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);log.info("=====================log start================================");log.info("module:{}",logAnnotation.module());log.info("operation:{}",logAnnotation.operator());// 请求的方法名String className = joinPoint.getTarget().getClass().getName();String methodName = signature.getName();log.info("request method:{}",className + "." + methodName + "()");// 请求的参数Object[] args = joinPoint.getArgs();String params = JSON.toJSONString(args[0]);log.info("params:{}",params);// 获取request 设置IP地址HttpServletRequest request = HttpContextUtils.getHttpServletRequest();log.info("ip:{}", IpUtils.getIpAddr(request));// 记录执行时间log.info("excute time : {} ms",time);log.info("=====================log end================================");}}

使用

    /*** 首页 文章列表* @param pageParams* @return*/@PostMapping// 加上此注解 代表要对此接口记录日志@LogAnnotation(module="文章",operator="获取文章列表")public Result listArticle(@RequestBody PageParams pageParams){return articleService.listArticle(pageParams);}

缓存

创建注解

/*** @author ljy* @version 1.0.0* @Description TODO* @createTime 2021年12月03日 10:54:00*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Cache {// 过期时间long expire() default 1 * 60 * 1000;// 缓存标识 keyString name() default "";}

配置注解

/*** @author ljy* @version 1.0.0* @Description TODO* @createTime 2021年12月03日 10:55:00*/
@Aspect // aop 定义一个切面,切面定义了切点和通知的关系
@Component
@Slf4j
public class CacheAspect {@Autowiredprivate RedisTemplate<String, String> redisTemplate;// 切点@Pointcut("@annotation(com.liu.blog.common.cache.Cache)")public void pt(){}// 通知@Around("pt()")public Object around(ProceedingJoinPoint pjp){try {Signature signature = pjp.getSignature();//类名String className = pjp.getTarget().getClass().getSimpleName();//调用的方法名String methodName = signature.getName();// 参数类型Class[] parameterTypes = new Class[pjp.getArgs().length];// 请求的参数Object[] args = pjp.getArgs();//参数String params = "";for(int i=0; i<args.length; i++) {if(args[i] != null) {params += JSON.toJSONString(args[i]);parameterTypes[i] = args[i].getClass();}else {parameterTypes[i] = null;}}if (StringUtils.isNotEmpty(params)) {//加密 以防出现key过长以及字符转义获取不到的情况params = DigestUtils.md5Hex(params);}// 主要是为了拿到注解,拿到方法再去拿到注解Method method = pjp.getSignature().getDeclaringType().getMethod(methodName, parameterTypes);//获取Cache注解Cache annotation = method.getAnnotation(Cache.class);//缓存过期时间long expire = annotation.expire();//缓存名称String name = annotation.name();//先从redis获取 redisKey:注解名称+类名+方法名称+md5参数String redisKey = name + "::" + className+"::"+methodName+"::"+params;// 先去缓存中那,如果有就走缓存String redisValue = redisTemplate.opsForValue().get(redisKey);if (StringUtils.isNotEmpty(redisValue)){log.info("走了缓存~~~,{},{}",className,methodName);return JSON.parseObject(redisValue, Result.class);}// 调用我们的方法Object proceed = pjp.proceed();// 将方法返回的结果转为json 存到redis中redisTemplate.opsForValue().set(redisKey,JSON.toJSONString(proceed), Duration.ofMillis(expire));log.info("存入缓存~~~ {},{}",className,methodName);return proceed;} catch (Throwable throwable) {throwable.printStackTrace();}return Result.fail(-999,"系统错误");}}

使用缓存

    /*** 首页 文章列表* @param pageParams* @return*/@PostMapping// 加上此注解 代表要对此接口记录日志@LogAnnotation(module="文章",operator="获取文章列表")// 缓存 五分钟@Cache(expire = 5 * 60 * 1000,name = "listArticle")public Result listArticle(@RequestBody PageParams pageParams){return articleService.listArticle(pageParams);}

第一次先存入缓存

第二次直接从缓存中取,可以看到明显加快访问速度,主要避免短时间大量接口请求

8、七牛云

配置

# 七牛云的秘钥 在七牛云官网找
qiniu.accessKey=???
qiniu.accessSecretKey=???# 上传文件总的最大值
spring.servlet.multipart.max-request-size=20MB
# 单个文件的最大值
spring.servlet.multipart.max-file-size=2MB
/*** @author ljy* @version 1.0.0* @Description 七牛云* @createTime 2021年12月03日 21:10:00*/
@Component
public class QiniuUtils {// 七牛云30使用域名public static  final String url = "http://r3jkcbpns.hb-bkt.clouddn.com/";@Value("${qiniu.accessKey}")private  String accessKey;@Value("${qiniu.accessSecretKey}")private  String accessSecretKey;public  boolean upload(MultipartFile file, String fileName){//构造一个带指定 Region 对象的配置类Configuration cfg = new Configuration(Region.huabei());//...其他参数参考类注释UploadManager uploadManager = new UploadManager(cfg);//...生成上传凭证,然后准备上传,这个是七牛云官网的空间名称String bucket = "liujiany";//默认不指定key的情况下,以文件内容的hash值作为文件名try {byte[] uploadBytes = file.getBytes();Auth auth = Auth.create(accessKey, accessSecretKey);String upToken = auth.uploadToken(bucket);Response response = uploadManager.put(uploadBytes, fileName, upToken);//解析上传成功的结果DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class);return true;} catch (Exception ex) {ex.printStackTrace();}return false;}
}

使用

/*** @author ljy* @version 1.0.0* @Description 上传* @createTime 2021年12月03日 10:12:00*/
@RestController
@RequestMapping("upload")
public class UploadController {@Autowiredprivate QiniuUtils qiniuUtils;@PostMapping// RequestParam前端传入的参数名称,MultipartFile是SpringBoot用于接受文件的一个类型public Result upload(@RequestParam("image") MultipartFile file){// file.getOriginalFilename() 原始文件名称 比如 传入aa.png,拿到的就是aa.png// 上传的名称应该是随机的不能是aa.png// StringUtils.substringAfterLast(file.getOriginalFilename(), ".")的作用就是拿到pngString fileName = UUID.randomUUID().toString() + "." + StringUtils.substringAfterLast(file.getOriginalFilename(), ".");// 可以上传到服务器,但是不建议,采用七牛云来管理// 七牛云 云服务器 按量付费 速度快 把图片发放到离用户最近的服务器上// 降低 我们自身应用服务器的带宽消耗boolean upload = qiniuUtils.upload(file, fileName);if (upload){return Result.success(QiniuUtils.url + fileName);}return Result.fail(20001,"上传失败");}
}

substringAfterLast源码

9、SpringSecurity

跟拦截器类似,但是SpringSecurity可以自定义权限,可以指定用户拥有哪些权限

配置

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {// springSecurity的加密策略bCrypt@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder(){return new BCryptPasswordEncoder();}public static void main(String[] args) {//加密策略 MD5 不安全 彩虹表  MD5 加盐String liu = new BCryptPasswordEncoder().encode("123456");System.out.println(liu); // 将123456加密// $2a$10$A.hEQYiBLyN6K5dBwfCGlenTcUIMflLwiR/eXa5ojCPR0Efj2LJpe// $2a$10$RZECQ90DjOT/t1mhnXsl5.XSuZWc0Sa1XduPxj2rb4yaSYcje3nWW}@Overridepublic void configure(WebSecurity web) throws Exception {super.configure(web);}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests() //开启登录认证
//                .antMatchers("/user/findAll").hasRole("admin") // 访问接口需要admin的角色.antMatchers("/css/**").permitAll() // permit:放行的意思,就是所有的人都可以访问.antMatchers("/img/**").permitAll().antMatchers("/js/**").permitAll().antMatchers("/plugins/**").permitAll()// 表示admin下的路径要通过认证,这样就不影响login页面的操作  自定义authService(里面实现auth) 来去实现实                 时的权限认证。返回true则通过.antMatchers("/admin/**").access("@authService.auth(request,authentication)") // 自定义                       service 来去实现实时的权限认证.antMatchers("/pages/**").authenticated() // 登录成功才能访问.and().formLogin()// 登录配置.loginPage("/login.html") // 自定义的登录页面.loginProcessingUrl("/login") // 登录处理接口,这是SpringSecurity给我们提供的接口,不需要我们自己写.usernameParameter("username") // 定义登录时的用户名的key 默认为username ,对应login.html中name为                                                   username的属性.passwordParameter("password") // 定义登录时的密码key,默认是password.defaultSuccessUrl("/pages/main.html") // 登录成功就跳转到/pages/main.html.failureUrl("/login.html") // 失败就继续登录.permitAll() // 通过 不拦截,更加前面配的路径决定,这是指和登录表单相关的接口 都通过.and().logout() // 退出登录配置.logoutUrl("/logout") //退出登录接口,也是默认提供的.logoutSuccessUrl("/login.html").permitAll() // 退出登录的接口放行.and().httpBasic() // 单纯的http访问也拦截,比如 PostMan.and().csrf().disable() // csrf关闭 如果自定义登录 需要关闭,跨站请求伪造,默认只能post方式提交logout请求.headers().frameOptions().sameOrigin();// 支持iframe页面嵌套}
}

authService.auth需要我们自己定义

@Service
public class AuthService {@Autowiredprivate AdminService adminService;public boolean auth(HttpServletRequest request, Authentication authentication){// 权限认证// 请求路径String requestURI = request.getRequestURI();// 拿到当前用户的信息Object principal = authentication.getPrincipal();// 没有登录 或者是匿名用户if (principal == null || "anonymousUser".equals(principal)){//未登录return false;}// 拿到用户nameUserDetails userDetails = (UserDetails) principal;String username = userDetails.getUsername();// 再去数据库的拿到用户信息Admin admin = adminService.findAdminByUsername(username);if (admin == null){return  false;}if (1 == admin.getId()){//超级管理员return true;}Long id = admin.getId();List<Permission> permissionList = this.adminService.findPermissionByAdminId(id);// 请求的路径有可能有?传参,所以我们要保证requestURI是?前面的路径requestURI = StringUtils.split(requestURI,'?')[0];for (Permission permission : permissionList) {// 如果用户的访问路径和权限路径相同则通过if (requestURI.equals(permission.getPath())){return true;}}return false;}
}

使用

// UserDetailsService 是SpringSecurity提供的
@Component
public class SecurityUserService implements UserDetailsService {@Autowiredprivate AdminService adminService;/*** 登录的时候,会自动把username 传递到这里* @param username* @return* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 我们需要通过username查询 admin表,如果 admin存在 将密码告诉spring security// 如果不存在 返回null 认证失败了Admin admin = this.adminService.findAdminByUsername(username);if (admin == null){//登录失败return null;}// 有此用户但是还没有验证密码,我们是通过SpringSecurity来加密的,所以将密码交给SpringSecurity来帮我们验证UserDetails userDetails = new User(username,admin.getPassword(),new ArrayList<>());return userDetails;}
}

SpringBoot练手项目总结相关推荐

  1. SpringBoot练手项目《尚硅谷智慧校园》学习笔记

    ​ 一 项目展示 1 登录及角色控制 ​ 2首页展示 3 业务模块展示 二 智慧校园系统简介 2.1 项目简介 智慧校园管理系统:主要是以年级.班级为单位,进行老师和学生信息记录和统计功能.项目采用前 ...

  2. 70个Python练手项目列表 预祝大家 快乐

    小孩眺望远方,成人怀念故乡. 为此给大家分享一下珍藏的Python实战项目,祝大家节日快乐哦!!! Python 前言:不管学习哪门语言都希望能做出实际的东西来,这个实际的东西当然就是项目啦,不用多说 ...

  3. 一个适合于Python 初学者的入门练手项目

    随着人工智能的兴起,国内掀起了一股Python学习热潮,入门级编程语言,大多选择Python,有经验的程序员,也开始学习Python,正所谓是人生苦短,我用Python 有个Python入门练手项目, ...

  4. 推荐 Python 十大经典练手项目,让你的 Python 技能点全亮!

    前言:如果有人问:"Python还火吗?""当然,很火.""哪能火多久呢?""不知道." 技术发展到现在衍生出许多种编程 ...

  5. 别让双手闲下来,来做一些练手项目吧

    作者:Weston,原文链接,原文日期:2016-01-27 译者:saitjr:校对:Cee:定稿:千叶知风 自从我昨天发了文,收到的最多的评论就是: 我应该选择哪些 App 来练手呢? 这个问题很 ...

  6. python项目-推荐 10 个有趣的 Python 练手项目

    想成为一个优秀的Python程序员,没有捷径可走,势必要花费大量时间在键盘后. 而不断地进行各种小项目开发,可以为之后的大开发项目积攒经验,做好准备. 但不少人都在为开发什么项目而苦恼. 因此,我为大 ...

  7. python新手项目-Python 的练手项目有哪些值得推荐?

    其实初学者大多和题主类似都会经历这样一个阶段,当一门语言基础语法学完,之后刷了不少题,接下来就开始了一段迷茫期,不知道能用已经学到的东西做些什么即便有项目也无从下手,而且不清楚该如何去提高技术水平. ...

  8. python可以做什么项目-适合Python 新手的5大练手项目,你练了么?

    已经学习了一段时间的Python,如果你看过之前W3Cschool的文章,就知道是时候该进去[项目]阶段了. 但是在练手项目的选择上,还存在疑问?不知道要从哪种项目先下手? W3Cschool首先有两 ...

  9. python新手项目-推荐:一个适合于Python新手的入门练手项目

    原标题:推荐:一个适合于Python新手的入门练手项目 随着人工智能的兴起,国内掀起了一股Python学习热潮,入门级编程语言,大多选择Python,有经验的程序员,也开始学习Python,正所谓是人 ...

最新文章

  1. vue 解决跨域 调试_Electron-vue解决跨域
  2. Nginx在Windows平台的配置与使用
  3. 【机器学习】太香啦!只需一行Python代码就可以自动完成模型训练!
  4. LNMP部署(分享十七)
  5. ubuntu+ngrok内网穿透+Flask部署以及frp稍微提一下
  6. 9.3磁盘及文件系统管理详解
  7. list 释放 java_Java --list 常用方法汇总一
  8. 昔日光伏巨头赛维LDK迎“接盘侠” 平煤系深度整合产业链
  9. 设计模式是什么鬼(状态)
  10. opencv-api moments
  11. android 动态生成直线,Android使用自定义view在指定时间内匀速画一条直线的实例代码...
  12. 辅助函数 php,php的辅助函数功能
  13. 优化方法总结(梯度下降法、牛顿法、拟牛顿法等)
  14. Android关于BottomNavigationView效果实现指南
  15. matlab把图像白色部分变透明,怎么将PPT中的白底图片,白色部分变透明
  16. java 6面骰子_java计算掷6面骰子6000次每个点数出现的概率代码实例
  17. 模拟网易云的H5音乐播放器
  18. Mac下brew的安装
  19. 89c52流水灯c语言程序,【学习之路】STC89C52RC流水灯程序
  20. java中日期加上特定的天数或者时间

热门文章

  1. 九宫格拼图游戏设计,及代码时序问题解决
  2. 计算机考研408-2009
  3. 高颜值微信小程序 UI 组件库!
  4. vue使用阿里云矢量图
  5. Java基础读书笔记
  6. 2019SUCTF EasyWeb
  7. VS C++ error LNK2005 1169报错
  8. 基于cruise的混合动力商用车仿真,P2并联混动仿真模型可实现并联混动汽车动力性经济性仿真
  9. wordpress+HTML5游戏,轻松在wordpress上植入一个网页游戏
  10. 计算机毕业设计Java银创科技有限公司人事信息系统(系统+程序+mysql数据库+Lw文档)