4.Redis,一站式高性能存储方案


项目源码可以在 https://gitee.com/ShayneC/community获取

Redis,一站式高性能存储方案

1. Redis入门

  • Redis是一款基于键值对的NoSQL数据库,它的值支持多种数据结构:
    字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等。
  • Redis将所有的数据都存放在内存中,所以它的读写性能十分惊人。
    同时,Redis还可以将内存中的数据以快照或日志的形式保存到硬盘上,以保证数据的安全性。
  • Redis典型的应用场景包括:缓存、排行榜、计数器、社交网络、消息队列等。

2. Spring整合Redis

  • 引入依赖

    • spring-boot-starter-data-redis
  • 配置Redis

    • 配置数据库参数
    # RedisProperties
    spring.redis.database=11
    spring.redis.host=localhost
    spring.redis.port=6379
    
    • 编写配置类,构造RedisTemplate

    在config包下新建RedisConfig类

    @Configuration
    public class RedisConfig {
    
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(factory);
    
            // 设置key的序列化方式
            template.setKeySerializer(RedisSerializer.string());
            // 设置value的序列化方式
            template.setValueSerializer(RedisSerializer.json());
            // 设置hash的key的序列化方式
            template.setHashKeySerializer(RedisSerializer.string());
            // 设置hash的value的序列化方式
            template.setHashValueSerializer(RedisSerializer.json());
    
            template.afterPropertiesSet();
            return template;
        }
    }
    
  • 访问Redis

    • redisTemplate.opsForValue()
    • redisTemplate.opsForHash()
    • redisTemplate.opsForList()
    • redisTemplate.opsForSet()
    • redisTemplate.opsForZSet()

3. 点赞

  • 点赞
    • 支持对帖子、评论点赞。
    • 第1次点赞,第2次取消点赞。
  • 首页点赞数量
    • 统计帖子的点赞数量。
  • 详情页点赞数量
    • 统计点赞数量。
    • 显示点赞状态。

新建RedisKeyUtil类,用于生成键.

public class RedisKeyUtil {

    private static final String SPLIT = ":";
    private static final String PREFIX_ENTITY_LIKE = "like:entity";

    // 某个实体的赞
    // like:entity:entityType:entityId -> set(userId)
    public static String getEntityLikeKey(int entityType, int entityId) {
        return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId;
    }
}

新建LikeService类,用来实现点赞业务。

@Service
public class LikeService {

    @Autowired
    private RedisTemplate redisTemplate;

    // 点赞
    public void like(int userId, int entityType, int entityId) {
        String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
        boolean isMember = redisTemplate.opsForSet().isMember(entityLikeKey, userId);
        if (isMember) { // 已经点过赞
            redisTemplate.opsForSet().remove(entityLikeKey, userId);
        } else { // 未点过赞
            redisTemplate.opsForSet().add(entityLikeKey, userId);
        }
    }

    // 查询实体点赞的数量
    public long findEntityLikeCount(int entityType, int entityId) {
        String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
        return redisTemplate.opsForSet().size(entityLikeKey);
    }

    // 查询某人对实体点赞状态
    public int findEntityLikeStatus(int userId, int entityType, int entityId) {
        String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
        return redisTemplate.opsForSet().isMember(entityLikeKey, userId) ? 1 : 0;
    }
}

新建LikeController,返回点赞相关的数据给页面。

@Controller
public class LikeController {

    @Autowired
    private LikeService likeService;

    @Autowired
    private HostHolder hostHolder;

    @RequestMapping(path = "/like", method = RequestMethod.POST)
    @ResponseBody
    public String like(int entityType, int entityId) {
        User user = hostHolder.getUser();

        // 点赞
        likeService.like(user.getId(), entityType, entityId);
        // 点赞数量
        long likeCount = likeService.findEntityLikeCount(entityType, entityId);
        // 点赞状态
        int likeStatus = likeService.findEntityLikeStatus(user.getId(), entityType, entityId);

        Map<String, Object> map = new HashMap<>();
        map.put("likeCount", likeCount);
        map.put("likeStatus", likeStatus);

        return CommunityUtil.getJSONString(200, null, map);
    }
}

在HomeController中增加获取帖子点赞数量的语句

map.put("likeCount", likeService.findEntityLikeCount(ENTITY_TYPE_POST, post.getId()));

在DiscussPostController中

增加获取帖子点赞和点赞数量的语句

// 获取当前帖子点赞数量
model.addAttribute("likeCount", likeService.findEntityLikeCount(ENTITY_TYPE_POST, discussPostId));
// 获取当前帖子点赞的状态
model.addAttribute("likeStatus", hostHolder.getUser() == null ? 0 : likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_POST, discussPostId));

增加获取评论点赞和点赞数量的语句

// 获取当前评论点赞数量
commentVo.put("likeCount", likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT, comment.getId()));
// 获取当前评论点赞的状态
commentVo.put("likeStatus", hostHolder.getUser() == null ? 0 : likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, comment.getId()));

增加获取回复点赞和点赞数量的语句

// 获取当前回复点赞数量
replyVo.put("likeCount", likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT, reply.getId()));
// 获取当前回复点赞的状态
replyVo.put("likeStatus", hostHolder.getUser() == null ? 0 : likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, reply.getId()));

最后修改index.html和discuss-detail.html

新建discuss.js,并在discuss-detail.html中使用,用于改变点赞的状态.

function like(btn, entityType, entityId) {
    $.post(
        CONTEXT_PATH + "/like",
        {"entityType":entityType, "entityId":entityId},
        function (data) {
            data = $.parseJSON(data);
            if (data.code == 200) {
                $(btn).children("i").text(data.likeCount);
                $(btn).children("b").text(data.likeStatus==1?"已赞":"赞");
            } else {
                alert(data.msg);
            }
        }
    )
}

4. 我收到的赞

  • 重构点赞功能
    • 以用户为key,记录点赞数量
    • increment(key),decrement(key)
  • 开发个人主页
    • 以用户为key,查询点赞数量

修改LikeService

@Service
public class LikeService {

    @Autowired
    private RedisTemplate redisTemplate;

    // 点赞
    public void like(int userId, int entityType, int entityId, int entityUserId) {
        redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
                String userLikeKey = RedisKeyUtil.getUserLikeKey(entityUserId);

                Boolean isMember = operations.opsForSet().isMember(entityLikeKey, userId);
                operations.multi(); //开启事务
                if (isMember) {
                    operations.opsForSet().remove(entityLikeKey, userId);
                    operations.opsForValue().decrement(userLikeKey);
                } else {
                    operations.opsForSet().add(entityLikeKey, userId);
                    operations.opsForValue().increment(userLikeKey);
                }
                return operations.exec();
            }
        });
    }

    // 查询实体点赞的数量
    public long findEntityLikeCount(int entityType, int entityId) {
        String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
        return redisTemplate.opsForSet().size(entityLikeKey);
    }

    // 查询某人对实体点赞状态
    public int findEntityLikeStatus(int userId, int entityType, int entityId) {
        String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
        return redisTemplate.opsForSet().isMember(entityLikeKey, userId) ? 1 : 0;
    }

    // 查询某个用户获得的赞
    public int findUserLikeCount(int userId) {
        String userLikeKey = RedisKeyUtil.getUserLikeKey(userId);
        Integer count = (Integer) redisTemplate.opsForValue().get(userLikeKey);
        return count == null ? 0 : count.intValue();
    }
}

修改RedisKeyUtil

package com.nowcoder.community.util;

public class RedisKeyUtil {

    private static final String SPLIT = ":";
    private static final String PREFIX_ENTITY_LIKE = "like:entity";
    private static final String PREFIX_USER_LIKE = "like:user";

    // 某个实体的赞
    // like:entity:entityType:entityId -> set(userId)
    public static String getEntityLikeKey(int entityType, int entityId) {
        return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId;
    }

    // 某个用户的赞
    // like:user:userId -> int
    public static String getUserLikeKey(int userId) {
        return PREFIX_USER_LIKE + SPLIT + userId;
    }
}

在UserController中增加访问个人主页的请求

// 个人主页
@RequestMapping(path = "/profile/{userId}", method = RequestMethod.GET)
public String getProfilePage(@PathVariable("userId") int userId, Model model) {
    User user = userService.findUserById(userId);
    if (user == null) {
        throw new RuntimeException("该用户不存在");
    }

    // 用户
    model.addAttribute("user", user);
    // 点赞数量
    int likeCount = likeService.findUserLikeCount(userId);
    model.addAttribute("likeCount", likeCount);

    return "/site/profile";
}

最后修改profile.html,index.html,discuss-detail.html,discuss.js文件

5. 关注、取消关注

  • 需求
    • 开发关注、取消关注功能。
    • 统计用户的关注数、粉丝数。
  • 关注
    • 若A关注了B,则A是B的Follower(粉丝),B是A的Followee(目标)。
    • 关注的目标可以是用户、帖子、题目等,在实现时将这些目标抽象为实体。

在RedisKeyUtil中新增生成某个用户关注的实体和拥有的粉丝的方法。

public class RedisKeyUtil {

    private static final String SPLIT = ":";
    private static final String PREFIX_ENTITY_LIKE = "like:entity";
    private static final String PREFIX_USER_LIKE = "like:user";
    private static final String PREFIX_FOLLOWEE = "followee";
    private static final String PREFIX_FOLLOWER = "follower";

    // 某个实体的赞
    // like:entity:entityType:entityId -> set(userId)
    public static String getEntityLikeKey(int entityType, int entityId) {
        return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId;
    }

    // 某个用户的赞
    // like:user:userId -> int
    public static String getUserLikeKey(int userId) {
        return PREFIX_USER_LIKE + SPLIT + userId;
    }

    // 某个用户关注的实体
    // followee:userId:entityType -> zset(entityId,now)
    public static String getFolloweeKey(int userId, int entityType) {
        return PREFIX_FOLLOWEE + SPLIT + userId + SPLIT + entityType;
    }

    // 某个用户拥有的粉丝
    // follower:entityType:entityId -> zset(userId, now)
    public static String getFollowerKey(int entityType, int entityId) {
        return PREFIX_FOLLOWER + SPLIT + entityType + SPLIT + entityId;
    }
}

新建FollowService类,实现关注和取消关注的业务方法

@Service
public class FollowService {

    @Autowired
    private RedisTemplate redisTemplate;

    public void follow(int userId, int entityType, int entityId) {
        redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
                String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);

                operations.multi();

                operations.opsForZSet().add(followeeKey, entityId, System.currentTimeMillis());
                operations.opsForZSet().add(followerKey, userId, System.currentTimeMillis());

                return operations.exec();
            }
        });
    }

    public void unFollow(int userId, int entityType, int entityId) {
        redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
                String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);

                operations.multi();

                operations.opsForZSet().remove(followeeKey, entityId);
                operations.opsForZSet().remove(followerKey, userId);

                return operations.exec();
            }
        });
    }

    // 查询关注的实体的数量
    public long findFolloweeCount(int userId, int entityType) {
        String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
        return redisTemplate.opsForZSet().zCard(followeeKey);
    }

    // 查询实体的粉丝的数量
    public long findFollowerCount(int entityType, int entityId) {
        String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);
        return redisTemplate.opsForZSet().zCard(followerKey);
    }

    // 查询当前用户是否已关注该实体
    public boolean hasFollowed(int userId, int entityType, int entityId) {
        String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
        return redisTemplate.opsForZSet().score(followeeKey, entityId) != null;
    }
}

新建FollowController,发起关注的异步请求

@Controller
public class FollowController {

    @Autowired
    private FollowService followService;

    @Autowired
    private HostHolder hostHolder;

    @RequestMapping(path = "/follow", method = RequestMethod.POST)
    @ResponseBody
    public String follow(int entityType, int entityId) {
        User user = hostHolder.getUser();

        followService.follow(user.getId(), entityType, entityId);

        return CommunityUtil.getJSONString(200, "已关注");
    }

    @RequestMapping(path = "/unfollow", method = RequestMethod.POST)
    @ResponseBody
    public String unfollow(int entityType, int entityId) {
        User user = hostHolder.getUser();

        followService.unFollow(user.getId(), entityType, entityId);

        return CommunityUtil.getJSONString(200, "已取消关注");
    }
}

修改UserController中访问主页的请求

// 个人主页
@RequestMapping(path = "/profile/{userId}", method = RequestMethod.GET)
public String getProfilePage(@PathVariable("userId") int userId, Model model) {
    User user = userService.findUserById(userId);
    if (user == null) {
        throw new RuntimeException("该用户不存在");
    }

    // 用户
    model.addAttribute("user", user);
    // 点赞数量
    int likeCount = likeService.findUserLikeCount(userId);
    model.addAttribute("likeCount", likeCount);

    // 关注数量
    long followeeCount = followService.findFolloweeCount(userId, ENTITY_TYPE_USER);
    model.addAttribute("followeeCount", followeeCount);
    // 粉丝数量
    long followerCount = followService.findFollowerCount(ENTITY_TYPE_USER, userId);
    model.addAttribute("followerCount", followerCount);
    // 是否已关注
    boolean hasFollowed = false;
    if (hostHolder.getUser() != null) {
        hasFollowed = followService.hasFollowed(hostHolder.getUser().getId(), ENTITY_TYPE_USER, userId);
    }
    model.addAttribute("hasFollowed", hasFollowed);

    return "/site/profile";
}

修改profile.js

$(function(){
	$(".follow-btn").click(follow);
});

function follow() {
	var btn = this;
	if($(btn).hasClass("btn-info")) {
		// 关注TA
		$.post(
			CONTEXT_PATH + "/follow",
			{"entityType":3, "entityId":$(btn).prev().val()},
			function (data) {
				data = $.parseJSON(data);
				if (data.code == 200) {
					window.location.reload();
				} else {
					alert(data.msg);
				}
			}
		);
		// $(btn).text("已关注").removeClass("btn-info").addClass("btn-secondary");
	} else {
		// 取消关注
		$.post(
			CONTEXT_PATH + "/unfollow",
			{"entityType":3, "entityId":$(btn).prev().val()},
			function (data) {
				data = $.parseJSON(data);
				if (data.code == 200) {
					window.location.reload();
				} else {
					alert(data.msg);
				}
			}
		);
		// $(btn).text("关注TA").removeClass("btn-secondary").addClass("btn-info");
	}
}

最后修改profile.html

6. 关注列表、粉丝列表

  • 业务层
    • 查询某个用户关注的人,支持分页。
    • 查询某个用户的粉丝,支持分页。
  • 表现层
    • 处理“查询关注的人”、“查询粉丝”请求。
    • 编写“查询关注的人”、“查询粉丝”模板。

在FollowService中增加实现查询某个用户关注的人和查询某个用户的粉丝的业务方法

// 查询某个用户关注的人
public List<Map<String, Object>> findFollowees(int userId, int offset, int limit) {
    String followeeKey = RedisKeyUtil.getFolloweeKey(userId, ENTITY_TYPE_USER);
    Set<Integer> targetIds = redisTemplate.opsForZSet().reverseRange(followeeKey, offset, offset + limit - 1);

    if (targetIds == null) {
        return null;
    }

    List<Map<String, Object>> list = new ArrayList<>();
    for (Integer targetId : targetIds) {
        Map<String, Object> map = new HashMap<>();
        User user = userService.findUserById(targetId);
        map.put("user", user);
        Double score = redisTemplate.opsForZSet().score(followeeKey, targetId);
        map.put("followTime", new Date(score.longValue()));
        list.add(map);
    }
    return list;
}

// 查询某用户的粉丝
public List<Map<String, Object>> findFollowers(int userId, int offset, int limit) {
    String followerKey = RedisKeyUtil.getFollowerKey(ENTITY_TYPE_USER, userId);
    Set<Integer> targetIds = redisTemplate.opsForZSet().reverseRange(followerKey, offset, offset + limit - 1);

    if (targetIds == null) {
        return null;
    }

    List<Map<String, Object>> list = new ArrayList<>();
    for (Integer targetId : targetIds) {
        Map<String, Object> map = new HashMap<>();
        User user = userService.findUserById(targetId);
        map.put("user", user);
        Double score = redisTemplate.opsForZSet().score(followerKey, targetId);
        map.put("followTime", new Date(score.longValue()));
        list.add(map);
    }
    return list;
}

在FollowController中增加查询关注列表和查询粉丝列表的请求。

@RequestMapping(path = "/followees/{userId}", method = RequestMethod.GET)
public String getFollowees(@PathVariable("userId") int userId, Page page, Model model) {
    User user = userService.findUserById(userId);
    if (user == null) {
        throw new RuntimeException("该用户不存在");
    }
    model.addAttribute("user", user);

    page.setLimit(5);
    page.setPath("/followees/" + userId);
    page.setRows((int) followService.findFolloweeCount(userId, ENTITY_TYPE_USER));

    List<Map<String, Object>> userList = followService.findFollowees(userId, page.getOffset(), page.getLimit());
    if (userList != null) {
        for (Map<String, Object> map : userList) {
            User u = (User) map.get("user");
            map.put("hasFollowed", hasFollowed(u.getId()));
        }
    }
    model.addAttribute("users", userList);

    return "/site/followee";
}

@RequestMapping(path = "/followers/{userId}", method = RequestMethod.GET)
public String getFollowers(@PathVariable("userId") int userId, Page page, Model model) {
    User user = userService.findUserById(userId);
    if (user == null) {
        throw new RuntimeException("该用户不存在");
    }
    model.addAttribute("user", user);

    page.setLimit(5);
    page.setPath("/followres/" + userId);
    page.setRows((int) followService.findFollowerCount(ENTITY_TYPE_USER, userId));

    List<Map<String, Object>> userList = followService.findFollowers(userId, page.getOffset(), page.getLimit());
    if (userList != null) {
        for (Map<String, Object> map : userList) {
            User u = (User) map.get("user");
            map.put("hasFollowed", hasFollowed(u.getId()));
        }
    }
    model.addAttribute("users", userList);

    return "/site/follower";
}

private boolean hasFollowed(int userId) {
    if (hostHolder.getUser() == null) {
        return false;
    }

    return followService.hasFollowed(hostHolder.getUser().getId(), ENTITY_TYPE_USER, userId);
}

最后修改profile.html followee.html follower.html 三个文件,展示查询结果。

7. 优化登录模块

  • 使用Redis存储验证码
    • 验证码需要频繁的访问与刷新,对性能要求较高。
    • 验证码不需永久保存,通常在很短的时间后就会失效。
    • 分布式部署时,存在Session共享的问题。

在RedisKeyUtil中增加存储在redis中验证码的key

private static final String PREFIX_KAPTCHA = "kaptcha";

    // 登录验证码
    // Kaptcha:owner -> String
    public static String getKaptchaKey(String owner) {
        return PREFIX_KAPTCHA + SPLIT + owner;
    }

然后修改LoginController中生成验证码请求方法

@GetMapping("/kaptcha")
public void getKaptcha(HttpServletResponse response/*, HttpSession session*/) {
    // 生成验证码
    String text = kaptchaProducer.createText();
    BufferedImage image = kaptchaProducer.createImage(text);

    // 将验证码存入session
//        session.setAttribute("kaptcha", text);

    // 验证码的归属
    String kaptchaOwner = CommunityUtil.generateUUID();
    Cookie cookie = new Cookie("kaptchaOwner", kaptchaOwner);
    cookie.setMaxAge(60);
    cookie.setPath(contextPath);
    response.addCookie(cookie);
    // 将验证码存入Redis
    String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
    redisTemplate.opsForValue().set(redisKey, text, 60, TimeUnit.SECONDS);

    // 将图片输出给浏览器
    response.setContentType("image/png");
    try {
        ServletOutputStream os = response.getOutputStream();
        ImageIO.write(image, "png", os);
    } catch (IOException e) {
        logger.error("响应验证码失败" + e.getMessage());
    }
}

和登录时验证验证码的请求方法

@PostMapping("/login")
public String login(String username, String password, String code, boolean rememberme, Model model/*, HttpSession session*/, HttpServletResponse response, @CookieValue("kaptchaOwner") String kaptchaOwner) {

    // 首先验证验证码
//        String kaptcha = (String) session.getAttribute("kaptcha");
    String kaptcha = null;
    if (StringUtils.isNotBlank(kaptchaOwner)) {
        String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
        kaptcha = (String) redisTemplate.opsForValue().get(redisKey);
    }
    if (StringUtils.isBlank(kaptcha) || StringUtils.isBlank(code) || !kaptcha.equalsIgnoreCase(code)) {
        model.addAttribute("codeMsg", "验证码不正确");
        return "/site/login";
    }

    // 检查账号,密码
    int expiredSeconds = rememberme ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
    Map<String, Object> map = userService.login(username, password, expiredSeconds);
    if (map.containsKey("ticket")) {
        Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
        cookie.setPath(contextPath);
        cookie.setMaxAge(expiredSeconds);
        response.addCookie(cookie);
        return "redirect:/index";
    } else {
        model.addAttribute("usernameMsg", map.get("usernameMsg"));
        model.addAttribute("passwordMsg", map.get("passwordMsg"));
        return "/site/login";
    }
}
  • 使用Redis存储登录凭证
    • 处理每次请求时,都要查询用户的登录凭证,访问的频率非常高。

在RedisKeyUtil中增加存储在redis中登录凭证的key

private static final String PREFIX_TICKET = "ticket";
    // 登录的凭证
    public static String getTicketKey(String ticket) {
        return PREFIX_TICKET + SPLIT + ticket;
    }

在LoginTicketMapper上增加@Deprecated弃用这个Mapper

修改UserService中需要处理LoginTicket的方法

public Map<String, Object> login(String username, String password, long expiredSeconds) {
    HashMap<String, Object> map = new HashMap<>();

    // 空值处理
    if (StringUtils.isBlank(username)) {
        map.put("usernameMsg", "账号不能为空");
        return map;
    }
    if (StringUtils.isBlank(password)) {
        map.put("passwordMsg", "密码不能为空");
        return map;
    }

    //验证账号
    User user = userMapper.selectByName(username);
    if (user == null) {
        map.put("usernameMsg", "该账号不存在");
        return map;
    }

    //验证状态
    if (user.getStatus() == 0) {
        map.put("usernameMsg", "该账号未激活");
        return map;
    }

    //验证密码
    password = CommunityUtil.md5(password + user.getSalt());
    if (!user.getPassword().equals(password)) {
        map.put("passwordMsg", "密码不正确");
        return map;
    }

    // 此时登录成功,同时生成登录凭证
    LoginTicket loginTicket = new LoginTicket();
    loginTicket.setUserId(user.getId());
    loginTicket.setStatus(0);
    loginTicket.setTicket(CommunityUtil.generateUUID());
    loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));
//        loginTicketMapper.insertLoginTicket(loginTicket);

    String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
    redisTemplate.opsForValue().set(redisKey, loginTicket);

    map.put("ticket", loginTicket.getTicket());
    return map;
}

public void logout(String ticket) {
//        loginTicketMapper.updateStatus(ticket, 1);
    String redisKey = RedisKeyUtil.getTicketKey(ticket);
    LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);
    loginTicket.setStatus(1);
    redisTemplate.opsForValue().set(redisKey, loginTicket);
}

public LoginTicket findLoginTicket(String ticket) {
//        return loginTicketMapper.selectByTicket(ticket);
    String redisKey = RedisKeyUtil.getTicketKey(ticket);
    return (LoginTicket) redisTemplate.opsForValue().get(redisKey);
}

public int updateHeader(int userId, String headerUrl) {
//        return userMapper.updateHeader(userId, headerUrl);
    int rows = userMapper.updateHeader(userId, headerUrl);
    clearCache(userId);
    return rows;
}
  • 使用Redis缓存用户信息
    • 处理每次请求时,都要根据凭证查询用户信息,访问的频率非常高。

在RedisKeyUtil中增加存储在redis中用户信息的key

private static final String PREFIX_USER = "user";
    // 用户
    public static String getUserKey(int userId) {
        return PREFIX_USER + SPLIT + userId;
    }

修改UserService中需要处理用户信息的方法

public User findUserById(int id) {
//        return userMapper.selectById(id);
User user = getCache(id);
if (user == null) {
    user = initCache(id);
}
return user;
}

public int updateHeader(int userId, String headerUrl) {
//        return userMapper.updateHeader(userId, headerUrl);
    int rows = userMapper.updateHeader(userId, headerUrl);
    clearCache(userId);
    return rows;
}

public int updatePassword(int userId, String newPassword) {
//        return userMapper.updatePassword(userId, newPassword);
    int rows = userMapper.updatePassword(userId, newPassword);
    clearCache(userId);
    return rows;
}

// 1.优先从缓存中取值
private User getCache(int userId) {
    String redisKey = RedisKeyUtil.getUserKey(userId);
    return (User) redisTemplate.opsForValue().get(redisKey);
}

// 2.取不到时初始化缓存数据
private User initCache(int userId) {
    User user = userMapper.selectById(userId);
    String redisKey = RedisKeyUtil.getUserKey(userId);
    redisTemplate.opsForValue().set(redisKey, user, 3600, TimeUnit.SECONDS);
    return user;
}

// 3.当数据变更时清除缓存数据
private void clearCache(int userId) {
    String redisKey = RedisKeyUtil.getUserKey(userId);
    redisTemplate.delete(redisKey);
}

, headerUrl);
clearCache(userId);
return rows;
}

public int updatePassword(int userId, String newPassword) {
// return userMapper.updatePassword(userId, newPassword);
int rows = userMapper.updatePassword(userId, newPassword);
clearCache(userId);
return rows;
}

// 1.优先从缓存中取值
private User getCache(int userId) {
String redisKey = RedisKeyUtil.getUserKey(userId);
return (User) redisTemplate.opsForValue().get(redisKey);
}

// 2.取不到时初始化缓存数据
private User initCache(int userId) {
User user = userMapper.selectById(userId);
String redisKey = RedisKeyUtil.getUserKey(userId);
redisTemplate.opsForValue().set(redisKey, user, 3600, TimeUnit.SECONDS);
return user;
}

// 3.当数据变更时清除缓存数据
private void clearCache(int userId) {
String redisKey = RedisKeyUtil.getUserKey(userId);
redisTemplate.delete(redisKey);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值