文章目录
- 前言
- 技术架构
- 效果图
- 后端项目
- 消息数据定义
- 存储结构
- 消息状态
- Nutty消息服务
- 项目结构
- 改动
- 消息bean
- 消息处理器
- 消息转换pojo工具
- 审核消息处理
- controller
- 实现类
- 服务调用
- 前端
- 连接代码
- 初始化
- 接受消息
- 消息的展示
- 效果
- 总结
前言
其实关于这个的话,我先前的几篇博文:
SpringBoot+Netty+Vue+Websocket实现在线推送/聊天系统
实用水文篇–SpringBoot整合Netty实现消息推送服务器
其实已经说的非常明白了,但是每想到后台还是有人找我要代码,因为完整的代码其实在博文里面都是贴出来了的,当然前面的博文我都是给一个类似于脚手架的东西,没有给出实际的案例。那么今天也是直接给出代码的案例吧,这里再次声明,所有的代码都是在这两篇博文里面有的,如果做了修改在本文当中会给出提示。此外的话,我们的项目是完全开源的,但是在开发阶段不开源,因为有一些铭感信息。在开发完成之后开源,这里请见谅,当然也是为什么我没有办法给出源码的原因(暂时)也是为什么你可以在我的那两篇博文看到完整的代码信息的原因。
Tips:
在我的频道只会告诉你怎么做饭,不会把饭菜做好了再给你,如果需要,那是另外“价格”。
那么废话不多说,我们开始。
技术架构
我们今天来看到我们的一个案例。首先是我们的技术架构:
那么在我们今天的话是这样的:
- 用户上传博文
- 博文通过之后发送审核消息给前端
- 前端对消息进行展示
效果图
这个是我们上传博文,博文通过之后会看到消息有个提示。
之后到具体的页面可以看到消息
因为图片是中午截取的,有个小bug没有调整,但是不要在意这些,这个bug是很简单的,因为一开始我这边做过初始化,没有清除缓存罢了。
后端项目
之后的话来看到我们的后端的一个服务情况。
我们要进行互动的服务就是这三个,一个是网关,一个是博文的服务,还有就是我们的消息服务器。因为我们是用户上传成功后再显示的。
那么关于博客的模块的话在这块有完整的说明:
SpringBoot + Vue实现博文上传+展示+博文列表
我们这边做的就是一个系列。当然只是演示实际上,你用先前我给出的代码是完全可以实现效果的,我们这边只是说用那一套代码来真正做我们具体的业务。
消息数据定义
存储结构
那么在开始之前的话,我们这边对我们的消息服务器设计了对应的数据库用来存储消息。
这一块看你自己,我们这边肯定是要的。
那么我们这次的案例需要使用到的表是这个表:
消息状态
之后的话,我们需要对我们的消息进行定义。
我们在这里对消息的状态做出如下定义:
- 消息具备两者状态,针对两个情况
- 签收状态,即,服务器确定用户在线,并且将消息传输到了客户端,为签收状态。
- 阅读状态,在保证已签收的情况下,用户是否已经阅读消息,这部分的逻辑有客户端代码处理。
- 对应未签收的消息,用户上线时,请求服务器是否存在未签收的消息,如果有,进行统一读取,存储到本地
- 对于未读消息,主要是对用户的状态进行一个判断,消息已经缓存到用户本地。
那么此时的话,我们就已经说清楚了这个。在我们的数据库里面status这个字段就是用来判断用户是不是签收了消息的。至于用户到底有没有读取消息,那么完全就是客户端需要做的判断了。
当然你也可以设计为全部由服务端来处理。
Nutty消息服务
项目结构
ok,说完了这个的话,我们再来看到我们的消息服务端是怎么处理的。
首先我们依然是和先前的博文一样,保留了先前的东西。
但是我们这边多了Controller,和Dao层。
那么在这边的话,我们需要关注的只有这几个东西:
这几个东西就是我们来实现前面的效果的实际的业务代码。
除了这些当然还有我们的Dao,但是这个是根据你的业务来的,这里我就不展示了,类比嘛。
改动
那么说完了这些,我们来看到和先前的代码有哪些改动的东西。
消息bean
首先是我们的消息的改动。
@AllArgsConstructor
@NoArgsConstructor
@ToString
/**
* 由于我们这边Nutty处理的消息只有注册,所以话这里只需要
* 保留action和userid即可
* */
public class DataContent implements Serializable {
private Integer action;
private String userid;
}
那么我们的消息的类型是这样的:
public enum MessageActionEnum {
//定义消息类型
CONNECT(1,"第一次(或重连)初始化连接"),
CHAT(2,"聊天消息"),
SIGNED(3,"消息签收"),
KEEPALIVE(4,"客户端保持心跳"),
PULL_FRIEND(5, "拉取好友"),
HOLEADUITMSG(6,"审核消息");
public final Integer type;
public final String content;
MessageActionEnum(Integer type,String content) {
this.type = type;
this.content = content;
}
}
消息处理器
既然我们的这个消息类型变了,那么我们的这个代码也变了:
@Component
@ChannelHandler.Sharable
public class ServerListenerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
private static final Logger log = LoggerFactory.getLogger(ServerBoot.class);
static {
//先初始化出来
UserConnectPool.getChannelMap();
UserConnectPool.getChannelGroup();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
String content = msg.text();
/**获取客户端传过来的消息*/
DataContent dataContent = JsonUtils.jsonToPojo(content, DataContent.class);
assert dataContent != null;
Integer action = dataContent.getAction();
Channel channel = ctx.channel();
/**
* 根据消息类型对其进行处理,我们这里只做两个事情
* 1. 注册用户
* 2. 心跳在线
* */
if(Objects.equals(action, MessageActionEnum.CONNECT.type)){
/**
* 2.1 当websocket 第一次 open 的时候,
* 初始化channel,把用的 channel 和 userid 关联起来
* */
String userid = dataContent.getUserid();
AttributeKey<String> key = AttributeKey.valueOf("userId");
ctx.channel().attr(key).setIfAbsent(userid);
UserConnectPool.getChannelMap().put(userid,channel);
UserConnectPool.output();
} else if(Objects.equals(action, MessageActionEnum.KEEPALIVE.type)){
/**
* 心跳包的处理
* */
System.out.println("收到来自channel 为["+channel+"]的心跳包"+dataContent);
channel.writeAndFlush(
new TextWebSocketFrame(
JsonUtils.objectToJson(R.ok("返回心跳包").
put("type", MessageActionEnum.KEEPALIVE.type))
)
);
System.out.println("已返回消息");
}
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//接收到请求
log.info("有新的客户端链接:[{}]", ctx.channel().id().asLongText());
AttributeKey<String> key = AttributeKey.valueOf("userId");
ctx.channel().attr(key).setIfAbsent("temp");
UserConnectPool.getChannelGroup().add(ctx.channel());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
String chanelId = ctx.channel().id().asShortText();
log.info("客户端被移除:channel id 为:"+chanelId);
removeUserId(ctx);
UserConnectPool.getChannelGroup().remove(ctx.channel());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
//发生了异常后关闭连接,同时从channelgroup移除
ctx.channel().close();
removeUserId(ctx);
UserConnectPool.getChannelGroup().remove(ctx.channel());
}
/**
* 删除用户与channel的对应关系
*/
private void removeUserId(ChannelHandlerContext ctx) {
AttributeKey<String> key = AttributeKey.valueOf("userId");
String userId = ctx.channel().attr(key).get();
UserConnectPool.getChannelMap().remove(userId);
}
}
这个就是我们核心的消息处理器。
那么其他的关于Nutty的玩意我压根没有改动。
消息转换pojo工具
这里还有咱们的消息转换的工具类。这个的话,我也给一下:
public class JsonUtils {
// 定义jackson对象
private static final ObjectMapper MAPPER = new ObjectMapper();
/**
* 将对象转换成json字符串。
* <p>Title: pojoToJson</p>
* <p>Description: </p>
* @param data
* @return
*/
public static String objectToJson(Object data) {
try {
String string = MAPPER.writeValueAsString(data);
return string;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
/**
* 将json结果集转化为对象
*
* @param jsonData json数据
* @param beanType 对象类型
* @return
*/
public static <T> T jsonToPojo(String jsonData, Class<T> beanType) {
try {
T t = MAPPER.readValue(jsonData, beanType);
return t;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 将json数据转换成pojo对象list
* <p>Title: jsonToList</p>
* <p>Description: </p>
* @param jsonData
* @param beanType
* @return
*/
public static <T>List<T> jsonToList(String jsonData, Class<T> beanType) {
JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType);
try {
List<T> list = MAPPER.readValue(jsonData, javaType);
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
前面应该是有给的。
重点就是咱们的这个Controller这一块。
审核消息处理
controller
我们首先来看到我们的Controller
@RestController
@RequestMapping("/message/holeAduit")
public class HoleAduitMsgController {
@Autowired
HoleAduitMsgService holeAduitMsgService;
@PostMapping("/aduit")
public R holeAduitMsg(@Validated @RequestBody HoleAduitMsgQ holeAduitMsgQ){
return holeAduitMsgService.holeaduitMsg(holeAduitMsgQ);
}
}
我们只看到这一个接口,因为其他的都是类似的。
那么这里的话我们还是需要一个请求的实体类的。
那么这个实体类的话是这个样子的:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class HoleAduitMsgQ {
@NotEmpty
private String userid;
private String msg;
private String msgtitle;
private Long linkid;
private Integer type;
}
这个实体类的话,是被我封装了这里:
因为我们是一个微服务,所以的话,对应的这个请求我们都是放在了第三方的一个包下面。
那么对于的还有咱们暴露出来的服务。
@FeignClient("message")
@RequestMapping("/message/holeAduit")
public interface FeignHoleAduitMsgService {
@PostMapping("/aduit")
public R holeAduitMsg(@RequestBody HoleAduitMsgQ holeAduitMsgQ);
}
之后的话,我们可以看到具体的实现类。
实现类
@Service
public class HoleAduitMsgServiceImpl implements HoleAduitMsgService {
@Autowired
HoleAuditService auditService;
@Override
public R holeaduitMsg(HoleAduitMsgQ holeAduitMsgQ) {
//1.对消息进行存储,只要用户在线的话,我们就直接先给他签收一下
String userid = holeAduitMsgQ.getUserid();
Channel channel = UserConnectPool.getChannelFromMap(userid);
HoleAuditEntity holeAuditEntity = new HoleAuditEntity();
BeanUtils.copyProperties(holeAduitMsgQ,holeAuditEntity);
holeAuditEntity.setCreateTime(DateUtils.getCurrentTime());
if(channel!=null){
//这边只是保证存在,双层保险,这个时候的话就是在线
Channel realChannel = UserConnectPool.getChannelGroup().find(channel.id());
if(realChannel!=null){
holeAuditEntity.setStatus(1);
//我们这边直接转发消息就好了,不需要再额外处理
realChannel.writeAndFlush(
new TextWebSocketFrame(
JsonUtils.objectToJson(
Objects.requireNonNull(R.ok().put("data", holeAuditEntity))
.put("type", MessageActionEnum.HOLEADUITMSG.type)
)
)
);
}
}
//这里进行消息的存储
auditService.save(holeAuditEntity);
return R.ok();
}
}
这里面的逻辑其实非常简单,就几个步骤。
1.接受请求
2.判断用户是否在线,在线推送,并保存设置为已签收(消息)
如果不在线,不进行推送,但是保存消息并设置为未签收
这里的话就是非常简单的。
服务调用
之后的话,就是我们的调用。我们的调用是在我们的博客服务进行调用的。
我们先看到我们完整的博客服务的实现类。
public class BlogUpServiceImpl implements BlogUpService {
@Autowired
FeignUserService feignUserService;
@Autowired
ContentService contentService;
@Autowired
FeignHeadimgService feignHeadimgService;
@Autowired
WordFilter wordFilter;
@Autowired
BlogService blogService;
@Autowired
FeignLogActicleService feignLogActicleService;
@Autowired
RedisUtils redisUtils;
@Autowired
FeignHoleAduitMsgService feignHoleAduitMsgService;
private final static Double threshold = 0.05;
/**
* 接口对用户进行十分钟限制
* 1.完成用户博文的上传
* 2.存储用户博文,博文对应信息
* 3.修改用户日志
* */
@Override
public R blogUp(UpBlogEntity entity) {
String userid = entity.getUserid();
String backMessage = "success";
//接口限流
if(redisUtils.hasKey(RedisTransKey.getBlogUpKey(entity.getUserid()))){
return R.error(BizCodeEnum.OVER_UPBLOG.getCode(), BizCodeEnum.OVER_UPBLOG.getMsg());
}
R info = feignUserService.info(userid);
String userString = FastJsonUtils.toJson(info.get("user"));
UserEntity user = FastJsonUtils.fromJson(userString, UserEntity.class);
if(user!=null){
String context = entity.getContext();
String blogInfo = entity.getInfo();
/**
* 先对context和bloginfo进行校验,是否为存在不友好的信息
* */
int countContext = wordFilter.wordCount(context);
int countInfo = wordFilter.wordCount(blogInfo);
int status = 1;
//博文的摘要过滤,只要摘要没有过,直接先打回去!
if(countInfo>=blogInfo.length()*threshold){
return R.error(BizCodeEnum.BAD_BLOGINFO.getCode(),BizCodeEnum.BAD_BLOGINFO.getMsg());
}
//博文内容的过滤
if(countContext>=context.length()*threshold){
//直接就是没有通过审核
return R.error(BizCodeEnum.BAD_CONTEXT.getCode(),BizCodeEnum.BAD_CONTEXT.getMsg());
}else if (countContext>0&&countContext<context.length()*threshold){
backMessage="哇!您的提交直接通过了呢!";
}else {
status = 2;
context = wordFilter.replace(context, '*');
backMessage="您的提问已提交,正在等待审核哟!";
}
//预存储content
ContentEntity contentEntity = new ContentEntity();
contentEntity.setContent(context);
contentEntity.setVersion("1.0");
contentEntity.setCreateTime(DateUtils.getCurrentTime());
contentService.save(contentEntity);
Long contentid = contentEntity.getContentid();
//预存储博文
BlogEntity blogEntity = new BlogEntity();
blogEntity.setBlogTitle(entity.getBlogTitle());
blogEntity.setLevel(entity.getLevel());
blogEntity.setBlogtype(entity.getBlogtype());
//查询用户的头像信息
R RHeadImg = feignHeadimgService.headimg(userid);
String headImgString = FastJsonUtils.toJson(RHeadImg.get("headimg"));
final HeadimgEntity headimg = FastJsonUtils.fromJson(headImgString, HeadimgEntity.class);
if(headimg!=null){
blogEntity.setUserImg(headimg.getImgpath());
}
blogEntity.setCreateTime(DateUtils.getCurrentTime());
blogEntity.setUserNickname(user.getNickname());
blogEntity.setUserid(userid);
blogEntity.setStatus(status);
blogEntity.setInfo(blogInfo);
blogService.save(blogEntity);
Long blogid = blogEntity.getBlogid();
//完成正式存储
contentEntity.setBlogid(blogid);
blogEntity.setContentid(contentid);
blogService.updateById(blogEntity);
contentService.updateById(contentEntity);
/**
* 更新用户日志
* */
LogActicleEntity logActicleEntity = new LogActicleEntity();
logActicleEntity.setAction(1);
logActicleEntity.setUserid(userid);
logActicleEntity.setArticleid(blogEntity.getBlogid());
logActicleEntity.setArticleTitle(blogEntity.getBlogTitle());
logActicleEntity.setCreteTime(blogEntity.getCreateTime());
feignLogActicleService.save(logActicleEntity);
/**
* 发送消息
* */
if(status==1){
/**
* 此时是直接通过了审核,那么直接进行发送
* 如果没有的话,那么就是后台通过审核由MQ发送消息
* */
HoleAduitMsgQ holeAduitMsgQ = new HoleAduitMsgQ();
holeAduitMsgQ.setMsg("您的博文"+blogEntity.getBlogTitle()+"直接通过了审核");
holeAduitMsgQ.setMsgtitle("博文审核通过");
holeAduitMsgQ.setUserid(user.getUserid());
holeAduitMsgQ.setLinkid(blogid);
holeAduitMsgQ.setType(1);
feignHoleAduitMsgService.holeAduitMsg(holeAduitMsgQ);
}
/**
* 设置标志
*/
redisUtils.set(RedisTransKey.setBlogUpKey(entity.getUserid())
,1,10, TimeUnit.MINUTES
);
}else{
return R.error(BizCodeEnum.NO_SUCHUSER.getCode(),BizCodeEnum.NO_SUCHUSER.getMsg());
}
return R.ok(backMessage);
}
}
这里面有注释,重点就是那个发送消息的。
到此的话,我们的后端就没啥事情了。
前端
之后的话就是我们的前端。
我们的前端主要是负责两件事情
- 向服务器注册(如果用户登录了的话)
- 保持心跳在线
- 接收服务器发送过来的消息,并保存,然后要通知用户
连接代码
首先是我们先前博文有说到的,我们的连接封装好的代码。
我这里是放在了socket包下面,其他的你们自己看着办。
// 导出socket对象
export {
socket
}
import { Message } from 'element-ui'
// socket主要对象
var socket = {
websock: null,
/**
* 这个是我们的ws的地址
* */
ws_url: "ws://localhost:9000/ws",
userid: null,
msgfunc: null,
/**
* 开启标识
* */
socket_open: false,
/**
* 心跳timer
* */
hearbeat_timer: null,
/**
* 心跳发送频率
* */
hearbeat_interval: 10000,
/**
* 是否开启重连
* */
is_reonnect: true,
/**
* 重新连接的次数
* */
reconnect_count: 3,
/**
* 当前重新连接的次数,默认为:1
* */
reconnect_current: 1,
/**
* 重新连接的时间类型
* */
reconnect_timer: null,
/**
* 重新连接的间隔
* */
reconnect_interval: 3000,
/**
* 登录后才进行连接
* */
/**
* 初始化连接
*/
init: () => {
let loginToken = localStorage.getExpire("LoginToken");
let userid = localStorage.getExpire("userid");
if(loginToken==null && userid==null) {
Message({
message: '当前正在以游客身份访问',
type: 'info',
});
return ;
}
if (!("WebSocket" in window)) {
Message({
message: '当前浏览器与网站不兼容丫',
type: 'error',
});
console.log('浏览器不支持WebSocket')
return null
}
// 已经创建过连接不再重复创建
if (socket.websock) {
return socket.websock
}
socket.websock = new WebSocket(socket.ws_url)
socket.websock.onmessage = function (e) {
socket.receive(e)
}
// 关闭连接
socket.websock.onclose = function (e) {
console.log('连接已断开')
console.log('connection closed (' + e.code + ')')
clearInterval(socket.hearbeat_interval)
socket.socket_open = false
// 需要重新连接
if (socket.is_reonnect) {
socket.reconnect_timer = setTimeout(() => {
// 超过重连次数
if (socket.reconnect_current > socket.reconnect_count) {
clearTimeout(socket.reconnect_timer)
return
}
// 记录重连次数
socket.reconnect_current++
socket.reconnect()
}, socket.reconnect_interval)
}
}
// 连接成功
socket.websock.onopen = function () {
Message({
message: 'Welcome here',
type: 'success',
});
let userid = localStorage.getExpire("userid");
socket.userid = userid;
console.log('连接成功')
socket.socket_open = true
socket.is_reonnect = true
// 开启心跳
socket.heartbeat()
//注册用户
let resit={
"action": 1,
"userid": userid
}
socket.send(resit)
}
// 连接发生错误
socket.websock.onerror = function (err) {
Message({
message: '无法连接至服务器!',
type: 'error',
});
console.log('WebSocket连接发生错误')
}
},
/**
* 获取websocket对象
* */
getSocket:()=>{
//创建了直接返回,反之重来
if (socket.websock) {
return socket.websock
}else {
socket.init();
}
},
getStatus:()=> {
if (socket.websock.readyState === 0) {
return "未连接";
} else if (socket.websock.readyState === 1) {
return "已连接";
} else if (socket.websock.readyState === 2) {
return "连接正在关闭";
} else if (socket.websock.readyState === 3) {
return "连接已关闭";
}
},
/**
* 发送消息
* @param {*} data 发送数据
* @param {*} callback 发送后的自定义回调函数
*/
send: (data, callback = null) => {
// 开启状态直接发送
if (socket.websock.readyState === socket.websock.OPEN) {
socket.websock.send(JSON.stringify(data))
if (callback) {
callback()
}
// 正在开启状态,则等待1s后重新调用
} else if (socket.websock.readyState === socket.websock.CONNECTING) {
setTimeout(function () {
socket.send(data, callback)
}, 1000)
// 未开启,则等待1s后重新调用
} else {
socket.init()
setTimeout(function () {
socket.send(data, callback)
}, 1000)
}
},
/**
* 接收消息
* @param {*} message 接收到的消息
*/
receive: (message) => {
var recData = JSON.parse(message.data)
/**
*这部分是我们具体的对消息的处理
* */
if(socket.msgfunc==null){
Message({
message: 'receive需要传入一个func进行全局消息处理!',
type: 'error',
});
}else {
socket.msgfunc(recData)
}
},
/**
* 心跳
*/
heartbeat: () => {
console.log('socket', 'ping')
if (socket.hearbeat_timer) {
clearInterval(socket.hearbeat_timer)
}
socket.hearbeat_timer = setInterval(() => {
//发送心跳包
let data = {
"action": 4,
"userid": socket.userid
}
socket.send(data)
}, socket.hearbeat_interval)
},
/**
* 主动关闭连接
*/
close: () => {
console.log('主动断开连接')
clearInterval(socket.hearbeat_interval)
socket.is_reonnect = false
socket.websock.close()
},
/**
* 重新连接
*/
reconnect: () => {
console.log('发起重新连接', socket.reconnect_current)
if (socket.websock && socket.socket_open) {
socket.websock.close()
}
socket.init()
},
}
这段代码里面主要有两个重要的地方、
- 初始化的连接
- 发送消息(这边的话我们基本上是通过http发送到具体的服务,然后由消息服务器转发的,但是心跳在线还是需要这个的)
- 接收到消息的处理方法(这个要自己实现的,怎么实现待会会有说明)
初始化
首先是初始化,我这边的话是做消息的推送,包括用户的聊天,所以的话,我们这边是需要全局使用的。那么这边的话,我们初始化需要在你的根页面进行使用。那么我这边是在这边在这里。你们自己的自己看着办。
接受消息
之后是我们接受消息的地方。
哪里需要哪里使用。我这边是在这个地方使用:
这部分的完整代码是这样的:
created() {
/**
* 在这里我们负责对消息进行处理,我们把消息存储到缓存当中
* 到具体的页面的时候,我们就加载,这样就好了。
* */
socket.msgfunc=this.msgfunc;
//加载消息
let messageNum_Local = localStorage.getExpire("messageNum");
let messageContent_Local = localStorage.getExpire("messageContent");
if(messageNum_Local){
this.messageNum = messageNum_Local;
this.total = messageNum_Local.total;
}else {
this.messageNum = messageNum;
localStorage.setExpire("messageNum", this.messageNum,this.OverTime);
this.total = 0;
}
if(messageContent_Local){
this.messageContent = messageContent_Local;
}else {
this.messageContent = messageContent;
//因为一开始有初始值,这个初始值是不要的
delete this.messageContent[0];
localStorage.setExpire("messageContent",this.messageContent,this.OverTime);
}
},
msgfunc(res){
//这个msgfunc是负责处理全局信息的
if(res.type===4){
//心跳正常
}else {
/**
* 这里面就是我们接下来要做的逻辑了,
* 这里的话消息是分为两个状态的,签收和读取
* */
let messageNum_Local = localStorage.getExpire("messageNum");
messageNum_Local.total+=1;
this.total = messageNum_Local.total;
this.messageNum = messageNum_Local;
//加载消息
this.messageContent = localStorage.getExpire("messageContent");
if(res.type===6){
//这个时候是我们的审核消息
this.messageNum.auditNum+=1;
//头部插入消息
this.messageContent.auditContent.unshift(res.data)
}
//当token过期的时候,我们这些东西都需要进行删除
localStorage.setExpire("messageNum",this.messageNum,this.OverTime);
localStorage.setExpire("messageContent",this.messageContent,this.OverTime);
}
},
我们在这个页面实现了对消息的处理,并且让我们的socket进行了指定。
那么这段代码主要做了这些事情
- 指定了消息的处理方法(初始化部分)
- 在消息处理部分,主要是针对消息的类型,进行不同的存储
我们着这边定义了两个消息存储的玩意。
这个就是我们定义存储消息的玩意
let messageNum = {
total: 0,
auditNum: 0,
};
let messageContent = {
auditContent:[
{
userid: null,
msg: null,
msgtitle: null,
linkid: null,
createTime: null,
msgid: null,
status: null,
type: null,
}
]
}
export {
messageNum,
messageContent
}
刚刚的那一段代码就是为了存储消息。这里的话,目前是只定义了一个消息类型。
消息的展示
尽然我们把消息保存起来了,那么我们就需要进行读取了。
那么读取的话很简单啊,我们都存起来了,直接读取加载不就好了。
那么在这边的话,就是咱们的这个页面
<template>
<div style="background-color: rgba(239,250,246,0.53)">
<br>
<div class="head" style="width: 90%;margin: 0 auto">
<el-button style="margin-left:80%" type="primary" plain>清空所有</el-button>
</div>
<br>
<div style="width: 80%;margin-left: 1%" class="main">
<el-card shadow="hover" v-for="(message,index) in Messages" :key="index">
<div style="height:80px">
<div style="display:inline-block;margin-left: 5%">
<p class="message">
<router-link v-if="message.type==1" class="alink"
:to="{ path: '/blogshow',query:{'blogid':message.linkid}}"
>
{{message.msgtitle}} <i class="el-icon-circle-check"></i>
</router-link>
</p>
<el-tooltip class="item" effect="dark" :content="message.msg" placement="top-start">
<p class="message">
查看详情:{{message.msg}}
</p>
</el-tooltip>
</div>
<div style="display:inline-block;margin-left: 20%">
<p>
<el-button icon="el-icon-delete" ></el-button>
</p>
<p style="font-size: 8px">
{{message.createTime}}
</p>
</div>
</div>
<br>
</el-card>
</div>
<br>
<div class="footer" style="margin: 0 auto;width: 100%;">
<div class="block" >
<el-pagination
background
layout="total, prev, pager, next, jumper"
:total=total>
</el-pagination>
</div>
</div>
</div>
</template>
<script>
import {messageContent} from "../../socket/message";
export default {
//这里的话是我们审核消息的具体的页面
name: "auditInformation",
data(){
return{
total: 0,
Messages: messageContent.auditContent,
}
},
created() {
//如果在我们这边本地有缓存的话,那么我们就加载缓存的
//如果没有缓存的话,那么我们就需要去求取服务器,加载
let messageContent= localStorage.getExpire("messageContent");
if(messageContent){
//分页的话,我们在这边做切片即可
this.Messages = messageContent.auditContent;
}else {
//这里去求取服务器拿到数据,然后重新保存在本地
}
}
}
</script>
<style scoped>
.message{
width: 20em;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
.alink{
text-decoration: none;
color: #333333;
}
</style>
这块的实现现在还是很粗糙,反正就是一个案例。这下子的话完整的过程我是已经说明白了的。
其实如果有时间的话,完全是可以把这个封装成一个专门的消息处理中间件的。刚好有用,而且很有必要。这里的话,我可以有机会试试,仿造一下,也搞一个消息中间件玩玩。
效果
之后的 话就是我们的测试效果。
登录有提示:
这边有心跳包:
后端也可以看到连接
编写文章的时候,上传成功后有消息提示:
这个是我们上传博文,博文通过之后会看到消息有个提示。
然后还可以到具体的页面查看。
总结
这里的话,如果这个系列ok了的话,那么此时你已经学会了 CURD+Nutty了。此时你已经可以脱离XXX管理系统了,可以做一个微信聊天之类的东西了。那么在我们这边的不足之处的话,还差一个就是聊天的信息加密,但是这个东西,怎么说呢,如果是类似于md5这种类型的加密,那么是不可能有人破解,但是我也不可能还原,我还原不了,你也就只能看到乱码。我要是可以解密,那么只是保证了在传输的时候是安全的,但是我一样可以看,除非聊天秘钥在你手里,但是你的秘钥总还是要再我们的服务器存储的,至少是要上传的,不然我怎么给你解密。所以这个我可能都不会去加密,顶多传输的时候加一下,甚至不加上一个https就完了。