当前位置:   article > 正文

Springboot +Netty+Vue实现聊天(单聊+创建群聊并聊天)_springboot+vue实现聊天室

springboot+vue实现聊天室

Springboot +Netty+Vue实现简单的单对单聊天

后台

项目结构

在这里插入图片描述

pom文件

主要在SpringBoot项目的pom文件基础上,加上下面的

<dependency>
		<groupId>io.netty</groupId>
		<artifactId>netty-all</artifactId> <!-- Use 'netty-all' for 4.0 or above -->
           <scope>compile</scope>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

目录展示的后端代码

ChannelGroupConfig
package com.ydy.netty.config;

import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;

public class ChannelGroupConfig {
    //存储每一个客户端接入进来的对象
    public static ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
StartWebSocket

初始化配置类。使用了多线程启动,如果主线程启动的话,后面的controller调用会阻塞在那。连接端口使用的8888端口。
使用@PostConstruct注解,在项目加载完所有bean之后,执行该方法

package com.ydy.netty.config;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.concurrent.*;

@Component
public class StartWebSocket {

    private static Logger LOGGER = LoggerFactory.getLogger(StartWebSocket.class);


    private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
            60L, TimeUnit.SECONDS,
            new ArrayBlockingQueue(10));

    @PostConstruct
    public static void initNetty(){
        LOGGER.info("初始化netty,主线程开始");
        executor.execute(new Runnable() {
            @Override
            public void run() {
                action();
            }
        });
        LOGGER.info("初始化netty,主线程结束");
    }

    public static void action(){
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();
        try {
            //开启服务端
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(eventLoopGroup,workGroup);
            serverBootstrap.channel(NioServerSocketChannel.class);
            serverBootstrap.childHandler(new WebSocketChannelHandler());
            LOGGER.info("服务端开启等待客户端连接..");
            Channel channel = serverBootstrap.bind(8888).sync().channel();
            channel.closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //退出程序
            eventLoopGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
WebSocketChannelHandler
package com.ydy.netty.config;

import com.ydy.netty.socket.WebSocketHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;

public class WebSocketChannelHandler extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        pipeline.addLast("http-codec",new HttpServerCodec());
        pipeline.addLast("aggregator",new HttpObjectAggregator(65536));
        pipeline.addLast("http-chunked",new ChunkedWriteHandler());
        pipeline.addLast("handler",new WebSocketHandler());
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
WebSocketHandler 核心处理类

用channelUserMap 存储所有的连接用户键为:用户的code,值为:通道对象
当有用户发送来连接通道请求(和发送信息的请发方式不同),就把该用户加入进去,并查询该用户的所有好友,如果好友存在map中,就拿出该用户的通道。向里面写入XX已上线或者已下线通知。
该类如法自动注入bean对象,所以引入了SpringUtils
广播式发送消息就不需要map通过key拿到特定channel对象,直接写信息就行了。

package com.ydy.netty.socket;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ydy.common.model.ChatRecord;
import com.ydy.common.model.UserFriend;
import com.ydy.common.util.JsonUtil;
import com.ydy.common.vo.UserFriendVo;
import com.ydy.netty.config.ChannelGroupConfig;
import com.ydy.netty.service.NettyService;
import com.ydy.netty.util.SpringUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Timestamp;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

public class WebSocketHandler extends SimpleChannelInboundHandler<Object> {

    //用户id=>channel示例
    //可以通过用户的唯一标识保存用户的channel
    //这样就可以发送给指定的用户
    public static ConcurrentHashMap<String, Channel> channelUserMap = new ConcurrentHashMap<>();

    private WebSocketServerHandshaker webSocketServerHandshaker;

    private static Logger LOGGER = LoggerFactory.getLogger(WebSocketHandler.class);

    private NettyService nettyService;


    /**
     * 每当服务端收到新的客户端连接时,客户端的channel存入ChannelGroup列表中,并通知列表中其他客户端channel
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        //获取连接的channel
        LOGGER.info("handlerAdded,连接channel{},连接id{}",ctx.channel(),ctx.channel().id());
        ChannelGroupConfig.group.add(ctx.channel());
    }

    /**
     *每当服务端断开客户端连接时,客户端的channel从ChannelGroup中移除,并通知列表中其他客户端channel
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        //从服务端的channelGroup中移除当前离开的客户端
        ChannelGroupConfig.group.remove(channel);

        //获得删除channle对应的userCode
        String removeUserCode = "";
        for (String userCode : channelUserMap.keySet()) {
            Channel userChannel = channelUserMap.get(userCode);
            if(userChannel.equals(channel)){
                removeUserCode = userCode;
                break;
            }
        }

        //从服务端的channelMap中移除当前离开的客户端
        Collection<Channel> col = channelUserMap.values();
        while(true == col.contains(channel)) {
            col.remove(ctx.channel());
            LOGGER.info("handlerRemoved,netty客户端连接删除成功!,删除channel:{},channelId:{}",ctx.channel(),ctx.channel().id());
        }
        //通知好友上线下线通知
        sendFriendMsgLoginOrOut(removeUserCode,"notice","下线了");
    }

    /**
     *
     * @Title: sendFriendMsgLoginOrOut
     * @author: dy.yin 2021/4/22 10:49
     * @param: [removeUserCode]
     * @return: void
     * @throws
     */
    private void sendFriendMsgLoginOrOut(String userCode,String type,String message) {

        //查询该用户好友
        nettyService = SpringUtils.getBean(NettyService.class);
        List<UserFriendVo> friendList =  nettyService.getUserFriendsList(userCode);
        for (UserFriendVo friend : friendList) {
            String friendCode = friend.getFriendCode();
            String userName = friend.getUserName();
            if(channelUserMap.containsKey(friendCode)){
                channelUserMap.get(friendCode).writeAndFlush(new TextWebSocketFrame(userName + message));
            }
        }
    }

    /**
     * 服务端监听到客户端活动
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        LOGGER.info("channelActive,netty与客户端建立连接,通道开启!channel{}连接,连接id{}",ctx.channel(),ctx.channel().id());
    }

    /**
     * 服务端监听到客户端不活动
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        LOGGER.info("channelInactive,netty与客户端断开连接,通道关闭!channel:{},channelId:{}",ctx.channel(),ctx.channel().id());
    }

    //工程出现异常的时候调用
    @Override
    public void exceptionCaught(ChannelHandlerContext context, Throwable throwable)throws Exception{
        LOGGER.info("exceptionCaught,抛出异常,异常信息{},异常信息channel:{},channelId:{}",throwable.getLocalizedMessage(),context.channel(),context.channel().id());
        handlerRemoved(context);
        context.close();
    }


    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        //处理客户端向服务端发起的http握手请求
        if (o instanceof FullHttpRequest){
            LOGGER.info("http连接请求");
            handHttpRequest(channelHandlerContext,(FullHttpRequest) o);
        }else if (o instanceof WebSocketFrame){//处理websocket链接业务
            LOGGER.info("websocket信息请求");
            handWebSocketFrame(channelHandlerContext,(WebSocketFrame) o);
        }
    }

    /**
     * 处理客户端与服务端之间的websocket业务
     * @param context
     * @param webSocketFrame
     */
    private void handWebSocketFrame(ChannelHandlerContext context,WebSocketFrame webSocketFrame){
        if (webSocketFrame instanceof CloseWebSocketFrame){//判断是否是关闭websocket的指令
            webSocketServerHandshaker.close(context.channel(),(CloseWebSocketFrame) webSocketFrame.retain());
        }
        if (webSocketFrame instanceof PingWebSocketFrame){//判断是否是ping消息
            context.channel().write(new PongWebSocketFrame(webSocketFrame.content().retain()));
            return;
        }
        if (!(webSocketFrame instanceof TextWebSocketFrame)){//判断是否是二进制消息
            System.out.println("不支持二进制消息");
            throw new RuntimeException(this.getClass().getName());
        }
        //获取客户端向服务端发送的消息
        String text = ((TextWebSocketFrame) webSocketFrame ).text();
        LOGGER.info("服务端收到客户端的消息:" + text);
        ChatRecord chatRecord = exchangeChatMessage(context.channel(),text);
        //接收信息的userCode
        String toCode = chatRecord.getToCode();
        //判断发送的code是否是群聊code
        List<String> listCode = nettyService.queryGroupChatUsers(toCode);
        if(CollectionUtils.isNotEmpty(listCode)){
            //群聊 给群里的每个人都发
            listCode.forEach(v->{
                //服务端向好友客户端发送消息
                if(channelUserMap.containsKey(v) && !v.equals(chatRecord.getFromCode())){
                    channelUserMap.get(v).writeAndFlush(new TextWebSocketFrame(JsonUtil.getJson(chatRecord)));
                }
            });

        }else{
            //单聊
            //服务端向好友客户端发送消息
            if(channelUserMap.containsKey(toCode)){
                channelUserMap.get(toCode).writeAndFlush(new TextWebSocketFrame(JsonUtil.getJson(chatRecord)));
            }
        }
    }

    /**
     * 发送的信息转换
     * @Title: exchangeChatMessage
     * @author: dy.yin 2021/4/22 13:02
     * @param: [channel, text]
     * @return: java.util.Map<java.lang.String,java.lang.Object>
     * @throws
     */
    private ChatRecord exchangeChatMessage(Channel channel, String text) {

        JSONObject chatRecordJson = JSONObject.parseObject(text);
        ChatRecord chatRecord = JSON.toJavaObject(chatRecordJson,ChatRecord.class);
        chatRecord.setMessageTime(new Timestamp(System.currentTimeMillis()));

        nettyService = SpringUtils.getBean(NettyService.class);
        nettyService.insertChatRecord(chatRecord);

        return chatRecord;
    }

    /**
     * 处理客户端向服务端发起http握手请求业务
     * @param context
     * @param fullHttpRequest
     */
    private void handHttpRequest(ChannelHandlerContext context,FullHttpRequest fullHttpRequest){
        LOGGER.info("请求连接的channel{},id为{}",context.channel(),context.channel().id());
        //判断是否http握手请求
        if (!fullHttpRequest.getDecoderResult().isSuccess()
                ||!("websocket".equals(fullHttpRequest.headers().get("Upgrade")))){
            sendHttpResponse(context,fullHttpRequest, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }
        nettyService = SpringUtils.getBean(NettyService.class);
        String webSocketUrl = nettyService.getWebSocketUrl();
        WebSocketServerHandshakerFactory webSocketServerHandshakerFactory = new WebSocketServerHandshakerFactory(webSocketUrl,null,false);
        webSocketServerHandshaker = webSocketServerHandshakerFactory.newHandshaker(fullHttpRequest);
        if (webSocketServerHandshaker == null){
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(context.channel());
        }else{
            webSocketServerHandshaker.handshake(context.channel(),fullHttpRequest);
        }
        //把token解析成用户Code
        Channel channel = context.channel();
        String uri = fullHttpRequest.getUri();
        String userCode = uri.substring(uri.lastIndexOf("?")+1,uri.length());
        channelUserMap.put(userCode,channel);

        sendFriendMsgLoginOrOut(userCode,"notice","上线了");

    }

    /**
     * 服务端想客户端发送响应消息
     * @param context
     * @param fullHttpRequest
     * @param defaultFullHttpResponse
     */
    private void sendHttpResponse(ChannelHandlerContext context, FullHttpRequest fullHttpRequest, DefaultFullHttpResponse defaultFullHttpResponse){
        if (defaultFullHttpResponse.getStatus().code() != 200){
            ByteBuf buf = Unpooled.copiedBuffer(defaultFullHttpResponse.getStatus().toString(), CharsetUtil.UTF_8);
            defaultFullHttpResponse.content().writeBytes(buf);
            buf.release();
        }
        //服务端向客户端发送数据
        ChannelFuture future = context.channel().writeAndFlush(defaultFullHttpResponse);
        if (defaultFullHttpResponse.getStatus().code() !=200){
            future.addListener(ChannelFutureListener.CLOSE);
        }

    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
SpringUtils
package com.ydy.netty.util;

import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;


@Component
public class SpringUtils implements BeanFactoryPostProcessor {

    /** Spring应用上下文环境 */
    private static ConfigurableListableBeanFactory beanFactory;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
    {
        SpringUtils.beanFactory = beanFactory;
    }

    /**
     * 获取对象
     *
     * @param name
     * @return Object 一个以所给名字注册的bean的实例
     * @throws BeansException
     *
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException
    {
        return (T) beanFactory.getBean(name);
    }

    /**
     * 获取类型为requiredType的对象
     *
     * @param clz
     * @return
     * @throws BeansException
     *
     */
    public static <T> T getBean(Class<T> clz) throws BeansException
    {
        T result = (T) beanFactory.getBean(clz);
        return result;
    }

    /**
     * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
     *
     * @param name
     * @return boolean
     */
    public static boolean containsBean(String name)
    {
        return beanFactory.containsBean(name);
    }

    /**
     * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
     *
     * @param name
     * @return boolean
     * @throws NoSuchBeanDefinitionException
     *
     */
    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
    {
        return beanFactory.isSingleton(name);
    }

    /**
     * @param name
     * @return Class 注册对象的类型
     * @throws NoSuchBeanDefinitionException
     *
     */
    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException
    {
        return beanFactory.getType(name);
    }

    /**
     * 如果给定的bean名字在bean定义中有别名,则返回这些别名
     *
     * @param name
     * @return
     * @throws NoSuchBeanDefinitionException
     *
     */
    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
    {
        return beanFactory.getAliases(name);
    }

    /**
     * 获取aop代理对象
     *
     * @param invoker
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T getAopProxy(T invoker)
    {
        return (T) AopContext.currentProxy();
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111

文中用的redis主要是获取配置的请求路径,测试可以写死,不用配置:
在这里插入图片描述

ChatRecord 聊天实例对象
package com.ydy.common.model;

import java.sql.Timestamp;

public class ChatRecord {
    private Integer id;
    private String fromCode;
    private String fromName;
    private String mappingCode;
    private String toCode;
    private String fromHeadImage;
    private String message;
    private Timestamp messageTime;
    private String showTime;

    public ChatRecord() {
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getFromCode() {
        return fromCode;
    }

    public void setFromCode(String fromCode) {
        this.fromCode = fromCode;
    }

    public String getToCode() {
        return toCode;
    }

    public void setToCode(String toCode) {
        this.toCode = toCode;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Timestamp getMessageTime() {
        return messageTime;
    }

    public void setMessageTime(Timestamp messageTime) {
        this.messageTime = messageTime;
    }

    public String getFromHeadImage() {
        return fromHeadImage;
    }

    public void setFromHeadImage(String fromHeadImage) {
        this.fromHeadImage = fromHeadImage;
    }

    public String getFromName() {
        return fromName;
    }

    public void setFromName(String fromName) {
        this.fromName = fromName;
    }

    public String getMappingCode() {
        return mappingCode;
    }

    public void setMappingCode(String mappingCode) {
        this.mappingCode = mappingCode;
    }


    public String getShowTime() {
        return showTime;
    }

    public void setShowTime(String showTime) {
        this.showTime = showTime;
    }

    @Override
    public String toString() {
        return "ChatRecord{" +
                "id=" + id +
                ", fromCode='" + fromCode + '\'' +
                ", fromName='" + fromName + '\'' +
                ", mappingCode='" + mappingCode + '\'' +
                ", toCode='" + toCode + '\'' +
                ", fromHeadImage='" + fromHeadImage + '\'' +
                ", message='" + message + '\'' +
                ", messageTime=" + messageTime +
                '}';
    }
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
JsonUtil
package com.ydy.common.util;

import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;

import java.io.IOException;
import java.util.Map;

public class JsonUtil {

    private final static ObjectMapper objectMapper = new ObjectMapper();

    private static String objectToJson(Object object) {
        ObjectMapper om = new ObjectMapper();
        String json = "";
        try {
            try {
                json = om.writeValueAsString(object);
            } catch (JsonGenerationException e) {
                e.printStackTrace();
            } catch (JsonMappingException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return json;
    }

    public static String getJson(Object obj) {
        return objectToJson(obj);
    }

    public static <T> T jsonToObject(String json, TypeReference<T> typeReference) {
        ObjectMapper mapper = new ObjectMapper();
        try {
            return mapper.readValue(json, typeReference);
        } catch (JsonParseException e) {
        } catch (JsonMappingException e) {
        } catch (IOException e) {
        }
        return null;
    }
}



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

前台

前端效果图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

<template>
    <div class="chat_body">
        <el-container >
            <!--左边-->
            <div class="left-aside">
                <!--头像-->
                <el-row slot="reference">
                    <div class="head-portrait"><img :src="headPortrait" /></div>
                </el-row>
            </div>
            <!--中间-->
            <el-aside width="250px">
                <!--聊天列表头部-->
                <div class="chat-record">
                    <div class="chat-record-input">
                        <el-input v-model="inputQuery" size="mini" prefix-icon="el-icon-search" placeholder="搜索" clearable></el-input>
                    </div>
                    <div class="chat-record-add">
                        <a href="#" style="line-height: 25px" title="发起群聊" @click="addGroupChatDialog">✚</a>
                    </div>
                </div>
                <!--聊天记录列表-->
                <div v-for="(item,index) in friendChatList" :key="index" style="margin-top :20px;">
                    <el-row @click.native="clickChatRecord(item)">
                        <el-col :span="6">
<!--                                                            <el-badge :value="2" class="item">-->
                            <div class="chat-portrait"><img :src="item.friendPortrait" /></div>
<!--                                                            </el-badge>-->
                        </el-col>

                        <el-col :span="18">
                            <el-row>
                                <el-col :span="15" class="chat-nickName">{{item.friendName}}</el-col>
                                <el-col :span="9">{{item.showTime}}</el-col>
                            </el-row>
                            <div class="chat-newMsg">{{item.message}}</div>
                        </el-col>
                    </el-row>
                </div>
            </el-aside>
            <!--右边-->
            <el-main>
                <el-row class="main-title">
                    <el-col :span="21">{{mainTitle}}</el-col>
                    <el-col :span="3" @click.native="lookDetail">
                        <a href="#" class="el-icon-more" aria-hidden="true" title="聊天信息"></a>
                    </el-col>
                </el-row>
                <div class="main-msg">
                    <div v-for="(item,index) in chatMsgRecord" :key="index">
                        <el-row v-if="item.fromCode != fromCode">
                            <el-row><span class="message-time">{{item.showTime}}</span></el-row>
                            <el-col :span="3">
                                <div class="msg-portrait-left"><img :src="item.fromHeadImage" /></div>
                            </el-col>
                            <el-col :span="21">
                                <div class="chat-message-left-nickName">{{item.fromName}}</div>
                                <div class="chat-msg-left">
                                    <span class="msg-detail">{{item.message}}</span>
                                </div>
                            </el-col>
                        </el-row>

                        <el-row v-if="item.fromCode == fromCode">
                            <el-row>
                                <span class="message-time">{{item.showTime}}</span>
                            </el-row>
                            <el-col :span="21">
                                <div class="chat-message-right-nickName">{{item.fromName}}</div>
                                <div class="chat-msg-right">
                                    <span class="msg-detail">{{item.message}}</span>
                                </div>
                            </el-col>
                            <el-col :span="3">
                                <div class="msg-portrait-right"><img :src="headPortrait" /></div>
                            </el-col>
                        </el-row>
                    </div>
                </div>

                <div class ="main-chat-input">
                    <!--工具栏-->
                    <el-popover placement="top-start" width="400" trigger="click" class="emoBox">
                        <div class="emotionList">
                            <a href="javascript:void(0);" @click="getEmo(index)" v-for="(item,index) in faceList" :key="index" class="emotionItem">{{item}}</a>
                        </div>
                        <el-button class="emotionSelect" slot="reference">
                            <i class="el-icon-picture-outline-round" aria-hidden="true" title="表情"></i>
                        </el-button>
                    </el-popover>

                    <el-button class="emotionSelect">
                        <i class="el-icon-folder-opened" aria-hidden="true" title="发送文件"></i>
                    </el-button>

                    <el-button class="emotionSelect">
                        <i class="el-icon-chat-dot-round" aria-hidden="true" title="聊天记录"></i>
                    </el-button>

                    <!--输入框-->
                    <el-input type="textarea" :rows="7" v-model="textarea"  resize="none" border="none" @keyup.enter.native="sendMsg" id="textarea" >

                    </el-input>
                </div>
                <el-button size="mini" style="float:right" @click="sendMsg">发送(S)</el-button>
            </el-main>
        </el-container>

        <!--创建群聊弹框-->
        <el-dialog :visible.sync="groupChatDialog" :close-on-click-modal="false" :append-to-body="true">
            <div style="height: 300px;">
                <div class="add-chatGroup-left" >
                    <el-input v-model="inputQuery" size="mini" prefix-icon="el-icon-search" placeholder="搜索" clearable></el-input>
                    <div v-for="(item,index) in friendList" :key="index" style="margin-top :20px;">
                        <el-row>
                            <el-col :span="6">
                                <div class="chat-portrait"><img :src="item.friendPortrait" /></div>
                            </el-col>

                            <el-col :span="15">
                                <div style="height: 35px;line-height: 35px;font-size: 18px;font-weight: bold">{{item.friendName}}</div>
                            </el-col>
                            <el-col :span="2">
                                <div>
                                    <el-checkbox @change="groupChatCheckChange($event,item)"></el-checkbox>
                                </div>
                            </el-col>
                        </el-row>
                    </div>
                </div>
                <div class="add-chatGroup-right">
                    <div>
                        <span style="margin-right: 100px;">{{checkGroupChatTitle}}</span>
                        <el-button size="mini" type="success" @click="addGroupChat">确认</el-button>
                        <el-button size="mini" type="info">取消</el-button>
                    </div>
                    <div v-for="tag in checkChatUsers" :key="tag.friendCode" style="margin-top :20px;">
                        <el-row closable
                                :disable-transitions="false"
                                @close="handleCloseTag(tag.friendCode)">
                            <el-col :span="6">
                                <div class="chat-portrait"><img :src="tag.friendPortrait" /></div>
                            </el-col>

                            <el-col :span="10">
                                <div style="height: 35px;line-height: 35px;font-size: 18px;font-weight: bold">{{tag.friendName}}</div>
                            </el-col>
                        </el-row>
                    </div>
                </div>
            </div>
        </el-dialog>

        <!--群聊和个人详细信息-->
        <el-drawer
                title="我是标题"
                :visible.sync="drawer"
                :with-header="false">
            <span>我来啦!</span>
        </el-drawer>
    </div>
</template>
<script>
    const appData=require("../../assets/json/emoji.json")//引入存放emoji表情的json文件
    export default {
        components:{

        },
        data() {
            return {
                dialogVisible :true,
                //搜索框输入
                inputQuery:'',
                //登录客户头像
                headPortrait:sessionStorage.getItem("headPortrait"),
                //聊天记录表
                friendChatList:[],
                //聊天
                mainTitle:'',
                //信息
                textarea:'',
                //接收人UserCode
                toCode :'',
                //发送人userCode
                fromCode:sessionStorage.getItem("userCode"),
                fromName:sessionStorage.getItem("nickName"),
                //通信url
                webSocketUrl:sessionStorage.getItem("webSocketUrl"),
                //当前聊天对象的聊天记录
                chatMsgRecord :[],
                //所有的聊天记录
                chatRecord:'',
                //当前聊天的code组装key
                currentChatKey:'',
                /********/
                //群聊弹框
                groupChatDialog:false,
                //选择的好友
                checkChatUsers:[],
                //勾选好友抬头
                checkGroupChatTitle:'请勾选需要添加的联系人',
                //可添加为群聊的好友
                friendList:[],
                /*表情包*/
                faceList:[],//表情包数据
                content:'',
                /*群聊详细信息*/
                drawer:false,
            }
        },
        methods:{
            //查看聊天详细信息
            lookDetail(){
                this.drawer = true;
            },

            //获取表情包,放入输入框
            getEmo(index){
                let textArea = document.getElementById('textarea');
                //将选中的表情插入到输入文本的光标之后
                function changeSelectedText(obj, str) {
                    if (window.getSelection) {
                        // 非IE浏览器
                        textArea.setRangeText(str);
                        // 在未选中文本的情况下,重新设置光标位置
                        textArea.selectionStart += str.length;
                        textArea.focus()
                    } else if (document.selection) {
                        // IE浏览器
                        obj.focus();
                        var sel = document.selection.createRange();
                        sel.text = str;
                    }
                }
                changeSelectedText(textArea,this.faceList[index]);
                this.content=textArea.value;// 要同步data中的数据
                return;
            },

            //添加群聊好友
            addGroupChat(){
                if(this.checkChatUsers.length < 2){
                    this.$msg.success("请选择2个及以上好友!");
                    return;
                }
                console.log(this.checkChatUsers);
                this.groupChatDialog = false;
                //提交
                this.$api.addGroupChat(this.checkChatUsers).then(res => {

                }).catch(err => {
                    this.$commsgbox.alert(err);
                });

            },
            //勾选好友
            groupChatCheckChange(checked,item){
                if(checked  == true){
                    console.log("勾选:"+item.friendCode);
                    this.checkChatUsers.push(item);
                }else{
                    console.log("取消:"+item.friendCode);
                    for(var index in this.checkChatUsers){
                        if(this.checkChatUsers[index].friendCode === item.friendCode){
                            this.checkChatUsers.splice(index, 1);
                        }
                    }
                }
                if(this.checkChatUsers.length > 0 ){
                    this.checkGroupChatTitle  = "已选择了"+this.checkChatUsers.length+"个联系人";
                }else{
                    this.checkGroupChatTitle = '请勾选需要添加的联系人';
                }

            },
            //群聊弹框
            addGroupChatDialog(){
                let req = {"userCode":this.fromCode};
                this.$api.getFriendList(req).then(res => {
                    this.friendList = res.data.data;
                    this.groupChatDialog = true;
                    this.checkChatUsers = [];
                }).catch(err => {
                    this.$commsgbox.alert(err);
                });
            },

            /***********************/
            //查询聊天列表
            getChatFriendsList(){
                let req = {"userCode":this.fromCode};
                this.$api.getChatFriendsList(req).then(res => {
                    this.friendChatList = res.data.data;
                }).catch(err => {
                    this.$commsgbox.alert(err);
                });
            },
            //点击好友列表,开始聊天
            clickChatRecord(item){
                let that = this;
                //聊天信息展示的抬头显示好友昵称
                that.mainTitle = item.friendName;
                //好友的code为收信人code
                that.toCode = item.friendCode;

                //找到mappingCode
                let mappingCode = item.mappingCode;

                //给当前聊天纤细对象赋值
                if(that.chatRecord != undefined && mappingCode in that.chatRecord){
                    that.chatMsgRecord = that.chatRecord[mappingCode];
                }else{
                    that.chatMsgRecord = [];
                }
                //更新当前聊天对象祝贺key
                that.currentChatKey = mappingCode;
                //信息下拉滚条置到底部
                that.setScrollToEnd();
                //好友列表重新排序 TODO


            },
            //发送信息
            sendMsg () {
                let that = this;
                //信息输入框内容
                let msg = that.textarea;
                //发送人userCode
                let fromCode = that.fromCode;
                //接收人userCode
                let toCode = that.toCode;
                //当前聊天组合的mappingCode
                let mappingCode = that.currentChatKey;
                //判断信息不为空
                if(msg.trim()===''){
                    that.$commsgbox.alert("不能发送空白信息!");
                    return;
                }
                //判断收信人是否选择
                if(toCode===''){
                    that.$commsgbox.alert("请选择要发送信息的好友!");
                    return;
                }
                //组装发送的信息体
                let req = {
                    "fromCode":fromCode,
                    "fromName":this.fromName,
                    "mappingCode":mappingCode,
                    "toCode":toCode,
                    "message":msg,
                    "fromHeadImage":this.headPortrait,
                    "showTime":this.$comfunc.getHHmm()
                };
                //把组装的发送的信息添加到当前聊天对象的聊天信息集合中
                that.chatMsgRecord.push(req);
                //把对象转为字符串传输
                let agentData  = JSON.stringify(req);
                //websocket发送信息
                that.webSocketSendMessage(agentData);
                //更新好友列表的最新信息
                that.friendChatList.forEach(function(val,index){
                    let friendCode = val.friendCode;
                    if(toCode === friendCode){
                        //更新信息和时间
                        val.message = msg;
                        val.showTime = that.$comfunc.getHHmm();
                        return;
                    }
                });
                //信息输入框置空
                that.textarea = "";
                //聊天详细信息一直位于底部
                that.setScrollToEnd();
            },

            //websocket发送信息
            webSocketSendMessage(agentData){
                let that = this;
                //若是ws开启状态
                if (that.websock.readyState === that.websock.OPEN) {
                    that.websocketSend(agentData);
                }
                // 若是 正在开启状态,则等待300毫秒
                else if (that.websock.readyState === that.websock.CONNECTING) {
                    setTimeout(function () {
                        that.websocketSend(agentData);
                    }, 300);
                }
                // 若未开启 ,则等待500毫秒
                else {
                    that.initWebSocket();
                    setTimeout(function () {
                        that.websocketSend(agentData)
                    }, 500);
                }
            },
            //数据发送
            websocketSend(agentData){
                let that = this;
                console.log("发送的信息:"+agentData);
                that.websock.send(agentData);
            },
            //关闭
            websocketClose(e){
                console.log("connection closed (" + e.code + ")");
            },
            //设置div的下拉条始终在底部
            setScrollToEnd(){
                let that = this;
                that.$nextTick(()=> {
                    let box = that.$el.querySelector(".main-msg")
                    box.scrollTop = box.scrollHeight
                });
            },
            //监听服务端返回信息数据接收
            websocketOnmessage(e){
                let that = this;
                let reData = e.data;
                console.log("接收到的信息为:"+ reData);
                //好友上线下线信息
                if(that.showFriendNoticeMessage(reData)){return;}
                //json转换
                reData = JSON.parse(reData);
                /**
                 * 对数据做处理,处理逻辑为:
                 * 1、如果收到的信息为当前聊天对象的信息,直接把值付给当前聊天信息对象
                 * 2、如果收到的信息不是当前聊天对象的,找到该对象的聊天信息,然后把信息加进去
                 * 3、更新聊天列表的时间和信息
                 * @type {string}
                 */
                that.handleReceiveMessage(reData);
                //聊天详细信息一直位于底部
                that.setScrollToEnd();
            },
            //好友上下线消息提醒
            showFriendNoticeMessage(reData){
                let that = this;
                if(reData.indexOf("{") == -1){
                    console.log("message提示:"+reData);
                    that.$msg.success(reData);
                    return true;
                }
            },
            //处理接收到的信息
            handleReceiveMessage(reData){
                let that = this;
                //聊天组code
                let mappingCode = reData.mappingCode;
                //1、判断如果发送的信息为当前聊天对象,直接拼接信息
                if(that.currentChatKey === mappingCode){
                    console.log("聊天对象为当前对象");
                    that.chatMsgRecord.push(reData);
                }else{
                    //2、如果不是当前聊天的对象,拼接到对应list,然后重新放入map中
                    console.log("聊天对象为好友列表对象");
                    if(mappingCode in that.chatRecord){
                        let tmpChatMsgRecord = that.chatRecord[mappingCode];
                        tmpChatMsgRecord.push(reData);
                        that.chatRecord[mappingCode] = tmpChatMsgRecord;
                    }else{
                        let tmpChatMsgRecord =[];
                        tmpChatMsgRecord.push(reData);
                        that.chatRecord[mappingCode] = tmpChatMsgRecord;
                    }
                }
                //3、更新聊天列表的时间和信息
                console.log("更新好友列表信息");
                that.friendChatList.forEach(function(val,index){
                    let code = val.mappingCode;
                    //找到聊天列表中与当前接收到的信息为同一人的对象
                    if(code == mappingCode){
                        //更新信息和时间
                        val.message = reData.message;
                        val.showTime = that.$comfunc.getHHmm();
                    }
                });
            },
            //查询聊天界面信息
            getChatInfo(){
                let that = this;
                //连接websocket的userCode
                let req = {"userCode":that.fromCode};
                that.$api.getChatInfo(req).then(res => {
                    //好友聊天列表
                    that.friendChatList = res.data.data.friendChatList;
                    //所有的聊天记录
                    that.chatRecord = res.data.data.chatRecord;
                }).catch(err => {
                    that.$commsgbox.alert(err);
                });
            },
            //初始化websocket
            initWebSocket(){
                //ws地址
                const wsUri = this.webSocketUrl +"?"+this.fromCode;
                this.websock = new WebSocket(wsUri);
                //绑定message响应
                this.websock.onmessage = this.websocketOnmessage;
                //绑定关闭响应
                this.websock.onclose = this.websocketClose;
            },
            //初始化加载表情包列表
            initEmoji(){
                for (let i in appData){//读取json文件保存数据给数组
                    this.faceList.push(appData[i].char);
                }
            },
        },
        created() {
            //初始化websocket组件和方法
            this.initWebSocket();
            //初始化查询个人信息 好友列表 和所有的聊天信息
            this.getChatInfo();
        },
        mounted() {
            this.initEmoji();
        },
        filters: {
            time:function(time){
                return this.$comfunc.timeFormat(time);
            },
        }
    }
</script>
<style scoped>
    .message-time{
        background-color: #DADADA;
        padding:1px 0px;
    }
    .add-chatGroup-left{
        width:350px;
        float: left;
        height: 300px;
        overflow-y: auto;
        /*右边框*/
        border-width: 0 1px 0 0;
        border-style: solid;
        border-color: black;
    }
    .add-chatGroup-right{
        width: 400px;
        float: left;
        height:300px;
        overflow-y: auto;
    }
    .chat-message-left-nickName{
        text-align: left;
        margin: 0px 10px;
    }
    .chat-message-right-nickName{
        text-align: right;
        margin: 0px 10px;
    }
    .msg-detail{
        padding: 0px 15px;
    }
    .chat-msg-right{
        text-align: center;
        min-height:35px;
        height:max-content;
        line-height: 35px;
        margin: 5px 10px;
        background-color: #9EEA6A;
        border-radius:5px;
        width:max-content;
        float:right;
        max-width:250px;
        word-wrap: break-word;
    }
    .msg-portrait-right{
        width:35px;
        height: 35px;
        margin-right:20px;
        margin-top:10px;
    }
    .msg-portrait-right img{
        display: block;
        width: 35px;
        height: 35px;
    }
    .chat-msg-left{
        text-align: center;
        min-height:35px;
        height:max-content;
        line-height: 35px;
        margin: 5px 10px;
        background-color: #FFFFFF;
        border-radius:5px;
        width:max-content;
        max-width:250px;
        word-wrap: break-word;
    }
    .msg-portrait-left{
        width:35px;
        height: 35px;
        margin: 10px 15px;
    }
    .msg-portrait-left img{
        display: block;
        width: 35px;
        height: 35px;
    }
    .main-chat-input{
        height: 155px;
        background-color: #ffffff;
    }
    .main-msg{
        height:250px;
        overflow-y: auto;
    }

    .chat-newMsg{
        text-align: left;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
    }
    .chat-nickName{
        text-align: left;
        font-size: 16px;
        font-weight: bold;
        width: 100px;
        float: left;
    }
    .chat-msgTime{
        text-align: right;
        font-size: 12px;
        width: 20px;
    }
    .chat-record-add{
        float: right;
        width: 25px;
        height: 25px;
        margin-right: 10px;
        margin-top:20px;
        background-color: #DCD9D8;
    }
    .head-portrait{
        margin:20px 12px;
    }
    .head-portrait img {
        display: block;
        width: 35px;
        height: 35px;
    }
    a{
        text-decoration:none;
    }
    a:hover{color: black}
    input {
        background-color:transparent;
    }
    .chat-record-input{
        width: 175px;
        margin-left: 10px;
        line-height: 65px;
        float: left;
    }

    .left-aside{
        width: 60px;
        background-color: #28292C;
    }
    .chat_body{
        height: 500px;
        border: #99a9bf solid 1px;
    }
    .el-container{
        height: 500px;
        margin-bottom: 40px;
    }
    .el-aside {
        background-color: #EEEAE8;
    }
    .el-main {
        background-color: #F5F5F5;
        padding: 0px;
    }

    .chat-record{
        height: 65px;
        width:230px;
    }
    .main-title{
        height: 65px;
        border-bottom: #99a9bf solid 1px;
        font-size: 16px;
        font-weight: bold;
        text-align: left;
        line-height: 65px;
        padding-left: 25px;
    }

    /*表情*/
    .emotionSelect{
        border: none;
        padding:5px 10px;
        float:left;
    }
    .emotionList{
        display: flex;
        flex-wrap: wrap;
        padding:5px;
    }
    .emotionItem{
        width:10%;
        font-size:20px;
        text-align:center;
    }
    /*包含以下四种的链接*/
    .emotionItem {
        text-decoration: none;
    }
    /*正常的未被访问过的链接*/
    .emotionItem:link {
        text-decoration: none;
    }
    /*已经访问过的链接*/
    .emotionItem:visited {
        text-decoration: none;
    }
    /*鼠标划过(停留)的链接*/
    .emotionItem:hover {
        text-decoration: none;
    }
    /* 正在点击的链接*/
    .emotionItem:active {
        text-decoration: none;
    }
</style>

<style lang="scss">
    /* el-popover是和app同级的,所以scoped的局部属性设置无效 */
    /* 需要设置全局style */
    .el-popover{
        height:200px;
        width:300px;
        overflow-y:auto;
    }
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
  • 476
  • 477
  • 478
  • 479
  • 480
  • 481
  • 482
  • 483
  • 484
  • 485
  • 486
  • 487
  • 488
  • 489
  • 490
  • 491
  • 492
  • 493
  • 494
  • 495
  • 496
  • 497
  • 498
  • 499
  • 500
  • 501
  • 502
  • 503
  • 504
  • 505
  • 506
  • 507
  • 508
  • 509
  • 510
  • 511
  • 512
  • 513
  • 514
  • 515
  • 516
  • 517
  • 518
  • 519
  • 520
  • 521
  • 522
  • 523
  • 524
  • 525
  • 526
  • 527
  • 528
  • 529
  • 530
  • 531
  • 532
  • 533
  • 534
  • 535
  • 536
  • 537
  • 538
  • 539
  • 540
  • 541
  • 542
  • 543
  • 544
  • 545
  • 546
  • 547
  • 548
  • 549
  • 550
  • 551
  • 552
  • 553
  • 554
  • 555
  • 556
  • 557
  • 558
  • 559
  • 560
  • 561
  • 562
  • 563
  • 564
  • 565
  • 566
  • 567
  • 568
  • 569
  • 570
  • 571
  • 572
  • 573
  • 574
  • 575
  • 576
  • 577
  • 578
  • 579
  • 580
  • 581
  • 582
  • 583
  • 584
  • 585
  • 586
  • 587
  • 588
  • 589
  • 590
  • 591
  • 592
  • 593
  • 594
  • 595
  • 596
  • 597
  • 598
  • 599
  • 600
  • 601
  • 602
  • 603
  • 604
  • 605
  • 606
  • 607
  • 608
  • 609
  • 610
  • 611
  • 612
  • 613
  • 614
  • 615
  • 616
  • 617
  • 618
  • 619
  • 620
  • 621
  • 622
  • 623
  • 624
  • 625
  • 626
  • 627
  • 628
  • 629
  • 630
  • 631
  • 632
  • 633
  • 634
  • 635
  • 636
  • 637
  • 638
  • 639
  • 640
  • 641
  • 642
  • 643
  • 644
  • 645
  • 646
  • 647
  • 648
  • 649
  • 650
  • 651
  • 652
  • 653
  • 654
  • 655
  • 656
  • 657
  • 658
  • 659
  • 660
  • 661
  • 662
  • 663
  • 664
  • 665
  • 666
  • 667
  • 668
  • 669
  • 670
  • 671
  • 672
  • 673
  • 674
  • 675
  • 676
  • 677
  • 678
  • 679
  • 680
  • 681
  • 682
  • 683
  • 684
  • 685
  • 686
  • 687
  • 688
  • 689
  • 690
  • 691
  • 692
  • 693
  • 694
  • 695
  • 696
  • 697
  • 698
  • 699
  • 700
  • 701
  • 702
  • 703
  • 704
  • 705
  • 706
  • 707
  • 708
  • 709
  • 710
  • 711
  • 712
  • 713
  • 714
  • 715
  • 716
  • 717
  • 718
  • 719
  • 720
  • 721
  • 722
  • 723
  • 724
  • 725
  • 726
  • 727
  • 728
  • 729
  • 730
  • 731
  • 732
  • 733
  • 734
  • 735
  • 736
  • 737
  • 738
  • 739
  • 740
<template>
    <div>
        <div class="userDetail">
            <!--头像-->
            <div class="headPortraitImage" title = "个人头像">
                <span class="span-title">个人头像</span>
                <el-upload action="#" list-type="picture-card" :auto-upload="false"
                           :file-list="headPortraitList"
                           accept=".png,.jpg,.gif,.jpeg">
                    <i slot="default" class="el-icon-plus"></i>
                    <div slot="file" slot-scope="{file}">
                        <span v-if="file.userCode"><img class="el-upload-list__item-thumbnail" :src="file.headPortrait" alt="file.fileName"></span>
                        <span v-if="!file.userCode"><img class="el-upload-list__item-thumbnail" :src="file.url" alt=""></span>

                        <span class="el-upload-list__item-actions">

                    <span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)">
                      <i class="el-icon-zoom-in"></i>
                    </span>
                    <span v-if="!file.userCode" class="el-upload-list__item-delete" @click="handleUpload(file)">
                      <i class="el-icon-upload"></i>
                    </span>
                  </span>
                    </div>
                </el-upload>
                <el-dialog :visible.sync="dialogVisible">
                    <img width="100%" :src="headPortrait" alt="">
                </el-dialog>
            </div>
        </div>
        <div class="can-add-friends" title="可添加好友列表">
            <span class="span-title">可添加好友列表</span>
            <div v-for="(item,index) in canAddFriendList" :key="index" style="margin-top :20px;">
                <el-row>
                    <el-col :span="3">
                        <div class="chat-portrait"><img :src="item.friendPortrait" /></div>
                    </el-col>

                    <el-col :span="10">
                        <div>{{item.friendCode}}</div>
                    </el-col>

                    <el-col :span="3">
                        <div class="chat-nickName">{{item.friendName}}</div>
                    </el-col>

                    <el-col :span="3">
                        <el-button size="mini" @click="addFriend(item)">添加</el-button>
                    </el-col>
                </el-row>
            </div>
        </div>
        <div class="friends-add-request" title="好友添加申请列表">
            <span class="span-title">好友添加申请列表</span>
            <div v-for="(item,index) in friendAddRequestList" :key="index" style="margin-top :20px;">
                <el-row>
                    <el-col :span="3">
                        <div class="chat-portrait"><img :src="item.friendPortrait" /></div>
                    </el-col>

                    <el-col :span="10">
                        <div>{{item.friendCode}}</div>
                    </el-col>

                    <el-col :span="3">
                        <div class="chat-nickName">{{item.friendName}}</div>
                    </el-col>

                    <el-col :span="3">
                        <el-button size="mini" @click="agreeAddFriend(item)">同意</el-button>
                    </el-col>
                </el-row>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        components:{

        },
        data() {
            return {
                //能够添加为好友的列表
                canAddFriendList:[],
                //好友添加申请
                friendAddRequestList:[],
                //发送人userCode
                fromCode:sessionStorage.getItem("userCode"),
                //头像地址
                headPortrait:'',
                headPortraitList:[],
                dialogVisible:false,
                labelPosition:'left',
            }
        },
        methods: {
            //查询好友添加申请
            queryAddFriendRequestList(){
                let req = {"userCode": this.fromCode};
                this.$api.queryAddFriendRequest(req).then(res => {
                    this.friendAddRequestList = res.data.data;
                }).catch(err => {
                    this.$commsgbox.alert(err);
                });
            },
            //同意好友添加
            agreeAddFriend(item){
                let req = Object.assign(item,{"userCode": this.fromCode});
                this.$api.agreeAddFriend(req).then(res => {
                    this.queryAddFriendRequestList();
                }).catch(err => {
                    this.$commsgbox.alert(err);
                });
            },
            //添加好友提交
            addFriend(item) {
                console.log("添加的好友信息" + item);
                let req = {"userCode": this.fromCode, "friendCode": item.friendCode};
                this.$api.addFriend(req).then(res => {
                    this.getCanAddFriendList();
                }).catch(err => {
                    this.$commsgbox.alert(err);
                });
            },
            //查询可添加的好友列表
            getCanAddFriendList() {
                console.log("添加好友");
                let req = {"userCode": this.fromCode};
                this.$api.getCanAddFriendList(req).then(res => {
                    this.canAddFriendList = res.data.data;
                }).catch(err => {
                    this.$commsgbox.alert(err);
                });
            },
            //查看头像
            handlePictureCardPreview(file) {
                console.log(file);
                if (!file.userCode) {
                    this.headPortrait = file.url;
                } else {
                    this.headPortrait = file.headPortrait;
                }
                this.dialogVisible = true;
            },
            handleUpload(file){
                let that = this;
                console.log(file);
                let formData = new FormData();
                formData.append('file',file.raw);
                formData.append("userCode", this.fromCode);
                formData.append("nickName","尹家村帅勇");
                that.$api.headPortraitImageUpload(formData).then(res => {
                    let user = res.data.data;
                    let headPortrait = res.data.data.headPortrait;
                    this.headPortrait = headPortrait;
                    sessionStorage.setItem("headPortrait",headPortrait);
                    that.headPortraitList = [];
                    that.headPortraitList.push(user);
                }).catch(err => {
                    that.$commsgbox.alert(err);
                });
            },
            //查询界面信息
            getChatUserInfo(){
                let req = {"userCode": this.fromCode};
                this.$api.getChatUserInfo(req).then(res => {
                    //个人详细信息
                    let user = res.data.data.user;
                    this.headPortraitList.push(user);
                    //可添加好友列表
                    this.canAddFriendList = res.data.data.canAddFriendList;
                    //待同意好友列表
                    this.friendAddRequestList = res.data.data.friendAddRequestList;
                }).catch(err => {
                    this.$commsgbox.alert(err);
                });
            }
        },
        created() {
            this.getChatUserInfo();
        },
        mounted() {

        },
    }
</script>
<style>
    .chat-portrait img{
        display: block;
        width: 40px;
        height: 40px;
    }

    .chat-portrait{
        width:40px;
        height: 40px;
        margin-left: 10px;
    }
    .chat-nickName{
        text-align: center;
        font-size: 16px;
        font-weight: bold;
    }


    .userDetail{
        height:100px;
    }
    .can-add-friends{
        height: 200px;
        /*background-color: #9EEA6A;*/
    }
    .friends-add-request{
        height: 200px;
        /*background-color: #d27468;*/
    }

    /**********************/
    .el-upload{
        width: 80px;
        height: 80px;
        line-height: 80px;
    }
    .el-upload el-upload--picture-card{
        height: 80px;
        height: 80px;
    }
    .el-upload-list--picture-card .el-upload-list__item{
        width: 80px;
        height: 80px;
        line-height: 80px;
    }
    .el-upload-list--picture-card .el-upload-list__item-thumbnail{
        width: 80px;
        height: 80px;
        line-height: 80px;
    }
    .avatar{
        width: 80px;
        height: 80px;
    }
    .headPortraitImage{
        width: 300px;
        padding-left: 200px;
    }
    .span-title{
        font-weight: bold;
        font-size: 16px;
    }
</style>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
<template>
    <div>
        <el-row :gutter="12">
            <el-col :span="12">
                <el-card shadow="hover" class="box-card">
                    <chat ref="chat"></chat>
                </el-card>
            </el-col>
            <el-col :span="12">
                <el-card shadow="hover" class="box-card">
                    <chat-info ref="chatInfo"></chat-info>
                </el-card>
            </el-col>
        </el-row>
    </div>
</template>
<script>
    import chat from './chat.vue';
    import chatInfo from './chatInfo.vue';
    export default {
        components:{
            chat,
            chatInfo,
        },
        data() {
            return {
            }
        },
        methods:{
            
        },
        created() {

        },
        mounted() {

        },
    }
</script>
<style scoped>
    .box-card{
        height: 550px;
    }
</style>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

gitee地址: gitee

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/404279
推荐阅读
相关标签
  

闽ICP备14008679号