java 通知系统设计_8_1_异步设计和站内邮件通知系统

本文介绍了如何利用Redis构建一个异步通知系统,包括点赞信息和登录异常通知的处理。通过JedisAdapter实现消息队列的lpush和brpop操作,EventProducer和EventConsumer分别负责事件的推送和消费。事件模型EventModel用于封装事件数据,EventHandler接口定义了事件处理逻辑,LikeHandler和LoginExceptionHandler实现了具体的处理类。邮件发送则借助于MailSender类完成。
摘要由CSDN通过智能技术生成

一、需求描述

1. 利用Redis做消息队列,实现一个异步化服务框架;如图:

9d839a4eb6d7ba426e6e80fcaeafa05e.png

2. 利用搭建好的框架实现异步化发送点赞信息和登录异常信息 。

二、具体diamante实现

首先搭建应用Redis做消息队列的异步化框架

6dd7dfdfb4e14b96cbb6bad0078bcc58.png

1.准备

JedisAdapter.java

类中加上lpush 和 bpop的代码用来实现消息队列;加上setObject 和 getObject实现序列化与反序列的过程(将事件存入消息队列的时候要序列化,从队列中取出事件的时候需要反序列化):

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

public longlpush(String key, String value){

Jedis jedis= null;try{

jedis=jedisPool.getResource();returnjedis.lpush(key, value);

}catch(Exception e){

logger.error("Jedis lpush 发生异常 " +e.getMessage());return 0;

}finally{if(jedis != null){try{

jedis.close();

}catch(Exception e){

logger.error("Jedis 关闭异常 " +e.getMessage());

}

}

}

}public List brpop(inttimeout, String key){

Jedis jedis= null;try{

jedis=jedisPool.getResource();returnjedis.brpop(timeout, key);

}catch(Exception e){

logger.error("Jedis brpop发生异常 " +e.getMessage());return null;

}finally{if (jedis != null){try{

jedis.close();

}catch(Exception e){

logger.error("Jedis 关闭异常" +e.getMessage());

}

}

}

}//序列化

public voidsetObject(String key, Object object){

set(key, JSON.toJSONString(object));

}//反序列化

public T getObject(String key, Classclazz){

String value=get(key);if(value != null){returnJSON.parseObject(value, clazz);

}return null;

}

View Code

RedisKeyUtil.java

类中加上一个生成事件key的方法,以后的事件都存入这个key对应的set集合中。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

private static String BIZ_EVENT = "DISLIKE";/*** 事件发生的时候,生成key

*@return

*/

public staticString getEventQueueKey(){returnBIZ_EVENT;

}

View Code

2. 异步化框架

EventType.java :事件类型

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.nowcoder.async;/*** Created by Administrator on 2017/5/7.*/

public enumEventType {

LIKE(0),

COMMENT(1),

LOGIN(1),

MAIL(3);private intvalue;public intgetValue() {returnvalue;

}

EventType(intvalue) {this.value =value;

}

}

View Code

EventModel.java : 发生的事件的数据都打包成一个Model(然后对这个model中数据进行序列化)

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.nowcoder.async;importjava.util.HashMap;importjava.util.Map;/*** Created by Administrator on 2017/5/7.*/

public classEventModel {privateEventType type;//事件触发者

private intactorId;//表示一个触发事件的对象

private intentityId;private intentityType;//事件对象的拥有者

private intentityOwnerId;//存放触发的事件数据

Map exts = new HashMap<>();publicEventModel(EventType type){this.type =type;

}publicString getExt(String key) {returnexts.get(key);

}publicEventModel setExt(String key, String value) {

exts.put(key, value);return this;

}publicEventModel(){

}publicEventType getType() {returntype;

}publicEventModel setType(EventType type) {this.type =type;return this;

}public intgetActorId() {returnactorId;

}public EventModel setActorId(intactorId) {this.actorId =actorId;return this;

}public intgetEntityId() {returnentityId;

}public EventModel setEntityId(intentityId) {this.entityId =entityId;return this;

}public intgetEntityType() {returnentityType;

}public EventModel setEntityType(intentityType) {this.entityType =entityType;return this;

}public intgetEntityOwnerId() {returnentityOwnerId;

}public EventModel setEntityOwnerId(intentityOwnerId) {this.entityOwnerId =entityOwnerId;return this;

}public MapgetExts() {returnexts;

}public void setExts(Mapexts) {this.exts =exts;

}

}

View Code

EventProducer.java : 将发生的事件推送到消息队列。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.nowcoder.async;importcom.alibaba.fastjson.JSONObject;importcom.nowcoder.util.JedisAdapter;importcom.nowcoder.util.RedisKeyUtil;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;/*** Created by Administrator on 2017/5/7.*/@Servicepublic classEventProducer {private static final Logger logger = LoggerFactory.getLogger(EventProducer.class);

@Autowired

JedisAdapter jedisAdapter;/*** 将产生的事件model推送到redis的工作队列中

*@parammodel

*@return

*/

public booleanfireEvent(EventModel model){try{//序列化

String json =JSONObject.toJSONString(model);//产生key

String eventkey =RedisKeyUtil.getEventQueueKey();//放入工作队列

jedisAdapter.lpush(eventkey, json);return true;

}catch(Exception e){

logger.error("EventProducer fireEvent 发生异常 : " +e.getMessage());return false;

}

}

}

View Code

EventConsumer.java : 从消息队列中获取事件交给Handler类进行处理。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.nowcoder.async;importcom.alibaba.fastjson.JSON;importcom.nowcoder.util.JedisAdapter;importcom.nowcoder.util.RedisKeyUtil;importjdk.nashorn.api.scripting.JSObject;importorg.apache.commons.collections.map.HashedMap;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.BeansException;importorg.springframework.beans.factory.InitializingBean;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.ApplicationContext;importorg.springframework.context.ApplicationContextAware;importorg.springframework.stereotype.Service;importjava.util.ArrayList;importjava.util.HashMap;importjava.util.List;importjava.util.Map;/*** Created by Administrator on 2017/5/7.*/@Servicepublic class EventConsumer implementsInitializingBean, ApplicationContextAware{private static final Logger logger = LoggerFactory.getLogger(EventConsumer.class);//用来存储各种type事件的Handler

private Map> config = new HashMap>();privateApplicationContext applicationContext;

@Autowired

JedisAdapter jedisAdapter;

@Overridepublic void afterPropertiesSet() throwsException {//获取上下文所有实现EventHandler的类//使用BeanFatory的getBeansOfType()方法,该方法返回一个Map类型的实例,Map中的key为Bean的名,key对应的内容为Bean的实例。

Map beans = applicationContext.getBeansOfType(EventHandler.class);if (beans != null){for (Map.Entryentry : beans.entrySet()){

List eventTypes =entry.getValue().getSupportEventType();for(EventType type : eventTypes){//初始化的时候,若没有type,就加入

if(!config.containsKey(type)){

config.put(type,new ArrayList());

}

config.get(type).add(entry.getValue());

}

}

}//启动线程从工作队列中取出事件进行处理

Thread thread = new Thread(newRunnable() {

@Overridepublic voidrun() {while (true){

String key=RedisKeyUtil.getEventQueueKey();//从Redis数据库的键为key的set集合中获取存储的事件(事件Event为序列化过的,String类型)

List events = jedisAdapter.brpop(0, key);for(String message : events){if(message.equals(key)){continue;

}

EventModel eventModel= JSON.parseObject(message, EventModel.class);//若事件没有注册过

if (!config.containsKey(eventModel.getType())){

logger.error("不能识别的事件 ");continue;

}//获取关注过该事件的handler,一一进行处理事件

for(EventHandler handler : config.get(eventModel.getType())){

handler.doHandle(eventModel);

}

}

}

}

});

thread.start();

}/*** 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量.*/@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throwsBeansException {this.applicationContext =applicationContext;

}

}

View Code

EventHandler.java: 接口,可以从消费者中获取事件交给对应的Handler实现类去处理:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.nowcoder.async;importjava.util.List;/*** Created by Administrator on 2017/5/7.*/

public interfaceEventHandler {//对EventConsumer中的event事件进行处理

voiddoHandle(EventModel model);//获取哪些关注事件类型

ListgetSupportEventType();

}

View Code

LikeHandler.java: 实现点赞通知的类

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.nowcoder.async.handler;importcom.nowcoder.async.EventHandler;importcom.nowcoder.async.EventModel;importcom.nowcoder.async.EventType;importcom.nowcoder.model.HostHolder;importcom.nowcoder.model.Message;importcom.nowcoder.model.User;importcom.nowcoder.service.MessageService;importcom.nowcoder.service.UserService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;importjava.util.Arrays;importjava.util.Date;importjava.util.List;/*** Created by Administrator on 2017/5/7.*/@Componentpublic class LikeHandler implementsEventHandler{

@Autowired

MessageService messageService;

@Autowired

UserService userService;

@Autowired

HostHolder hostHolder;

@Overridepublic voiddoHandle(EventModel model) {

System.out.print("有人点赞了");

Message message= newMessage();//测试方便查看:就是自己发送给自己站内信//int fromId = model.getActorId();//int toId = fromId;//正常情况下fromId是当前点赞用户id,toId是点赞的咨询news所在的id//actorId = hostHolder.getUser().getId();

int fromId =model.getActorId();//entityOwnerId = news.getId()

int toId =model.getEntityOwnerId();

message.setHasRead(0);//0 代表未读 1 代表已读

message.setFromId(fromId);

message.setToId(toId);

message.setConversationId(fromId< toId ? String.format("%d_$d", fromId, toId) : String.format("%d_%d", toId, fromId));

User user=userService.getUser(model.getActorId());

message.setContent("用户" +user.getName()+ "赞了你的资讯,http://127.0.0.1:8080/news/" +model.getEntityId());

message.setCreatedDate(newDate());

messageService.addMessage(message);

}

@Overridepublic ListgetSupportEventType() {returnArrays.asList(EventType.LIKE);

}

}

View Code

LoginExceptionHandler:登录时发生登录异常到对应的站内信,以及实现邮件发送

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.nowcoder.async.handler;importcom.nowcoder.async.EventHandler;importcom.nowcoder.async.EventModel;importcom.nowcoder.async.EventType;importcom.nowcoder.model.Message;importcom.nowcoder.service.MessageService;importcom.nowcoder.util.MailSender;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;import java.util.*;/*** Created by Administrator on 2017/5/7.*/@Servicepublic class LoginExceptionHandler implementsEventHandler{

@Autowired

MessageService messageService;

@Autowired

MailSender mailSender;

@Overridepublic voiddoHandle(EventModel model) {//判断是否有异常登陆

Message message = newMessage();

message.setToId(model.getActorId());

message.setContent("你上次的登陆ip异常");

message.setFromId(17);

message.setCreatedDate(newDate());

messageService.addMessage(message);//邮件发送

Map map = new HashMap();

map.put("username", model.getExt("username"));

mailSender.sendWithHTMLTemplate(model.getExt("email"), "登陆异常", "mails/welcome.html",

map);

}

@Overridepublic ListgetSupportEventType() {returnArrays.asList(EventType.LOGIN);

}

}

View Code

3. 邮件发送

引入jar包:

com.sun.mail

javax.mail

1.5.5

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.nowcoder.util;importorg.apache.velocity.app.VelocityEngine;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.InitializingBean;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.mail.javamail.JavaMailSenderImpl;importorg.springframework.mail.javamail.MimeMessageHelper;importorg.springframework.stereotype.Service;importjavax.mail.internet.MimeUtility;importorg.springframework.ui.velocity.VelocityEngineUtils;importjavax.mail.internet.InternetAddress;importjavax.mail.internet.MimeMessage;importjava.util.Map;importjava.util.Properties;/*** Created by Administrator on 2017/5/7.*/@Servicepublic class MailSender implementsInitializingBean {private static final Logger logger = LoggerFactory.getLogger(MailSender.class);privateJavaMailSenderImpl mailSender;

@AutowiredprivateVelocityEngine velocityEngine;public booleansendWithHTMLTemplate(String to, String subject,

String template, Mapmodel) {try{

String nick= MimeUtility.encodeText("阮宏宝");

InternetAddress from= new InternetAddress(nick + "<1032335358@qq.com>");

MimeMessage mimeMessage=mailSender.createMimeMessage();

MimeMessageHelper mimeMessageHelper= newMimeMessageHelper(mimeMessage);

String result=VelocityEngineUtils

.mergeTemplateIntoString(velocityEngine, template,"UTF-8", model);

mimeMessageHelper.setTo(to);

mimeMessageHelper.setFrom(from);

mimeMessageHelper.setSubject(subject);

mimeMessageHelper.setText(result,true);

mailSender.send(mimeMessage);return true;

}catch(Exception e) {

logger.error("发送邮件失败" +e.getMessage());return false;

}

}

@Overridepublic void afterPropertiesSet() throwsException {

mailSender= newJavaMailSenderImpl();

mailSender.setUsername("1032335358@qq.com");

mailSender.setPassword("***********");

mailSender.setHost("smtp.qq.com");

mailSender.setPort(465);

mailSender.setProtocol("smtps");

mailSender.setDefaultEncoding("utf8");

Properties javaMailProperties= newProperties();

javaMailProperties.put("mail.smtp.ssl.enable", true);

mailSender.setJavaMailProperties(javaMailProperties);

}

}

View Code

4. 测试

LikeController.java:

点赞的时候加入异步点赞通知:

//异步发送

eventProducer.fireEvent(newEventModel(EventType.LIKE)

.setActorId(hostHolder.getUser().getId())

.setEntityId(newsId)

.setEntityType(EntityType.ENTITY_NEWS)

.setEntityOwnerId(news.getUserId()));

LoginController.java

登录时加上登录异常的通知:

eventProducer.fireEvent(newEventModel(EventType.LOGIN)

.setActorId(18)

.setExt("username", username).setExt("email", "1032335358 @qq.com"));

5 相关代码

LoginController.java

packagecom.nowcoder.controller;importcom.nowcoder.async.EventModel;importcom.nowcoder.async.EventProducer;importcom.nowcoder.async.EventType;importcom.nowcoder.service.UserService;importcom.nowcoder.util.ToutiaoUtil;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Controller;importorg.springframework.ui.Model;import org.springframework.web.bind.annotation.*;importjavax.servlet.http.Cookie;importjavax.servlet.http.HttpServletResponse;importjava.util.Map;/*** Created by Administrator on 2017/4/8.*/@Controllerpublic classLoginController {private static final Logger logger = LoggerFactory.getLogger(LoginController.class);

@Autowired

UserService userService;

@Autowired

EventProducer eventProducer;

@RequestMapping(path= {"/reg/"}, method ={RequestMethod.GET, RequestMethod.POST})

@ResponseBodypublicString reg(Model model,

@RequestParam("username") String username,

@RequestParam("password") String password,

@RequestParam(value= "rember", defaultValue = "0") intrember,

HttpServletResponse response){try{

Map map =userService.register(username, password);if(map.containsKey("ticket")){

Cookie cookie= new Cookie("ticket", map.get("ticket").toString());

cookie.setPath("/");//有记住我,就设置时间长一点

if(rember > 0){

cookie.setMaxAge(3600 * 24 * 5);

}

response.addCookie(cookie);return ToutiaoUtil.getJSONString(0, "注册成功");

}else{return ToutiaoUtil.getJSONString(1, map);

}

}catch(Exception e){

logger.error("注册异常" +e.getMessage());return ToutiaoUtil.getJSONString(1, "注册异常");

}

}

@RequestMapping(path= {"/login/"}, method ={RequestMethod.GET, RequestMethod.POST})

@ResponseBodypublicString login(Model model,

@RequestParam("username") String username,

@RequestParam("password") String password,

@RequestParam(value= "rember", defaultValue = "0") intrememberme,

HttpServletResponse response){try{

Map map =userService.login(username, password);if (map.containsKey("ticket")) {

Cookie cookie= new Cookie("ticket", map.get("ticket").toString());

cookie.setPath("/");if (rememberme > 0) {

cookie.setMaxAge(3600*24*5);

}

response.addCookie(cookie);

eventProducer.fireEvent(newEventModel(EventType.LOGIN)

.setActorId(18)

.setExt("username", username).setExt("email", "1032335358 @qq.com"));return ToutiaoUtil.getJSONString(0, "登录成功");

}else{return ToutiaoUtil.getJSONString(1, map);

}

}catch(Exception e) {

logger.error("登录异常" +e.getMessage());return ToutiaoUtil.getJSONString(1, "登录异常");

}

}

@RequestMapping(path= {"/logout/"}, method ={RequestMethod.POST, RequestMethod.GET})public String logout(@CookieValue("ticket") String ticket){

userService.logout(ticket);return "redirect:/";

}

}

LikeController.java

packagecom.nowcoder.controller;importcom.nowcoder.async.EventModel;importcom.nowcoder.async.EventProducer;importcom.nowcoder.async.EventType;importcom.nowcoder.model.EntityType;importcom.nowcoder.model.HostHolder;importcom.nowcoder.model.News;importcom.nowcoder.service.LikeService;importcom.nowcoder.service.NewsService;importcom.nowcoder.util.ToutiaoUtil;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Controller;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RequestMethod;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.ResponseBody;/*** Created by Administrator on 2017/5/1.*/@Controllerpublic classLikeController {

@Autowired

LikeService likeService;

@Autowired

NewsService newsService;

@Autowired

HostHolder hostHolder;

@Autowired

EventProducer eventProducer;

@RequestMapping(path= {"/like"}, method ={RequestMethod.GET, RequestMethod.POST})

@ResponseBodypublic String like(@RequestParam("newsId") intnewsId){//在likeKey对应的集合中加入当前用户

long likeCount =likeService.like(hostHolder.getUser().getId(), EntityType.ENTITY_NEWS, newsId);//资讯上更新点赞数

News news =newsService.getById(newsId);

newsService.updateLikeCount(newsId, (int)likeCount);//异步发送

eventProducer.fireEvent(newEventModel(EventType.LIKE)

.setActorId(hostHolder.getUser().getId())

.setEntityId(newsId)

.setEntityType(EntityType.ENTITY_NEWS)

.setEntityOwnerId(news.getUserId()));return ToutiaoUtil.getJSONString(0, String.valueOf(likeCount));

}

@RequestMapping(path= {"/dislike"}, method ={RequestMethod.POST, RequestMethod.GET})

@ResponseBodypublic String disLike(@RequestParam("newsId") intnewsId){//在disLikeKey对应的集合中加入当前用户

long likeCount =likeService.disLike(hostHolder.getUser().getId(), EntityType.ENTITY_NEWS, newsId);if(likeCount <= 0){

likeCount= 0;

}//资讯上更新喜欢数

newsService.updateLikeCount(newsId, (int)likeCount);return ToutiaoUtil.getJSONString(0, String.valueOf(likeCount));

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值