YMLiang

之前和几个朋友在开发网站时用到了redis数据库,这里分享干货给大家,废话不多说直接上代码

我们用到redis的地方是在验证登录时的一个小demo,思路是每个用户登录到网站时给这个用互提供一个唯一的校验码ticket,之后存到redis中在后续访问网站相关内容时要取出ticket进行验证方可继续访问

生成ticket

  • entity层
    • Ticket.java
private Integer userId;//用户id
private String userName;//用户名
private String token;//ticket编号
private long createTime;//创建时间
private long expire;//有效期

public String toString() {
// token+createTime+expire+userId+userName
String str = token + "-" + createTime + "-" + expire + "-" + userId;
//利用Base64算法处理
String base64Str = Base64Utils.encodeToString(str.getBytes()); 
return base64Str;
}
...get,set方法自己生成吧

这里用到了Base64算法封装ticket,便于验证时的比较

  • service层

    • TicketManager.java//管理ticket的TickManager类,有创建ticket的方法和检验ticket的方法

      @Component
      public class TicketManager {
      
      @Autowired
      private RedisTemplate<Object, Object> redis;
      
      public Ticket create(User user, int hour) {
          Ticket ticket = new Ticket();
          ticket.setUserId(user.getId());
          ticket.setUserName(user.getName());
          ticket.setCreateTime(System.currentTimeMillis());
          ticket.setExpire(hour * 3600 * 1000);// 有效时长
          ticket.setToken(UUID.randomUUID().toString());
          // 将ticket存入redis,方便将来验证
          // redis.opsForValue().set("ticket_"+user.getId(), t);
          redis.opsForHash().put("tickers", user.getId(), ticket);
      
          // 测试取userId
          Ticket t = (Ticket) redis.opsForHash().get("tickers", user.getId());
          System.out.println("从redis中读取=>:"+t);
          return ticket;
      }
      
      public int checkTicket(int userId, String ticket) {
          Ticket t = (Ticket) redis.opsForHash().get("tickers", userId);
          System.out.println("|--------------------------------------------------------------------------------------------------|");
          System.out.println("|从redis中读取=>:"+t.toString()+"|");
          System.out.println("|--------------------------------------------------------------------------------------------------|");
          System.out.println("|从request带来=>:"+ticket.toString()+"|");
          System.out.println("|--------------------------------------------------------------------------------------------------|");
          System.out.println("|"+t.toString().equals(ticket)+"|");
          System.out.println("|--------------------------------------------------------------------------------------------------|");
          // 检测ticket是否匹配
          if (t != null && t.toString().equals(ticket)) {
              // 匹配成功
              long currentTime = System.currentTimeMillis();
              long totalTime = t.getCreateTime() + t.getExpire();
              // 检测是否失效
              if (currentTime < totalTime) {
                  // 在有效期内   0
                  return yixueConstant.SUCCESS;
              } else {
                  // 过期  1
                  return yixueConstant.ERROR1;
              }
          }
          // 不匹配    -1 
          return yixueConstant.ERROR;
      }
      }   
      

      这里用到了Java访问Redis的方法因为网站是用SpringBoot开发的,boot也提供了访问redis的包 spring-boot-starter-data-redis

      这里我提供boot访问redis的方法:

    • 需要添加SpringData-redis包,它提供一个RedisTemplate对象使用。

    • 引入spring-boot-starter-data-redis工具包(Maven项目中的pom.xml中添加)

      <parent>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-parent</artifactId>
          <version>2.0.1.RELEASE</version>
      </parent>
      
      <properties>
          <java.version>1.8</java.version>
      </properties>
      
      <dependencies>
          <dependency>
              <groupId>redis.clients</groupId>
              <artifactId>jedis</artifactId>
          </dependency>
          <dependency>
              <groupId>junit</groupId>
              <artifactId>junit</artifactId>
          </dependency>
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-test</artifactId>
          </dependency>
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-data-redis</artifactId>
          </dependency>
      </dependencies>
      
    • 在application.properties配置redis连接参数(resources文件夹下的application.properties配置文件)

      spring.redis.host=localhost
      spring.redis.port=6379
      
    • 注入使用RedisTemplate对象(这是测试类,测试能否存储到redis中和能否取出)

      @RunWith(SpringRunner.class)
      @SpringBootTest(classes={MyBootApplication.class})
      public class TestRedisTemplate {
      
          @Autowired
          private RedisTemplate<Object, Object> redis;
      
          @Test
          public void test1(){
              redis.opsForValue().set("msg1", "redis");
              String value = (String)redis.opsForValue().get("msg1");
              System.out.println(value);
          }
      
      }
      
    • 接着我们在service接口添加方法,这个方法是检验ticket是否合法和是否超时

      /**
      * 检查凭证是否合法或超时功能的实现
      * @param userId 用户唯一ID
      * @param ticket 用户登录后唯一凭证
      * @return
      */
      public ReturnResult checkTicket(int userId,String ticket);
      
    • 在实现类serviceImp中注入ticketManager对象

      @Autowired
      private TicketManager ticketManager;
      
    • 在实现类serviceImp中有用户登录的方法和检验ticket的方法,我们需要在用户登录方法中调用create方法来实现用户每次登录时都创建一个唯一的ticket作为凭证,用户如果要想访问网站其他内容必须要经过ticket的校验,这一点我们后面用拦截器来实现

      /**
      * 登录功能实现
      */
      @Override
      public ReturnResult checkUser(String name, String password) {
          ReturnResult result = new ReturnResult();
          // 检验参数是否合法
          if (StringUtils.isEmpty(name) || StringUtils.isEmpty(password)) {
              // 用yixueConstant常量类替换
              // (yixueConstant.ERROR -> 参数错误
              // yixueConstant.PARAM_ERROR->"参数不合法")
              result.setStatus(yixueConstant.ERROR);
              result.setMsg(yixueConstant.PARAM_ERROR);
              return result;
          }
          // 检查账户是否存在
          User user = userDao.selectByName(name);
          if (user == null) {
              // 不存在
              result.setStatus(yixueConstant.ERROR1);
              result.setMsg(yixueConstant.LOGIN_NAME_ERROR);
              return result;
          }
          // 检验密码是否正确
          String salt = user.getSalt();
          String md5Password = PasswordUtil.md5(password + salt);
          if (!user.getPassword().equals(md5Password)) {
              result.setStatus(yixueConstant.ERROR2);
              result.setMsg(yixueConstant.LOGIN_PASSWORD_ERROR);
              return result;
          }
          // 账户和密码都正确
          result.setStatus(yixueConstant.SUCCESS);
          result.setMsg(yixueConstant.LOGIN_SUCCESS);
          //---关键代码----
          // 若登录成功即(账户和密码都正确),则创建一个ticket返回,有效期2小时
          Ticket ticket = ticketManager.create(user,2);
          Map<String, Object> map = new HashMap<>();
          map.put("userId", ticket.getUserId());
          //这里我们把ticket的toString方法的返回值放进map中了,也就是base64算法封装后的ticket
          map.put("ticket", ticket.toString());
          //---关键代码----
          result.setData(map);
          //返回的result是返回给浏览器,浏览器把ticket保存下来,继续后续的业务校验工作
          return result;
      }
      
      /**
      * 检查凭证是否合法或超时功能
      * 
      */
      @Override
      public ReturnResult checkTicket(int userId, String ticket) {
          ReturnResult result = new ReturnResult();
          int num = ticketManager.checkTicket(userId, ticket);
          if (num == yixueConstant.SUCCESS){
              // 凭证在有效期内
              result.setStatus(num);
              result.setMsg(yixueConstant.TICKETS_SUCCESS);
              return result;
          }else if(num == yixueConstant.ERROR1){
              //凭证过时
              result.setStatus(num);
              result.setMsg(yixueConstant.TICKETS_ERROR_OVERTIME);
              return result;
          }
          //凭证不匹配
          result.setStatus(num);
          result.setMsg(yixueConstant.TICKETS_ERROR);
          return result;
      }
      
    • returnResult 对象是 ReturnResult类的对象,是为了给浏览器返回一个状态码,状态信息,和数据内容,我们用ajax来取出传来的data信息进行前端的展示

      private int status;
      private String msg;
      private Object data;
      
- Controller层中注入service并添加方法,这个方法也就是我们后面如果有请求被拦截,都会调用这个方法来进行校验的

        @Autowired
        private UserService userService;

        @PostMapping("/user/ticket")
        public ReturnResult ticket(
                @RequestParam(name = "userId", required = false) int userId,
                @RequestParam(name = "ticket", required = false) String ticket) {
            ReturnResult result = userService.checkTicket(userId,ticket);
            return result;
        }


- 之后我们如果想拦截用户访问其他模块的请求,就在相应的模块中添加拦截器,这里以访问视频模块为例,在视频模块中添加intercepter拦截器

        public class CheckInterceptor implements HandlerInterceptor {

            //发请求 restTemplate
            //专门调rest服务的对象
            @Autowired
            private RestTemplate restTemplate;

            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                    throws Exception {

                //截取请求中的userId,ticket
                String userId = request.getParameter("userId");
                String ticket = request.getParameter("ticket");
                // 将userId,ticket发给/user/ticket检测
                //返回结果是ReturnResult对象
                if(userId!=null&&ticket!=null){
                    String url = "http://localhost:7001/user/ticket";
                    MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
                    params.set("userId", userId);
                    params.set("ticket", ticket);
                    ReturnResult result = restTemplate.postForObject(url, params, ReturnResult.class);
                    if(result.getStatus()==yixueConstant.SUCCESS){
                        //校验正确,放行
                        return true;
                    }
                }
                //校验失败,拦截请求,给用户返回一个json结果
                response.setContentType("text/html;Charset=UTF-8");
                PrintWriter out = response.getWriter();
                out.print("{\"status\":-2,\"msg\":\"令牌凭证无效\"}");
                out.flush();
                return false;
            }
        }

- 有了拦截器,我们还要把拦截器加入配置,使其生效

        @Component
        public class CheckInterceptorConfiguration implements WebMvcConfigurer {
            @Autowired
            private CheckInterceptor checkInterceptor;

            public void addInterceptors(InterceptorRegistry registry) {
                //添加要拦截的请求地址url,在我们访问视频模块时,需要先拦截请求,在拦截器中验证,如果返回结果为true则放行
                String[] urls = {"/course/chapter"};
                registry.addInterceptor(checkInterceptor).addPathPatterns(urls);
            }
        }


到这里大致就结束了,其实用到redis的地方就是springBoot访问redis的包,和访问redis的几行代码,这里稍微总结一下:

    @Autowired
    private RedisTemplate<Object, Object> redis;
注入并使用RedisTemplate对象

- 存到redis中
`redis.opsForHash().put("tickers", user.getId(), ticket);`
- 取出
`redis.opsForHash().get("tickers", user.getId());`

也就是我们在用户登录是要create一个ticket存入redis中,然后在checkTicket时我们从redis取出ticket做校验,如果相同再去校验是否超时达到单点登录的功能

这里附一个redis的教程供大家参考

 评论


博客内容遵循 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议

本站使用 Material X 作为主题 , 总访问量为 次 。
Copyright 2018-2019 YMLiang'BLOG   |   京ICP备 - 19039949  |  载入天数...载入时分秒...