论坛项目进展04

第4章 Redis,一站式高性能存储方案
4.1 redis入门

image-20220617150502963

像排行榜,点赞,等功能访问的太频繁,就把它放到内存中的redis去存。

4.2spring整合redis

image-20220617152328570

其实springboot也对redis做了一些配置,配好了redisTemplate,我们知道redis是key-value存储的,但是它配的redis中的key是是object类型,这样做的好处是更通用,但是我们在项目中一般都是用string类型,它配成object我们用起来不方便,所以我们还是重新配一下。这是springboot配的redistemplate,key是object类型:

image-20220617153111997

在config包下写一个RedisConfig:

@Configuration
public class RedisConfig {
   @Bean
   public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
       RedisTemplate<String, Object> template = new RedisTemplate<>();//key为string类型
       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;
  }
}
4.3点赞

image-20220617154935442

可能会有很多人同时要给某个帖子点赞,所以要考虑性能问题,所以像点赞这样的数据最好把它存到redis里,写代码时,因为把数据存到redis中,而操作redis就像操作一个map一样,非常简单,所以就不用写dao层了,直接写业务层。在redis中是面向key去操作的,为了能让key反复的复用,容易记,就写一个工具类用来生成key。

在util包下建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";
   private static final String PREFIX_KAPTCHA = "kaptcha";
   private static final String PREFIX_TICKET = "ticket";
   private static final String PREFIX_USER = "user";
   private static final String PREFIX_UV = "uv";
   private static final String PREFIX_DAU = "dau";
   private static final String PREFIX_POST = "post";
   // 某个实体的赞的key
   // 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;
  }
   // 登录验证码
   public static String getKaptchaKey(String owner) {
       return PREFIX_KAPTCHA + SPLIT + owner;
  }
   // 登录的凭证
   public static String getTicketKey(String ticket) {
       return PREFIX_TICKET + SPLIT + ticket;
  }
   // 用户
   public static String getUserKey(int userId) {
       return PREFIX_USER + SPLIT + userId;
  }
   // 单日UV
   public static String getUVKey(String date) {
       return PREFIX_UV + SPLIT + date;
  }
   // 区间UV
   public static String getUVKey(String startDate, String endDate) {
       return PREFIX_UV + SPLIT + startDate + SPLIT + endDate;
  }
   // 单日活跃用户
   public static String getDAUKey(String date) {
       return PREFIX_DAU + SPLIT + date;
  }
   // 区间活跃用户
   public static String getDAUKey(String startDate, String endDate) {
       return PREFIX_DAU + SPLIT + startDate + SPLIT + endDate;
  }
   // 帖子分数
   public static String getPostScoreKey() {
       return PREFIX_POST + SPLIT + "score";
  }
}

写service:

@Service
public class LikeService {
   @Autowired
   private RedisTemplate redisTemplate;
   // 点赞 userid:谁点的赞,entityType:给哪个实体点赞
   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);
  }
   // 查询某人对某实体的点赞状态,1表示已点赞,0表示未点赞
   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();
  }
}

写controller:

@Controller
public class LikeController implements CommunityConstant {
   @Autowired
   private LikeService likeService;
   @Autowired
   private HostHolder hostHolder;
   @Autowired
   private EventProducer eventProducer;
   @Autowired
   private RedisTemplate redisTemplate;
   @RequestMapping(path = "/like", method = RequestMethod.POST)
   @ResponseBody
   public String like(int entityType, int entityId, int entityUserId, int postId) {
       User user = hostHolder.getUser();
       // 点赞
       likeService.like(user.getId(), entityType, entityId, entityUserId);
       // 数量
       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);
       // 触发点赞事件
       if (likeStatus == 1) {
           Event event = new Event()
                  .setTopic(TOPIC_LIKE)
                  .setUserId(hostHolder.getUser().getId())
                  .setEntityType(entityType)
                  .setEntityId(entityId)
                  .setEntityUserId(entityUserId)
                  .setData("postId", postId);
           eventProducer.fireEvent(event);
      }
       if(entityType == ENTITY_TYPE_POST) {
           // 计算帖子分数
           String redisKey = RedisKeyUtil.getPostScoreKey();
           redisTemplate.opsForSet().add(redisKey, postId);
      }
       return CommunityUtil.getJSONString(0, null, map);
  }
}
4.4我收到的赞

image-20220617165727050

RedisKeyUtil里新增方法getUserLikeKey,而且LikeService的like方法也重写,同时也要加几个方法

 // 某个用户的赞
   // like:user:userId -> int
   public static String getUserLikeKey(int userId) {
       return PREFIX_USER_LIKE + SPLIT + userId;
  }
// 点赞 userid:谁点的赞,entityType:给哪个实体点赞
   public void like(int userId, int entityType, int entityId, int entityUserId) {//重写
       redisTemplate.execute(new SessionCallback() {
           @Override
           public Object execute(RedisOperations operations) throws DataAccessException {//在一个方法里要同时做两次操作(remove和increment),必须同时成功,所以要用到redis的事务管理。
               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();
  }

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";
  }
4.5关注,取消关注

image-20220619140002607

在RedisKeyUtil里加:

    // 某个用户关注的实体
   // 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 implements CommunityConstant {
   @Autowired
   private RedisTemplate redisTemplate;
   @Autowired
   private UserService userService;
   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 implements CommunityConstant {
   @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(0, "已关注!");
  }
   @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(0, "已取消关注!");
  }
}
4.6关注列表,粉丝列表

image-20220619142411487

因为是用redis,所以不用写dao层。在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("/followers/" + 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);
  }
4.7优化登录模块

image-20220619144653336

1.之前是把验证码存到session里,但是存在session共享的问题。但是存到redis中,性能高,而且redis对key可以设置有效时间,过期了验证码就失效。这样分布式部署时所有应用服务器都从redis里读取验证码,这样避免了session共享的问题。

2.之前是把登录凭证存到mysql里,但是登录凭证访问频率高,太耗时。存到redis之后,mysql的login_ticket表就作废了。

3.每次根据登录凭证到mysql里查用户信息,太耗时,所以把用户存到redis中,然后可以设置个有效期,过一段时间失效。这样也符合实际用户访问网站的情况,一般访问一段时间就退出了,redis就不需要该用户信息了。

在RedisKeyUtil里增加:

 // 登录验证码
   public static String getKaptchaKey(String owner) {
       return PREFIX_KAPTCHA + SPLIT + owner;
  }
   // 登录的凭证
   public static String getTicketKey(String ticket) {
       return PREFIX_TICKET + SPLIT + ticket;
  }
   // 用户
   public static String getUserKey(int userId) {
       return PREFIX_USER + SPLIT + userId;
  }

1.存验证码

在LoginController里修改下面两个方法:

 @RequestMapping(path = "/kaptcha", method = RequestMethod.GET)
   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 {
           OutputStream os = response.getOutputStream();
           ImageIO.write(image, "png", os);
      } catch (IOException e) {
           logger.error("响应验证码失败:" + e.getMessage());
      }
  }
@RequestMapping(path = "/login", method = RequestMethod.POST)
   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";
      }
  }

2.存登录凭证(即使用户退出登录也不删除,只能设置状态)

在Userservice里修改login方法:把登录凭证放到redis中

//loginTicketMapper.insertLoginTicket(loginTicket);
String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
redisTemplate.opsForValue().set(redisKey, loginTicket);//redis会把loginTicket对象序列化成一个json字符串

再修改logout:把loginTicket从redis中取出来,修改状态为1,再存回去。

 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);
  }

再修改findLoginTicket:

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

3.存用户信息(一段时间后要从redis中删除)

在UserService里增加:

 // 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);
  }

然后修改UserService里的方法:

public User findUserById(int id) {
//       return userMapper.selectById(id);
       User user = getCache(id);//先从redis中取值
       if (user == null) {
           user = initCache(id);//没有则更新redis
      }
       return user;
  }
public int activation(int userId, String code) {
       User user = userMapper.selectById(userId);
       if (user.getStatus() == 1) {//status为1说明已激活过了
           return ACTIVATION_REPEAT;
      } else if (user.getActivationCode().equals(code)) {//code等于激活码,激活成功,status设置为1
           userMapper.updateStatus(userId, 1);
           clearCache(userId);//用户信息更改则删除缓存
           return ACTIVATION_SUCCESS;
      } else {//激活失败
           return ACTIVATION_FAILURE;
      }
  }
public int updateHeader(int userId, String headerUrl) {
//       return userMapper.updateHeader(userId, headerUrl);
       int rows = userMapper.updateHeader(userId, headerUrl);
       clearCache(userId);//用户信息更改则删除缓存
       return rows;
  }
 
posted @ 2022-06-19 15:44  zhangshuai2496689659  阅读(20)  评论(0)    收藏  举报