文章目录

  • 1. 点赞
    • 1.1 业务层(直接调redis不用数据访问层)
      • 1.1.1 生成redis key的工具
      • 1.1.2 Service 下实现 LikeService
    • 1.2 表现层
    • 1.3 前端页面
      • 1.3.1 对帖子点赞
      • 1.3.2 对回帖列表点赞
      • 1.3.3 对回复列表点赞
      • 1.3.4 js 实现 like 方法
    • 1.3 首页赞的数量的显示
    • 1.4 帖子详情页面赞的数量的显示
  • 2. 我收到的赞
    • 2.1 重构点赞功能
      • 2.1.1 RedisKeyUtil 增加功能
      • 2.1.2 LikeService
      • 2.1.3 LikeController
      • 2.1.4 页面处理
    • 2.2 用户主页开发
      • 2.2.1 Controller 层
      • 2.2.2 修改前端页面index.html
      • 2.2.2 修改主页页面profile.html
  • 3. 关注和取关
    • 3.1 新加 RedisKeyUtil
    • 3.2 实现 FollowService
    • 3.2 实现 FollowController
    • 3.3 profile.js 关注按钮事件
    • 3.4 profile.html 显示相关数据
      • 3.4.1 FollowService 补充方法
      • 3.4.2 UserController. getProfilePage补充方法
      • 3.4.3 处理profile.html
  • 4. 关注列表和粉丝列表
    • 4.1 FollowerService 补充
    • 4.2 FollowController 补充
    • 4.3 处理 profile.html
    • 4.4 处理 followee.html
  • 5. 优化登陆模块
    • 5.1 使用Redis存储验证码
      • 5.1.1 RedisKeyUtil
      • 5.1.2 LoginController.getKaptcha
      • 5.1.3 LoginController.login
    • 5.2 使用Redis存储登录凭证
      • 5.2.1 RedisKeyUtil
      • 5.2.2 LoginTicketMapper
      • 5.2.3 UserService
    • 5.3 使用Redis缓存用户数据
      • 5.3.1 UserService

1. 点赞

1.1 业务层(直接调redis不用数据访问层)

1.1.1 生成redis key的工具

在 util 下实现 RedisKeyUtil,集合set存储谁给某个实体点的赞

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

1.1.2 Service 下实现 LikeService

package com.nowcoder.community.service;import com.nowcoder.community.util.RedisKeyUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Service;@Service
public class LikeService {@Autowiredprivate RedisTemplate redisTemplate;// 点赞public void like(int userId, int entityType, int entityId) {String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);boolean isMember = operations.opsForSet().isMember(entityLikeKey, userId);if (isMember) {operations.opsForSet().remove(entityLikeKey, userId);} else {operations.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;}
}

1.2 表现层

Controller 下实现 LikeController

  1. 获取当前用户
  2. 调用service点赞方法
  3. 获取数量和状态
  4. 放入map
  5. 返回json格式数据
package com.nowcoder.community.controller;import com.nowcoder.community.entity.User;
import com.nowcoder.community.service.LikeService;
import com.nowcoder.community.util.CommunityUtil;
import com.nowcoder.community.util.HostHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;import java.util.HashMap;
import java.util.Map;@Controller
public class LikeController {@Autowiredprivate LikeService likeService;@Autowiredprivate HostHolder hostHolder;@RequestMapping(path = "/like", method = RequestMethod.POST)@ResponseBodypublic String like(int entityType, int entityId) {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);return CommunityUtil.getJSONString(0, null, map);}}

1.3 前端页面

1.3.1 对帖子点赞

descuss-detail.html

  1. 处理超链接,点击超链接的时候访问服务器href="javascript:;
  2. 定义单击事件,调用js方法发送请求like(this,1,${post.id};
    1. this:当前结点
    2. 1:entityType
    3. ${post.id}:entityId
  3. 赞 和 赞的数量 分别加标签,方便以后修改
<li class="d-inline ml-2"><a href="javascript:;" th:onclick="|like(this,1,${post.id});|" class="text-primary"><b>赞</b> <i>11</i></a>
</li>

1.3.2 对回帖列表点赞

处理逻辑同上

<li class="d-inline ml-2"><a href="javascript:;" th:onclick="|like(this,2,${cvo.comment.id});|" class="text-primary"><b>赞</b>(<i>1</i>)</a>
</li>

1.3.3 对回复列表点赞

处理逻辑同上

<li class="d-inline ml-2"><a href="javascript:;" th:onclick="|like(this,2,${rvo.reply.id});|" class="text-primary"><b>赞</b>(<i>1</i>)</a>
</li>

1.3.4 js 实现 like 方法

在 discus-detail.html 下创建新的js

<script th:src="@{/js/discuss.js}"></script>

在 static.js 下实现 discuss.js

  1. 三个参数 btn, entityType, entityId
  2. post 请求
  3. 访问路径:CONTEXT_PATH + "/like"
  4. 携带参数:{"entityType":entityType,"entityId":entityId,"entityUserId":entityUserId}
  5. 返回的字符串转成 json 串data = $.parseJSON(data);
  6. 如果成功,改变赞的数量和显示的文字
    1. 获取 btn 的子节点 i,并改变为data返回的数据 $(btn).children("i").text(data.likeCount);
    2. 获取 btn 的子节点 b,并改变为data返回的数据 $(btn).children("b").text(data.likeStatus==1?'已赞':"赞");
  7. 如果失败,响应一个msgalert(data.msg);
function like(btn, entityType, entityId) {$.post(CONTEXT_PATH + "/like",{"entityType":entityType,"entityId":entityId,"entityUserId":entityUserId},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.3 首页赞的数量的显示

HomeController 下的 getIndexPage 添加逻辑

    @Autowiredprivate LikeService likeService;long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, post.getId());map.put("likeCount", likeCount);

修改 index.html 显示赞的逻辑

<li class="d-inline ml-2">赞 <span th:text="${map.likeCount}">11</span></li>

1.4 帖子详情页面赞的数量的显示

修改 DiscussPostController 下的 getDiscussPost

@Autowired
private LikeService likeService;
  1. 对于帖子,如果没有登录,点赞状态就是0
// 点赞数量
long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, discussPostId);
model.addAttribute("likeCount", likeCount);
// 点赞状态
int likeStatus = hostHolder.getUser() == null ? 0 :likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_POST, discussPostId);
model.addAttribute("likeStatus", likeStatus);
  1. 对于评论
// 点赞数量
likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT, comment.getId());
commentVo.put("likeCount", likeCount);
// 点赞状态
likeStatus = hostHolder.getUser() == null ? 0 :likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, comment.getId());
commentVo.put("likeStatus", likeStatus);
  1. 对于回复
// 点赞数量
likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT, reply.getId());
replyVo.put("likeCount", likeCount);
// 点赞状态
likeStatus = hostHolder.getUser() == null ? 0 :likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, reply.getId());
replyVo.put("likeStatus", likeStatus);
  1. 修改前端显示数据
<b th:text="${likeStatus==1?'已赞':'赞'}">赞</b> <i th:text="${likeCount}">11</i>
<b th:text="${cvo.likeStatus==1?'已赞':'赞'}">赞</b>(<i th:text="${cvo.likeCount}">1</i>)
<b th:text="${rvo.likeStatus==1?'已赞':'赞'}">赞</b>(<i th:text="${rvo.likeCount}">1</i>)

2. 我收到的赞

2.1 重构点赞功能

2.1.1 RedisKeyUtil 增加功能

  1. 以user为key
private static final String PREFIX_USER_LIKE = "like:user";// 某个用户的赞
// like:user:userId -> int
public static String getUserLikeKey(int userId) {return PREFIX_USER_LIKE + SPLIT + userId;
}

2.1.2 LikeService

需要增加一步操作,保证事务性,通过编程解决,重构like

  1. 先得到两个 key,一个以实体,一个以user
  2. 多传一个参数,被赞人的userid
  3. 查询放在事务之外
  4. operations.multi();
  5. 执行事务,点赞或取消赞
  6. return operations.exec();
// 点赞
public void like(int userId, int entityType, int entityId, int entityUserId) {redisTemplate.execute(new SessionCallback() {@Overridepublic 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();}});`在这里插入代码片`
}

添加新方法
默认得到 object,转成 Integer

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

2.1.3 LikeController

方法中增加参数

// 点赞
likeService.like(user.getId(), entityType, entityId, entityUserId);

2.1.4 页面处理

discuss-detail.html。后面两个对于评论的和回复的处理方法一样${rvo.reply.id}

<a href="javascript:;" th:onclick="|like(this,2,${rvo.reply.id},${rvo.reply.userId});|" class="text-primary"><b th:text="${rvo.likeStatus==1?'已赞':'赞'}">赞</b>(<i th:text="${rvo.likeCount}">1</i>)
</a>

修改js增加参数

{"entityType":entityType,"entityId":entityId,"entityUserId":entityUserId},

2.2 用户主页开发

2.2.1 Controller 层

UserController 下 增加 getProfilePage

  1. 查出访问的用户userService.findUserById(userId)
  2. 防止恶意攻击判断用户是否存在
  3. 用户
  4. 点赞数
@Autowired
private LikeService likeService;// 个人主页
@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";
}

2.2.2 修改前端页面index.html

  1. 修改头部
<a class="dropdown-item text-center" th:href="@{|/user/profile/${loginUser.id}|}">个人主页</a>
  1. 帖子列表里,每个用户头像都能点进他的首页
<a th:href="@{|/user/profile/${map.user.id}|}"><img th:src="${map.user.headerUrl}" class="mr-4 rounded-circle" alt="用户头像" style="width:50px;height:50px;">
</a>

2.2.2 修改主页页面profile.html

  1. 声明模板
  2. 路径处理
  3. header复用
  4. js路径处理
  5. 用户头像修改
<img th:src="${user.headerUrl}" class="align-self-start mr-4 rounded-circle" alt="用户头像" style="width:50px;">
  1. 用户名
<span th:utext="${user.username}">nowcoder</span>
  1. 注册时间
<span>注册于 <i class="text-muted" th:text="${#dates.format(user.createTime,'yyyy-MM-dd HH:mm:ss')}">2015-06-12 15:20:12</i></span>
  1. 获得多少赞
<span class="ml-4">获得了 <i class="text-danger" th:text="${likeCount}">87</i> 个赞</span>

3. 关注和取关

3.1 新加 RedisKeyUtil

  1. 两份数据,一份是业务需要,一份是统计方便
private static final String PREFIX_FOLLOWEE = "followee";
private static final String PREFIX_FOLLOWER = "follower";// 某个用户关注的实体
// 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;
}

3.2 实现 FollowService

  1. 关注功能 follow,一项业务两次存储,事务
  2. 取关功能 unfolow,一项业务两次存储,事务
package com.nowcoder.community.service;import com.nowcoder.community.entity.User;
import com.nowcoder.community.util.CommunityConstant;
import com.nowcoder.community.util.RedisKeyUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Service;import java.util.*;@Service
public class FollowService implements CommunityConstant {@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate UserService userService;public void follow(int userId, int entityType, int entityId) {redisTemplate.execute(new SessionCallback() {@Overridepublic 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() {@Overridepublic 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();}});}
}

3.2 实现 FollowController

  1. 实现关注功能,异步,@ResponseBody
  2. 成功后返回消息
@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, "已关注!");
}
  1. 取关功能,异步,@ResponseBody
@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, "已取消关注!");
}

3.3 profile.js 关注按钮事件

  1. 路径 CONTEXT_PATH + "/follow",
  2. 参数 {"entityType":3,"entityId":$(btn).prev().val()},。profile.html文件button前加个隐藏框传入id,<input type="hidden" id="entityId" th:value="${user.id}">获得entityId
  3. 返回值转成 js 对象 data = $.parseJSON(data);
  4. 成功后刷新页面(省事)
  5. 失败后弹框
$(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 == 0) {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 == 0) {window.location.reload();} else {alert(data.msg);}});//$(btn).text("关注TA").removeClass("btn-secondary").addClass("btn-info");}
}

3.4 profile.html 显示相关数据

3.4.1 FollowService 补充方法

// 查询关注的实体的数量
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;
}

3.4.2 UserController. getProfilePage补充方法

// 关注数量
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);

3.4.3 处理profile.html

  1. 关注状态
<span>关注了 <a class="text-primary" th:href="@{|/followees/${user.id}|}" th:text="${followeeCount}">5</a> 人</span>
<span class="ml-4">关注者 <a class="text-primary" th:href="@{|/followers/${user.id}|}" th:text="${followerCount}">123</a> 人</span>
  1. 按钮状态
    ① 登录用户访问自己空间不能点击关注 th:if="${loginUser!=null&&loginUser.id!=user.id}"
    ② 关注后样式改变 th:class="|btn ${hasFollowed?'btn-secondary':'btn-info'} btn-sm float-right mr-5 follow-btn|"
<button type="button" th:class="|btn ${hasFollowed?'btn-secondary':'btn-info'} btn-sm float-right mr-5 follow-btn|"
th:text="${hasFollowed?'已关注':'关注TA'}" th:if="${loginUser!=null&&loginUser.id!=user.id}">关注TA</button>

4. 关注列表和粉丝列表

4.1 FollowerService 补充

  1. map 包装返回的数据
  2. 由大到小倒序查询reverseRange
// 查询某用户关注的人
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;
}

4.2 FollowController 补充

@Autowired
private UserService userService;@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.3 处理 profile.html

<span>关注了 <a class="text-primary" th:href="@{|/followees/${user.id}|}" th:text="${followeeCount}">5</a> 人</span>
<span class="ml-4">关注者 <a class="text-primary" th:href="@{|/followers/${user.id}|}" th:text="${followerCount}">123</a> 人</span>

4.4 处理 followee.html

  1. 页签连接,显示当前用户名
<li class="nav-item"><a class="nav-link position-relative active" th:href="@{|/followees/${user.id}|}"><i class="text-info" th:utext="${user.username}">Nowcoder</i> 关注的人</a></li><li class="nav-item"><a class="nav-link position-relative" th:href="@{|/followers/${user.id}|}">关注 <i class="text-info" th:utext="${user.username}">Nowcoder</i> 的人</a>
</li>
  1. 遍历关注列表
<li class="media pb-3 pt-3 mb-3 border-bottom position-relative" th:each="map:${users}">
  1. 关注者的头像
<a th:href="@{|/user/profile/${map.user.id}|}"><img th:src="${map.user.headerUrl}" class="mr-4 rounded-circle user-header" alt="用户头像" >
</a>
  1. 关注名和关注时间
<h6 class="mt-0 mb-3"><span class="text-success" th:utext="${map.user.username}">落基山脉下的闲人</span><span class="float-right text-muted font-size-12">关注于 <i th:text="${#dates.format(map.followTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-28 14:13:25</i></span>
</h6>
  1. 关注他的处理
<div><input type="hidden" id="entityId" th:value="${map.user.id}"><button type="button" th:class="|btn ${map.hasFollowed?'btn-secondary':'btn-info'} btn-sm float-right follow-btn|"th:if="${loginUser!=null && loginUser.id!=map.user.id}" th:text="${map.hasFollowed?'已关注':'关注TA'}">关注TA</button>
</div>
  1. 复用首页分页逻辑
  2. follower.html 相同处理逻辑

5. 优化登陆模块

5.1 使用Redis存储验证码

5.1.1 RedisKeyUtil

owner,用户的临时凭证

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

5.1.2 LoginController.getKaptcha

老方法:

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

新方法:

@Autowired
private RedisTemplate redisTemplate;// 验证码的归属
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);

5.1.3 LoginController.login

老方法:

// 检查验证码
// String kaptcha = (String) session.getAttribute("kaptcha");

新方法,首先加参数@CookieValue("kaptchaOwner") String kaptchaOwner

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

5.2 使用Redis存储登录凭证

5.2.1 RedisKeyUtil

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

5.2.2 LoginTicketMapper

不推荐使用@Deprecated

@Mapper
@Deprecated
public interface LoginTicketMapper {}

5.2.3 UserService

  1. login 生成登录凭证
// 生成登录凭证
LoginTicket loginTicket = new LoginTicket();
loginTicket.setUserId(user.getId());
loginTicket.setTicket(CommunityUtil.generateUUID());
loginTicket.setStatus(0);
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());
  1. logout 退出登录,ticket取出来再存进去
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);
}
  1. LoginTicket 查询凭证
public LoginTicket findLoginTicket(String ticket) {//        return loginTicketMapper.selectByTicket(ticket);String redisKey = RedisKeyUtil.getTicketKey(ticket);return (LoginTicket) redisTemplate.opsForValue().get(redisKey);
}

5.3 使用Redis缓存用户数据

5.3.1 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);
}
public User findUserById(int id) {//        return userMapper.selectById(id);User user = getCache(id);if (user == null) {user = initCache(id);}return user;
}
public int activation(int userId, String code) {User user = userMapper.selectById(userId);if (user.getStatus() == 1) {return ACTIVATION_REPEAT;} else if (user.getActivationCode().equals(code)) {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;
}

牛客网项目——项目开发(六):点赞,关注和取关,优化登录相关推荐

  1. Java牛客网社区项目——知识点面试题

    Java牛客网社区项目--知识点&面试题 持续更新中(ง •̀_•́)ง 文章目录 Java牛客网社区项目--知识点&面试题 请简要介绍一下你的项目? 什么是Spring框架? 对Sp ...

  2. 仿牛客网社区项目 全栈总结

    学习仿牛客网社区项目 代码&资源 各章节总结 第一章 第二章 第三章 第四章 第五章 第六章 第七章 第八章 争取让每个知识点都有链接可点 项目总结 网站架构图 常见面试题 MySQL Red ...

  3. 牛客网–华为机试在线训练7:取近似值

    牛客网–华为机试在线训练7:取近似值 题目描述 写出一个程序,接受一个正浮点数值,输出该数值的近似整数值.如果小数点后数值大于等于5,向上取整:小于5,则向下取整. 输入描述: 输入一个正浮点数值 输 ...

  4. 2019牛客网高级项目

    本项目是一个基于SpringBoot的社区平台,实现了牛客网讨论区的功能.实现了邮箱注册.验证码登录.发帖.评论.私信.点赞.关注.统计网站访问次数等功能,数据库使用Mybatis.Redis,使用K ...

  5. 牛客网中级项目学习笔记(一)

    牛客中级项目学习: Controller 解析web请求 Service 业务层 DAO(data access object)数据处理层 database 底层数据库 重定向 代码如下: @Requ ...

  6. 2022-1-13牛客网C++项目—— 第二章 Linux 多进程开发(一)

    复习用的问题 进程和程序之间的关系是什么? 进程包含了哪些信息? 一.程序当中包含了一系列的信息,这些信息用于描述如何创建一个进程. 1)二进制格式标识:描述文件的格式,内核根据这个信息来解释文件中的 ...

  7. 牛客网实战项目详细到每一步(更新中)

    一技术架构 Spring Boot Spring Spring MVC MyBatics Redis Kafka Elasticsearch重点的提高性能的技术 Spring Security, Sp ...

  8. [牛客网中级项目]第四章用户注册登陆管理

    目录 1. 预习 1.1 拦截器: 1.2 MD5加密算法: 2. 内容: 3. 注册: 3.1 注册要实现的功能: 3.2 代码实现: 3.2.1 建立LoginCotroller.class 3. ...

  9. 牛客网社区项目——p3.4事务管理

    数据库保障事务的机制如下 spring事务管理 在业务层模拟某个业务,注册用户和自动发送新人报到帖这两个业务视作一个事务 代码如下(两种方法): @Transactional(isolation = ...

  10. 2021-12-11牛客网C++项目——Linux编程介绍入门(二)

    1.15 目录操作函数 mkdir 函数 /*#include <sys/stat.h>#include <sys/types.h>int mkdir(const char * ...

最新文章

  1. oracle9201怎么安装,Solaris10上安装64位Oracle9201
  2. ip pim spare 源树 和 共享树_iPhone通过内置应用与电脑传输共享文件指南
  3. Teradata QueryGrid整合最佳分析技术 拓展客户选择空间
  4. 判断1个整数的二进制形式的1的个数
  5. iphone-common-codes-ccteam源代码 CCNSArray.h
  6. SpringBoot(十) :邮件服务
  7. Python中list,tuple,dict,set的区别和用法
  8. 上下文无关文法(CFG)
  9. Unity3D 串口连接
  10. Duplicate entry ‘dongdong123‘ for key ‘tab_user.AK_nq_username‘
  11. 微信分享中将链接图标替换成自定义图片的实例
  12. 文件保险箱——用区块链保护文件
  13. wath修改data中的值后更新
  14. Windows 2003环境下IIS下ASP+ACCESS的配置方法
  15. Spring Boot整合websocket实现群聊,点对点聊天,图片发送,音频发送
  16. springboo+elasticsearch+经纬度查询
  17. java时间格式化与格式
  18. 11、CSS3选择器及属性
  19. 云服务器 ghost系统,在Ubuntu 18.04操作系统上安装Ghost CMS的步骤
  20. 纯css3圆柱模板立体图

热门文章

  1. 人口流向逆转带来什么变化(zz)
  2. DHCP原理及DHCP服务器的防攻击手段
  3. 直播预告:如何在“贵系”找到自己的发展方向?| 贵系万花筒
  4. 基于rfid的毕业设计题目50例
  5. matplotlib绘制随机漫步
  6. Android系统java/native crash和anr异常处理流程以及DroboxManagerService日志记录
  7. 蓝桥杯 算法训练 跳马
  8. Supported media formats (支持的媒体格式)
  9. phython学习资料
  10. html中竖线怎么写,网页中竖线的几种做法