ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

SpringBoot学习项目-博客系统-part9

2021-09-22 18:04:11  阅读:132  来源: 互联网

标签:return SpringBoot permission admin 博客 public part9 权限 id


  • 接下来就对接口的开发不做完整的记录,只是说明几点
    1. 开发接口的时候根据文档看清参数、地址、请求方式。
    2. 参数如果不是一个实体类的,建议进行封装
    3. 根据前端返回数据格式确定在serviceImpl最终返回的数据形式。具体是一个实体类还是封装的类对象
    4. 因为mp无法进行多表联合查询,当涉及的时候,使用xml或者直接在Mapper中写sql语句。

统一缓存处理(优化)

  1. 内存的访问速度远远大于磁盘的访问速度(1000倍起)
  2. 继续用AOP开发缓存功能,缓存加在哪个地方哪个地方就是切点
// 切点
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Cache {

    long expire() default 1 * 60 * 1000;
    //缓存标识 key
    String name() default "";

}
  1. 定义切面,切面定义了切点和通知的关系
//aop 定义一个切面,切面定义了切点和通知的关系
@Aspect
@Component
@Slf4j
public class CacheAspect {

    private static final ObjectMapper objectMapper = new ObjectMapper();


    @Autowired
    private RedisTemplate<String, String> redisTemplate;
//  切点
    @Pointcut("@annotation(com.lbj.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获取
            String redisKey = name + "::" + className+"::"+methodName+"::"+params;
            String redisValue = redisTemplate.opsForValue().get(redisKey);
            if (StringUtils.isNotEmpty(redisValue)){
                log.info("走了缓存~~~,{},{}",className,methodName);
                Result result = JSON.parseObject(redisValue, Result.class);
                return result;
            }
//            访问方法
            Object proceed = pjp.proceed();
            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,"系统错误");
    }

}

controller加上切点

/**
     * 首页 最热文章
     * @return
     */
    @PostMapping("hot")
    @Cache(expire = 5 * 60 * 1000,name = "hot_article")
    public Result hotArticle(){
        int limit = 5;
        return articleService.hotArticle(limit);
    }

权限部分

  1. 先说一下数据库表

ms_admin(权限用户表)

id                权限用户的id,主键
username          权限用户的用户名
password          权限用户的密码     

ms_permission(权限表名)

id                权限表的id,主键
name              权限名
path              权限路径
description       权限的描述

ms_admin_permission(权限用户对应权限表的关系表)

id                关系表id,主键
admin_id          权限用户的id
permission_id     权限id

权限管理

controller

  1. 接口中有权限的列表、增删改
@RestController
@RequestMapping("admin")
public class AdminController {

    @Autowired
    private PermissionService permissionService;

    @PostMapping("permission/permissionList")
    public Result permissionList(@RequestBody PageParam pageParam){
        return permissionService.listPermission(pageParam);
    }

    @PostMapping("permission/add")
    public Result add(@RequestBody Permission permission){
        return permissionService.add(permission);
    }

    @PostMapping("permission/update")
    public Result update(@RequestBody Permission permission){
        return permissionService.update(permission);
    }

    @GetMapping("permission/delete/{id}")
    public Result delete(@PathVariable("id") Long id){
        return permissionService.delete(id);
    }
}

service

@Service
public class PermissionService {

    @Autowired
    private PermissionMapper permissionMapper;

    public Result listPermission(PageParam pageParam){
        Page<Permission> page = new Page<>(pageParam.getCurrentPage(),pageParam.getPageSize());
        LambdaQueryWrapper<Permission> queryWrapper = new LambdaQueryWrapper<>();
        if (StringUtils.isNotBlank(pageParam.getQueryString())) {
            queryWrapper.eq(Permission::getName,pageParam.getQueryString());
        }
        Page<Permission> permissionPage = this.permissionMapper.selectPage(page, queryWrapper);
        PageResult<Permission> pageResult = new PageResult<>();
        pageResult.setList(permissionPage.getRecords());
        pageResult.setTotal(permissionPage.getTotal());
        return Result.success(pageResult);
    }

    public Result add(Permission permission) {
        this.permissionMapper.insert(permission);
        return Result.success(null);
    }

    public Result update(Permission permission) {
        this.permissionMapper.updateById(permission);
        return Result.success(null);
    }

    public Result delete(Long id) {
        this.permissionMapper.deleteById(id);
        return Result.success(null);
    }
}
  • 因为权限,就需要整合springsecurity
  1. 添加依赖
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
  1. 权限配置类
  • 自定义的权限服务配置要实现UserDetailsService接口,主要是实现UserDetailsService方法,加密策略选择了BCryptPasswordEncoder
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    public static void main(String[] args) {
        //加密策略 MD5 不安全 彩虹表  MD5 加盐
        String mszlu = new BCryptPasswordEncoder().encode("mszlu");
        System.out.println(mszlu);
    }
    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests() //开启登录认证
//                .antMatchers("/user/findAll").hasRole("admin") //访问接口需要admin的角色
                .antMatchers("/css/**").permitAll()
                .antMatchers("/img/**").permitAll()
                .antMatchers("/js/**").permitAll()
                .antMatchers("/plugins/**").permitAll()
                .antMatchers("/admin/**").access("@authService.auth(request,authentication)") //自定义service 来去实现实时的权限认证
                .antMatchers("/pages/**").authenticated()
                .and().formLogin()
                .loginPage("/login.html") //自定义的登录页面
                .loginProcessingUrl("/login") //登录处理接口
                .usernameParameter("username") //定义登录时的用户名的key 默认为username
                .passwordParameter("password") //定义登录时的密码key,默认是password
                .defaultSuccessUrl("/pages/main.html")
                .failureUrl("/login.html")
                .permitAll() //通过 不拦截,更加前面配的路径决定,这是指和登录表单相关的接口 都通过
                .and().logout() //退出登录配置
                .logoutUrl("/logout") //退出登录接口
                .logoutSuccessUrl("/login.html")
                .permitAll() //退出登录的接口放行
                .and()
                .httpBasic()
                .and()
                .csrf().disable() //csrf关闭 如果自定义登录 需要关闭
                .headers().frameOptions().sameOrigin();
    }
}

权限认证

  • 因为在配置的时候是使用了自定义serviceauthService进行权限认证的
@Service
@Slf4j
public class AuthService {

    @Autowired
    private AdminService adminService;

    public boolean auth(HttpServletRequest request, Authentication authentication){
        String requestURI = request.getRequestURI();
        log.info("request url:{}", requestURI);
        //true代表放行 false 代表拦截
        Object principal = authentication.getPrincipal();
        if (principal == null || "anonymousUser".equals(principal)){
            //未登录
            return false;
        }
        UserDetails userDetails = (UserDetails) principal;
        String username = userDetails.getUsername();
        Admin admin = adminService.findAdminByUserName(username);
        if (admin == null){
            return false;
        }
        if (admin.getId() == 1){
            //认为是超级管理员
            return true;
        }
        List<Permission> permissions = adminService.findPermissionsByAdminId(admin.getId());
        requestURI = StringUtils.split(requestURI,'?')[0];
        for (Permission permission : permissions) {
            if (requestURI.equals(permission.getPath())){
                log.info("权限通过");
                return true;
            }
        }
        return false;
    }
}

AdminMapper

public interface AdminMapper extends BaseMapper<Admin> {
}

PermissionMapper

public interface PermissionMapper extends BaseMapper<Permission> {

    List<Permission> findPermissionsByAdminId(Long adminId);
}

sql

<mapper namespace="com.mszlu.blog.admin.mapper.PermissionMapper">

    <select id="findPermissionsByAdminId" parameterType="long" resultType="com.mszlu.blog.admin.pojo.Permission">
        select * from ms_permission where id in (select permission_id from ms_admin_permission where admin_id=#{adminId})
    </select>
</mapper>
  • 该项目是一个单体项目,麻雀虽小五脏俱全,虽然简简单单的就总结完成了,总结还是根据当时文档进行总结的,自己也对着视频敲了三五天吧。说一下自己对这个项目的感觉

总结

  1. 任何一个项目给了数据库,在开发对应的接口的时候,应该好好看看对应的数据库表,这样才能进行相应的开发。
  2. 从最开始的依赖包的管理(maven)
  3. 设计到新的orm框架-mybatis-plus,其实这个简单的看一下就可以了,大致和mybatis差不多,但是使用是有分页插件的,所以要使用mp的时候要把mp插件配置写好。
  4. 是一个前后端分离项目,那么前后端分离就需要统一状态返回码
  5. 在开发的时候,永远牢记controller->service->mapper(dao),每个mapper会继承basemapper,但是mp的缺点是无法进行多表联合查询,所以一旦涉及到了多表联合查询,需要自己手写sql语句,这部分就mybatis的
    1. 如果xml是在mapper包下,要注意可能出现绑定异常的问题,这个时候就需要pom文件绑定xml以及在配置文件中配置xml文件路径。
    2. 如果xml在resource下,注意要和包名要一致,此时就不需要再配置和pom绑定了。
  6. 需要统一的异常处理,针对可以预见的异常进行处理
  7. 看前端返回数据形式,以及每次请求的参数,如果不是一个实体类能够解决的,那么就进行封装。
  8. JWT的使用,登陆的时候,密码不能以明文的方式传递到数据库表中,使用md5加盐的方式加密,JWT最关键的就是签名部分,这部分一般会有工具类
  9. 使用到redis,登陆的时候,服务器生成的token保存到redis中,以后可以直接从redis中进行取,速度快一些。
  10. 考虑事务,如果登陆的时候redis挂掉,就不能在数据库中保存注册的账户密码等信息,所以使用到@Transactional注解,这个注解在底层是ThreadLocal
  11. 拦截器,如果访问的是一些静态资源就不需要进行拦截,如果是需要登陆的,那么就需要进行拦截判断,同时放行之后将用户信息放在自己定义个ThreadLocal中,但是在使用了ThreadLocal之后一定要remove,不然就会发生内存泄露问题。之后需要可以直接获取。
    拦截器的配置。(通常也是写好的,但是还是看一下)
  12. 线程池的使用,如何去配置线程池的时候要写注解@EnableAsync(开启多线程),配置线程池的名字@Bean("taskExecutor"),以及在相应要使用线程的地方标注@Async("taskExecutor")
  13. 分布式id在前端会造成精度缺失,使用@JsonSerialize(using = ToStringSerializer.class)
  14. spring的两大特性:IOC和AOP
  15. 开发日志和缓存,都使用到了AOP
  16. 上传图片这些文件可以上传至云服务器上,比如:七牛云、阿里云...
  17. springsecurity的整合,用来进行权限管理,如果自己要自定义的权限登陆要继承WebSecurityConfigurerAdapter,自定义权限服务配置要实现UserDetailsService

标签:return,SpringBoot,permission,admin,博客,public,part9,权限,id
来源: https://www.cnblogs.com/yunge-thinking/p/15320985.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有