权限----用多线程优化登录

前言:

  • 在最近这个权限框架中,小编发现登录很慢,因为是框架启动后第一次登录,需要创建很多对象还有打通额外的链接,所以很慢,但是第二次登录就明显的快了。于是打开项目的源码,开始优化起来,在了解完大体的业务逻辑后,感觉能用上多线程,于是就测试了一下,发现挺管用,小编将经验分享给大家吧!

使用的技术点

  • SSM
  • Shiro
  • Json Web Token
  • Redis

登录的逻辑

  1. 用户输入用户名和密码发送给后端程序,Shiro去验证
  2. 验证成功后,生成token信息,紧接着将token信息存入Redis
  3. 调用Dubbo服务,将用户的基础信息、存入Redis
  4. 调用Dubbo服务,将用户的权限标识存入Redis
  5. 给用户返回登录信息,主要是token信息

    这里写图片描述

登录慢的原因

  • Shiro在第一次认证的时候很耗时
  • 第一次调用Dubbo服务的时候很耗时

  • 在shiro第一次认证时,需要走shiro的内部很多程序,这耗点时间,第一调用dubbo服务,需要建立连接,也浪费点时间。

  • 估计有读者在问,为什么还需要调用dubbo服务存入用户信息那?shiro认证完了用户信息不就在库里都搜出来了吗?原因是酱紫滴:因为用户的一些基本信息是其他服务提供的,不是权限框架提供的,这个权限框架是单独抽出来做认证、授权、单点登录用的,在自己的微服务中并不能将用户的所有信息都在自己的库中搜出来。

优化思路

  • 当shiro认证通过之后,我们需要生成用户的token信息,将token信息存入实体后返回给前台,而在这个过程,调用dubb服务和往redis存入任何信息都是不耽误给前台返回信息的,也就是说登录逻辑中的第一步过后,第2、3、4不是互不干涉的,于是小编就将2、3、4步抽到子线程中去执行,结果效果非常明显。
  • 让主线程去认证和返回用户信息,让子线程调用dubbo接口存入用户信息。

主要登录方法代码

@RestController
@RequestMapping("/access")
public class AccessController extends BaseController {

    @Autowired
    private UserService userService;

    //打印日志相关
    private static final Logger logger = LoggerFactory.getLogger(AccessController.class);

    //创建一个线程池
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public Object login(@RequestBody AllUsers allUsers) throws InterruptedException{

        final CountDownLatch connectedSemaphore = new CountDownLatch(1);
        String userCode=allUsers.getUserCode();
        String pwd=allUsers.getPassword();
        final VOUserLogin userLogin = new VOUserLogin();

        if(StringUtils.isEmpty(userCode)){
           throw new UserCodeRequiredException();
        }
        if(StringUtils.isEmpty(pwd)){
            throw new PasswordRequiredException();
        }

        //shiro进行登录验证
        SecurityUtils.getSubject().login(new UsernamePasswordToken(userCode,pwd));
        //验证成功后将用户信息搜出来
        final AllUsers  user= userService.findByUserCode(userCode);


        //生成token,返回userLogin对象
        cachedThreadPool.execute(new Runnable() {
            public void run() {
                try {
                    //上次访问的时间标志,将它和token一块存入redis中,解决token过期问题
                    String strNowMillis=String.valueOf(System.currentTimeMillis());
                    String lastLoninTime=PasswordUtil.base64Encoede(strNowMillis);
                    //生成token
                    String token=TokenUtil.generate(UUidUtil.generate(),user.getId(),"http://tfjybj.com",60*60*1000,user.getSchoolNo());
                    userLogin.setToken(token+"@"+lastLoninTime);
                    BeanUtils.copyProperties(userLogin, user);
                      connectedSemaphore.countDown();//释放信号,返回userLogin对象
                    String loginKey="aum"+":"+"tokenMessage"+":"+user.getSchoolNo()+":"+user.getId();
                    //向redis中存入登录信息(token)
                    addMessageToRedis(loginKey,userLogin.getToken(),60*30);
                } catch (Exception e) {
                    logger.error("AccessController.thread1 生成token并存入Redis失败:{}",e);
                }
            }
        });

        cachedThreadPool.execute(new Runnable() {
            public void run() {
                String userInfoKey = "aum" + ":" + "userInfo" + ":" + user.getSchoolNo() + ":" + user.getId();
                //向redis中 存入用户信息
                UserModel userModel=new UserModel();
                try {
                    BeanUtils.copyProperties(userModel, user);
                    addUserInfoToRedis(userInfoKey,userModel);
                    //向redis中 存入用户权限标识信息
                    addPermissionsToRedis(userInfoKey,user.getSchoolNo());
                } catch (Exception e) {
                    logger.error("AccessController.thread2 向Redis存入用户信息和权限标识失败",e);
                }
            }
        });
            //返回登录实体
            connectedSemaphore.await();

        return userLogin;
    }

}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

调用dubbo服务,向redis中存入用户信息

 public void addUserInfoToRedis(String key,UserModel userModel){
        userService.getUserInfo(key,userModel);
    }
 
 
  • 1
  • 2
  • 3

调用dubbo服务,向redis中存入用户的权限标识

public void addPermissionsToRedis(String key,String schoolNo){
       String userInfo= jedisCacheUtil.get(key);
       if (StringUtils.isNotBlank(userInfo)){
           try {
               UserModel userModel=JacksonJsonUntil.jsonToPojo(userInfo, UserModel.class);
               List<String> roleIds=userModel.getRoleId();
               if (roleIds.size()>0){
                   userService.getPermissionsFromRedis(roleIds,schoolNo);
               }
           } catch (IOException e) {
               e.printStackTrace();
           }
       }
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

小结

  • 通过上面的优化,整个登录时间提升了大3/4,瞬间感觉到了多线程的魅力了,还有值得一提的是,小编由于用的是SpringMvc,登录所用的Handler是单例的,所以小编声明了一个带缓冲的线程池,也就是说,当有大量用户登录时,这个缓冲池就能提现效果了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值