ids = new ArrayList<>();
if (letterList != null) {
for (Message message : letterList) {
if (hostHolder.getUser().getId() == message.getToId() && message.getStatus() == 0) {
ids.add(message.getId());
}
}
}
return ids;
}
/**
* 私信发送操作
* @param toName
* @param content
* @return
*/
@RequestMapping(path = "/letter/send", method = RequestMethod.POST)
@ResponseBody
public String sendLetter(String toName, String content) {
User target = userService.findUserByName(toName);
if (target == null) {
return CommunityUtil.getJSONString(1, "目标用户不存在!");
}
// 开始构建会话消息对象
Message message = new Message();
message.setFromId(hostHolder.getUser().getId());
message.setToId(target.getId());
if (message.getFromId() < message.getToId()) {
message.setConversationId(message.getFromId() + "_" + message.getToId());
} else {
message.setConversationId(message.getToId() + "_" + message.getFromId());
}
message.setContent(content);
message.setCreateTime(new Date());
messageService.addMessage(message);
return CommunityUtil.getJSONString(0);
}
}
```
## 9. 全局异常捕获与处理
### 404页面展示

### 错误页面展示

### 统一异常处理
> 相关注解介绍
* `@ControllerAdvice`
* 用于==修饰类==,表示该类是**Controller** 的全局适配类。
* 在此类中,可以对**Controller** 进行如下三种全局配置:
* :aerial_tramway: 异常处理方案
* :baby: 绑定数据方案
* :baby_chick: 绑定参数方案
* `@ExceptionHandler`(我们实例中使用则个注解修饰方法)
* 用于==修饰方法==,该方法会在**Controller** 出现异常后被调用,用于处理捕获到的异常。
* `@ModelAttribute`
* 用于==修饰方法==,该方法回在**Controller** 方法执行前被调用,用于为**Model** 对象绑定参数。
* `@DataBinder`
* 用于==修饰方法==,该方法回在**Controller** 方法执行前被调用,用于绑定参数的转换器。
> 实例代码
```java
/**
* @Auther: csp1999
* @Date: 2020/11/26/20:39
* @Description: 异常通知类
*/
// 所有带 @Controller 注解的类都会被扫描到
@ControllerAdvice(annotations = Controller.class)
public class ExceptionAdvice {
// 声明日志工厂
private static final Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class);
/**
* 自定义异常处理器,覆盖spring boot原来的异常处理器
* @param e 异常对象
* @param request 请求
* @param response 响应
* @throws IOException
*/
@ExceptionHandler({Exception.class})// 标识该方法是用来做异常处理的,处理的异常级别为Exception
public void handleException(Exception e, HttpServletRequest request,
HttpServletResponse response) throws IOException {
// 记录异常信息
logger.error("请求 URL : {} , 异常信息 : {}",request.getRequestURL(),e);
// 逐条记录错误日志
for (StackTraceElement element : e.getStackTrace()) {
logger.error(element.toString());
}
String xRequestedWith = request.getHeader("x-requested-with");
if ("XMLHttpRequest".equals(xRequestedWith)) {
response.setContentType("application/plain;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(CommunityUtil.getJSONString(1, "服务器异常!"));
} else {
response.sendRedirect(request.getContextPath() + "/error");
}
}
}
```
### 切面统一记录日志
```java
/**
* @Auther: csp1999
* @Date: 2020/11/26/21:12
* @Description: 自定义日志处理组件
*/
@Component
@Aspect
public class ServiceLogAspect {
// 获取日志工厂
private static final Logger logger = LoggerFactory.getLogger(ServiceLogAspect.class);
/**
* 切入点
*/
@Pointcut("execution(* com.haust.community.service.*.*(..))")
public void pointcut() {
}
/**
* 前置通知,在切面之前执行
*
* @param joinPoint
*/
@Before("pointcut()")
public void before(JoinPoint joinPoint) {
// 用户[1.2.3.4],在[xxx],访问了[com.nowcoder.community.service.xxx()].
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String ip = request.getRemoteHost();
String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
String target = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
logger.info(String.format("用户[%s],在[%s],访问了[%s].", ip, now, target));
}
/**
* 后置通知,在切面之后执行
*/
@After("pointcut()")
public void doAfter() {
logger.info("----------doAfter----------");
}
/**
* 切入点拦截的方法执行结束后,捕获返回内容
*
* @param result
*/
@AfterReturning(returning = "result", pointcut = "pointcut()")
public void doAfterRuturn(Object result) {
logger.info("捕获返回内容 : {}", result);
}
}
```
## 10. Redis 实现点赞/关注/粉丝列表
#### 生成Redis Key的工具类:
```java
/**
* @Auther: csp1999
* @Date: 2020/11/27/20:34
* @Description: Redis Key的工具类
*/
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";// 验证码key的前缀
private static final String PREFIX_TICKET = "ticket";// 登录凭证key的前缀
private static final String PREFIX_USER = "user";// 登录用户key的前缀
/**
* 某个实体赞的key
*
* key ---> like:entity:entityType:entityId -> set(userId)
*
* @param entityType
* @param entityId
* @return
*/
public static String getEntityLikeKey(int entityType, int entityId) {
return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId;
}
/**
* 某个用户累计的赞的key
*
* key ---> like:user:userId -> int
*
* @param userId
* @return
*/
public static String getUserLikeKey(int userId) {
return PREFIX_USER_LIKE + SPLIT + userId;
}
/**
* 某个用户关注某个实体的集合key
*
* key
* ---> followee:userId:entityType
* ---> zset(entityId,now),zset为有序集合,以now作为排序分数
* now表示当前时间的时间数,可以根据时间大小排序
*
*
* @param userId
* @param entityType
* @return
*/
public static String getFolloweeKey(int userId, int entityType) {
return PREFIX_FOLLOWEE + SPLIT + userId + SPLIT + entityType;
}
/**
* 某个实体拥有的粉丝集合key
*
* key ---> follower:entityType:entityId -> zset(userId,now)
*
* @param entityType
* @param entityId
* @return
*/
public static String getFollowerKey(int entityType, int entityId) {
return PREFIX_FOLLOWER + SPLIT + entityType + SPLIT + entityId;
}
/**
* 登录验证码的key
*
* @param owner 验证码所属者
* @return
*/
public static String getKaptchaKey(String owner) {
return PREFIX_KAPTCHA + SPLIT + owner;
}
/**
* 登录的凭证的key
*
* @param ticket
* @return
*/
public static String getTicketKey(String ticket) {
return PREFIX_TICKET + SPLIT + ticket;
}
/**
* 用户的key
*
* @param userId
* @return
*/
public static String getUserKey(int userId) {
return PREFIX_USER + SPLIT + userId;
}
}
```
#### Service层:
**LikeService**.java
```java
/**
* @Auther: csp1999
* @Date: 2020/11/27/20:39
* @Description: 点赞Service 存入Redis 缓存
*/
@Service
public class LikeService {
@Autowired
private RedisTemplate redisTemplate;
/**
* 点赞操作
*
* @param userId 点赞用户的id
* @param entityType 点赞实体的类型
* @param entityId 点赞实体的id
* @param entityUserId 被点赞实体的用户id
*/
public void like(int userId, int entityType, int entityId, int entityUserId) {
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
// 某帖子实体点赞集合的key
String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
// 某个用户累计的赞的key
String userLikeKey = RedisKeyUtil.getUserLikeKey(entityUserId);
// 查询entityLikeKey对应的集合中是否已经存在当前userId
boolean isMember = operations.opsForSet().isMember(entityLikeKey, userId);
// 开启redis事务
operations.multi();
// userId 已经存在,即当前用户已经为该帖子点过赞
if (isMember) {
// 取消这个赞
operations.opsForSet().remove(entityLikeKey, userId);
// 被点赞用户获赞数减少1
operations.opsForValue().decrement(userLikeKey);
} else {
// userId 不存在,即当前用户还没为该帖子点过赞,直接点赞即可
operations.opsForSet().add(entityLikeKey, userId);
// 被点赞用户获赞数增加1
operations.opsForValue().increment(userLikeKey);
}
// 执行redis事务
return operations.exec();
}
});
}
/**
* 查询某帖子实体点赞的数量
*
* @param entityType
* @param entityId
* @return
*/
public long findEntityLikeCount(int entityType, int entityId) {
// 某帖子实体点赞集合的key
String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
// 统计该帖子实体的点赞集合中的数据数量,即点赞数
return redisTemplate.opsForSet().size(entityLikeKey);
}
/**
* 查询某人对某帖子实体的点赞状态
* 0未点赞/1已点赞
*
* @param userId
* @param entityType
* @param entityId
* @return
*/
public int findEntityLikeStatus(int userId, int entityType, int entityId) {
// 某帖子实体点赞集合的key
String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId);
return redisTemplate.opsForSet().isMember(entityLikeKey, userId) ? 1 : 0;
}
/**
* 查询某个用户累计获得的赞
*
* @param userId
* @return
*/
public int findUserLikeCount(int userId) {
String userLikeKey = RedisKeyUtil.getUserLikeKey(userId);
Integer count = (Integer) redisTemplate.opsForValue().get(userLikeKey);
return count == null ? 0 : count.intValue();
}
}
```
**FollowService**.java
```java
/**
* @Auther: csp1999
* @Date: 2020/11/28/9:41
* @Description: 关注Service 存入Redis 缓存
*/
@Service
public class FollowService implements CommunityConstant {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private UserService userService;
/**
* 用户关注了某个实体
*
* @param userId
* @param entityType
* @param entityId
*/
public void follow(int userId, int entityType, int entityId) {
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
// 某个用户所关注的实体的集合key
String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
// 某个实体拥有的粉丝集合key
String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);
// 开启redis事务
operations.multi();
// 用户所关注的实体的集合新增一条关注数据
operations.opsForZSet().add(followeeKey, entityId, System.currentTimeMillis());
// 实体拥有的粉丝集合新增一个粉丝
operations.opsForZSet().add(followerKey, userId, System.currentTimeMillis());
// 执行redis事务
return operations.exec();
}
});
}
/**
* 用户取消关注了某个实体
*
* @param userId
* @param entityType
* @param entityId
*/
public void unfollow(int userId, int entityType, int entityId) {
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
// 某个用户所关注的实体的集合key
String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
// 某个实体拥有的粉丝集合key
String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);
// 开启redis事务
operations.multi();
// 用户关注所关注的实体的集合减少一条关注数据
operations.opsForZSet().remove(followeeKey, entityId);
// 实体拥有的粉丝集合减少一个粉丝
operations.opsForZSet().remove(followerKey, userId);
// 执行redis事务
return operations.exec();
}
});
}
/**
* 查询关注的实体的数量
*
* @param userId
* @param entityType
* @return
*/
public long findFolloweeCount(int userId, int entityType) {
// 某个用户所关注的实体的集合key
String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
return redisTemplate.opsForZSet().zCard(followeeKey);
}
/**
* 查询实体的粉丝的数量
*
* @param entityType
* @param entityId
* @return
*/
public long findFollowerCount(int entityType, int entityId) {
// 某个实体拥有的粉丝集合key
String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId);
return redisTemplate.opsForZSet().zCard(followerKey);
}
/**
* 查询当前用户是否已关注该实体
*
* @param userId
* @param entityType
* @param entityId
* @return
*/
public boolean hasFollowed(int userId, int entityType, int entityId) {
// 某个用户所关注的实体的集合key
String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType);
return redisTemplate.opsForZSet().score(followeeKey, entityId) != null;
}
/**
* 查询某用户关注的人
*
* @param userId
* @param offset
* @param limit
* @return
*/
public List