1.消息通知与即时通讯的区别
消息通知 | 即时通讯 | |
通知内容 | 以文字超链接为主 | 包括文字图片视频等 |
核心需求 | 要求高送达率,不要求延迟 | 要求连接稳定可靠 |
交互方式 | 要求送达,不要求回复 | 任何消息均可回复 |
2.完成基本的CRUD功能
其他功能不再赘述,重点关注分页查询功能的实现。
首先配置分页拦截器
MybatisPlusConfig
//创建分页拦截器
@Configuration
public class MybatisPlusConfig {
@Bean
public PaginationInterceptor createPaginationInterceptor() {
return new PaginationInterceptor();
}
}
controller
//分页查询
@RequestMapping(value = "search/{page}/{size}", method = RequestMethod.POST)
public Result findByPage(@RequestBody Notice notice,
@PathVariable Integer page,
@PathVariable Integer size) {
Page<Notice> pageData = noticeService.findByPage(page, size, notice);
PageResult<Notice> pageResult = new PageResult<>(
pageData.getTotal(), pageData.getRecords()
);
return new Result(StatusCode.OK, true, "查询成功", pageResult);
}
service
public Page<Notice> findByPage(Integer page, Integer size, Notice notice) {
Page<Notice> page1 = new Page<>(page, size);
List<Notice> noticeList = noticeDao.selectPage(page1, new EntityWrapper<>(notice));
for (Notice notice1 : noticeList) {
getInfo(notice1);
}
page1.setRecords(noticeList);
return page1;
}
掌握分页插件和条件构造器 EntityWrapper的使用方法
3.完善消息内容
此处需要将文章名和用户名查询出来封装在notice对象中,故消息微服务需要调用文章和用户微服务,要做feign client调用
先注册eureka,引入相关依赖,修改application.yml
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:6868/eureka/
instance:
prefer-ip-address: true
在启动类加上@EnableFeignClients、@EnableEurekaClient注解开启eureka和feign
@SpringBootApplication
@EnableFeignClients
@EnableEurekaClient
@MapperScan("com.csdn.notice.dao")
public class NoticeApplication {
public static void main(String[] args) {
SpringApplication.run(NoticeApplication.class, args);
}
}
编写getinfo(),将notice对象完善相关信息,此处利用feign调用user和article微服务
//完善信息
private void getInfo(Notice notice) {
//完善user信息
Result userResult = userClient.findById(notice.getOperatorId());
HashMap userMap = (HashMap) userResult.getData();
notice.setOperatorName(userMap.get("nickname").toString());
//完善文章信息
Result articleResult = articleClient.findById(notice.getTargetId());
HashMap articleMap = (HashMap) articleResult.getData();
notice.setTargetName(articleMap.get("title").toString());
}
4.根据用户id、文章id建立订阅关系
controller
//根据文章id和用户id建立订阅关系
//http://127.0.0.1:9004/article/subscribe
@RequestMapping(value = "/subscribe", method = RequestMethod.POST)
public Result subscribe(@RequestBody Map map) {
Boolean flag = articleService.subscribe(map.get("articleId").toString(),
map.get("userId").toString());
if (flag == true) {
//订阅成功
return new Result(StatusCode.OK, true, "订阅成功");
} else {
return new Result(StatusCode.OK, false, "取消订阅成功");
}
}
service
public Boolean subscribe(String articleId, String userId) {
//redis中用户key
String userKey = "article_subscribe_" + userId;
//redis中作者key
String authorKey = "article_author_" + authorId;
//设置redis中数据按照String类型进行序列化
RedisSerializer stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setValueSerializer(stringSerializer);
//通过用户key查询是否订阅该作者
Boolean flag = redisTemplate.boundSetOps(userKey).isMember(authorId);
if (flag == true) {
//true则取消订阅
//移除池中数据
redisTemplate.boundSetOps(userKey).remove(authorId);
redisTemplate.boundSetOps(authorKey).remove(userId);
return false;
} else {
//false则订阅
redisTemplate.boundSetOps(userKey).add(authorId);
redisTemplate.boundSetOps(authorKey).add(userId);
return true;
}
}
此处代码需注意redisTemplate.setKeySerializer(stringSerializer)的使用,可将存入redis的内容以string类型进行序列化,以保证用其他方式读取redis内容不会出现用相同的key却读不出value。
5.新增文章群发通知订阅用户
controller
//增加文章
@RequestMapping(method = RequestMethod.POST)
public Result save(@RequestBody Article article) {
articleService.save(article);
return new Result(StatusCode.OK, true, "保存成功");
}
service
//保存文章
//新增功能:新增文章后,通知订阅该文章作者的用户
public void save(Article article) {
//TODO:通过鉴权获取当前文章作者id
String userId = "3";
article.setUserid(userId);
String id = idWorker.nextId() + "";
article.setId(id);
article.setVisits(0);//访问量
article.setComment(0);//评论数
article.setThumbup(0);//点赞数
articleDao.insert(article);
//从redis中取出订阅该作者的用户
String authorKey = "article_author_" + userId;
Set<String> set = redisTemplate.boundSetOps(authorKey).members();
//判断集合不为空
if (null != set && set.size() > 0) {
Notice notice = null;
for (String uid : set) {
notice = new Notice();
notice.setReceiverId(uid);
notice.setOperatorId(userId);
}
}
}
此处需要注意:在通过循环将信息装入notice对象时,最好在循环外创建对象,以减少内存占用。
6.基于db实现通知系统存在的问题及解决办法
1.数据库访问压力大
用户的消息通知和新消息提醒都放在数据库中,数据库访问频繁,尤其tensquare_noticefresh,访问压力大
2.服务器性能压力大
采用页面轮询调接口的方式实现服务器想用户推送消息,服务器接口压力大
3.解决办法
使用rabbitmq实现新消息提醒内容的缓存功能,代替数据库ensquare_noticefresh。
使用全双工长连接的方式实现服务器向用户推送最新消息通知,代替轮询。
页面使用websocket,微服务端使用异步高性能框架netty