一、需求描述
1. 利用Redis做消息队列,实现一个异步化服务框架;如图:
2. 利用搭建好的框架实现异步化发送点赞信息和登录异常信息 。
二、具体diamante实现
首先搭建应用Redis做消息队列的异步化框架
1.准备
JedisAdapter.java
类中加上lpush 和 bpop的代码用来实现消息队列;加上setObject 和 getObject实现序列化与反序列的过程(将事件存入消息队列的时候要序列化,从队列中取出事件的时候需要反序列化):
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集合中。
private static String BIZ_EVENT = "DISLIKE";/*** 事件发生的时候,生成key
*@return
*/
public staticString getEventQueueKey(){returnBIZ_EVENT;
}
View Code
2. 异步化框架
EventType.java :事件类型
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中数据进行序列化)
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 : 将发生的事件推送到消息队列。
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类进行处理。
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实现类去处理:
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: 实现点赞通知的类
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:登录时发生登录异常到对应的站内信,以及实现邮件发送
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
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));
}
}