第一节:redis基础(暂时未整理)
第二节:spring整合redis(暂时未整理)
第三节:点赞功能实现 (通过ajax, 异步刷新)
实现功能预览:
第一步:生成redisKey, 注意此时key对应的数据结构为集合(set)
private static final String SPLIT = ":";
//点赞的实体
private static final String PREFIX_ENTITY_LIKE = "like:entity";
// 生成某个实体的赞key 实体通过entityType和entityId 来得到唯一的实体(帖子discussPost/评论comment/评论的评论)
//value数据结构为集合(set) -- 存一个集合set,放赞的userId
public static String getEntityLikeKey(int entityType, int entityId){
return PREFIX_ENTITY_LIKE + SPLIT + entityType+SPLIT + entityId;
}
第二步:点赞逻辑(service层
@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;
}
}
第三步:展示赞的数量
在通过controller访问页面时,将赞的数量保存到model内,通过themleaf展示
代码示例(论坛主页):访问/index路径的代码
@Autowired
private LikeService likeService;
@RequestMapping(path = {"/index","/"}, method = RequestMethod.GET){
//分页设置 略
List<Map<String,Object>> discussPosts = new ArrayList<>();
if(discussPostList != null){
//将数据库查询出的一页的帖子信息保存到 discussPosts中
for (DiscussPost dp : discussPostList){
Map<String, Object> map = new HashMap<>();
map.put("post", dp);
//获取redis中的点赞数量
long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, dp.getId());
map.put("likeCount", likeCount);
map.put("user", userService.findUserById(dp.getUserId()));
discussPosts.add(map);
}
}
// 将discussPosts 抛给 view (thymeleaf)中
model.addAttribute("discussPosts",discussPosts);
return "/index";
}
themleaf模板展示
<li class="d-inline ml-2">
赞 <span th:text="${map.likeCount}"> 11</span>
</li>
第四步:在帖子内点赞/取消赞
首先是异步访问 /like 路径,在controller内调用点赞service
@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 entityLikeCount = likeService.findEntityLikeCount(entityType, entityId);
int entityLikeStatus = likeService.findEntityLikeStatus(user.getId(), entityType, entityId);
Map<String,Object> map = new HashMap<>();
map.put("likeCount", entityLikeCount);
map.put("likeStatus", entityLikeStatus);
return CommunityUtil.getJSONString(0,null, map);
}
在themleaf内通过JavaScript(jQuery)进行Ajax访问
<a href="javascript:;" th:onclick="|like(this,1,${post.id});|" class="text-primary">
<b>赞</b> <i>11</i>
</a>
function like(btn, entityType, entityId){
$.post(
// path
CONTEXT_PATH+"/like",
// data
{"entityType":entityType, "entityId":entityId},
// 回调函数,data参数为服务器返回的数据
function (data){
data = $.parseJSON(data)
if(data.code == 0){
$(btn).children("i").text(data.likeCount);
$(btn).children("b").text(data.likeStatus==1?'已赞':'赞')
}else {
alert(data.msg)
}
}
)
}
第四节 我收到的赞(暂时未整理)
第五节 关注、取消关注(暂时未整理)
第六节 关注列表、粉丝列表(暂时未整理)
最后一节 优化登陆模块
1.优化登陆验证码
第一步:生成redisKey
//存入验证码 前缀
private static final String PREFIX_KAPTCHA = "kaptcha";
// 给用户发了一个临时凭证 owner 给cookie
public static String getKaptchaKey(String owner){
return PREFIX_KAPTCHA+SPLIT+owner;
}
第二步:获取验证码时将验证码存入redis (数据结构 k-v 为 String - String)
@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);
//存储验证码 存入redis
//用户临时凭证
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);
第三步:登陆时判断用户输入验证码是否正确
@RequestMapping(path = "/login", method = RequestMethod.POST)
public String login(String username, String password, String code, boolean remeberme,
Model model, /* HttpSession session */ HttpServletResponse response ,
@CookieValue(value = "kaptchaOwner",required = false/*当取不到cookie时也能正常运行 */) String kaptchaOwner){
// 读取验证码
String kaptcha =null;
// String kaptcha = (String) session.getAttribute("kaptcha");
//通过redis获取验证码
if(StringUtils.isNotBlank(kaptchaOwner)){
String kaptchaKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
kaptcha = (String) redisTemplate.opsForValue().get(kaptchaKey);
}
2.优化登陆凭证
数据结构为String - Object(redis配置了value的序列化方式为json,object以json格式存储)故为String - String。
第一步:生成redisKey
//用户登陆凭证
private static final String PREFIX_TICKET = "ticket";
public static String getTicketKey(String ticket){
return PREFIX_TICKET+SPLIT+ticket;
}
第二步:用户登陆时,将登陆凭证存入redis (在service层,ticket将不会存入mysql
String loginTicketKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
redisTemplate.opsForValue().set(loginTicketKey, loginTicket);
第三步:用户访问时,判断ticket是否有效时,获取ticket
public LoginTicket getTicket(String ticket){
String ticketKey = RedisKeyUtil.getTicketKey(ticket);
LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(ticketKey);
return loginTicket;
}
第四步:用户登出时废除ticket
public void logout(String ticket){
// int rows = loginTicketMapper.updateStatus(ticket, 1);
String ticketKey = RedisKeyUtil.getTicketKey(ticket);
LoginTicket loginTicket = (LoginTicket )redisTemplate.opsForValue().get(ticketKey);
loginTicket.setStatus(1);//作废
redisTemplate.opsForValue().set(ticketKey, loginTicket);
}
3.缓存用户信息
第一步:设置rediskey
private static final String PREFIX_USER = "user";
public static String getUserKey(int userid){
return PREFIX_USER+SPLIT+userid;
}
第二步:在获取用户的地方,通过redis获取用户。
如果获取不到,则从mysql中获取到用户信息存放到redis中。当更新用户信息时,删除掉redis的缓存信息。
先定义三个方法,getRedisCache,initCache,clearCache
private User getCache(int userId){
String redisKey = RedisKeyUtil.getUserKey(userId);
return (User) redisTemplate.opsForValue().get(redisKey);
}
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;
}
private void clearCache(int userId){
String redisKey = RedisKeyUtil.getUserKey(userId);
redisTemplate.delete(redisKey);
}
然后替换获取用户的代码 在service层:
public User findUserById(int id){
// User user = userMapper.selectById(id);
//增加redis缓存用户信息
//1.从redis取用户信息
//2.redis没有则更新redis
//3.用户更新删除 reids缓存
User user = getCache(id);
if(user == null){
user = initCache(id);
}
return user;
}
在update用户数据的后面添加clearCache
public int updateHeader(User user, String headUrl){
// return userMapper.updateHeader(user.getId(), headUrl);
int rows = userMapper.updateHeader(user.getId(), headUrl);
clearCache(user.getId());
return rows;
}
ps:redis的持久化