前言:
- 在最近这个权限框架中,小编发现登录很慢,因为是框架启动后第一次登录,需要创建很多对象还有打通额外的链接,所以很慢,但是第二次登录就明显的快了。于是打开项目的源码,开始优化起来,在了解完大体的业务逻辑后,感觉能用上多线程,于是就测试了一下,发现挺管用,小编将经验分享给大家吧!
使用的技术点
- SSM
- Shiro
- Json Web Token
- Redis
登录的逻辑
- 用户输入用户名和密码发送给后端程序,Shiro去验证
- 验证成功后,生成token信息,紧接着将token信息存入Redis
- 调用Dubbo服务,将用户的基础信息、存入Redis
- 调用Dubbo服务,将用户的权限标识存入Redis
-
给用户返回登录信息,主要是token信息
登录慢的原因
优化思路
- 当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();
}
SecurityUtils.getSubject().login(new UsernamePasswordToken(userCode,pwd));
final AllUsers user= userService.findByUserCode(userCode);
cachedThreadPool.execute(new Runnable() {
public void run() {
try {
String strNowMillis=String.valueOf(System.currentTimeMillis());
String lastLoninTime=PasswordUtil.base64Encoede(strNowMillis);
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();
String loginKey="aum"+":"+"tokenMessage"+":"+user.getSchoolNo()+":"+user.getId();
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();
UserModel userModel=new UserModel();
try {
BeanUtils.copyProperties(userModel, user);
addUserInfoToRedis(userInfoKey,userModel);
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);
}
调用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();
}
}
}
小结
- 通过上面的优化,整个登录时间提升了大3/4,瞬间感觉到了多线程的魅力了,还有值得一提的是,小编由于用的是SpringMvc,登录所用的Handler是单例的,所以小编声明了一个带缓冲的线程池,也就是说,当有大量用户登录时,这个缓冲池就能提现效果了。