java聊天功能

依赖

 <!--    socket      -->
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
        <version>2.7.3</version>
        </dependency>
  <!--gson-->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.9.0</version>
    </dependency>

配置类加入

@Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

java
基本无改动 复制粘贴用

package qiesiyv.ceshi.tool;

import com.google.gson.Gson;
import org.junit.platform.commons.util.StringUtils;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@ServerEndpoint("/liaotian/{userId}")
@Component
public class WebSocketServer {

    /**静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。*/
    private static int onlineCount = 0;
    /**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
    private static ConcurrentHashMap<String,Session> sessions = new ConcurrentHashMap<>();
    /**接收userId*/
    private String userId="";
    private String type ="";
    private String touserid ="";
    private String content ="";

    private static Gson gson = new Gson();
    /**
     * 连接建立成功调用的方法*/
    @OnOpen
    public void onOpen( Session session, @PathParam("userId") String userId) {
        sessions.put("sess"+userId,session);
        this.userId= userId;//获取发送人id
        System.out.println("用户"+userId+"加入WebSocketServer");
        if(sessions.containsKey("sess"+userId)){
            sessions.remove("sess"+userId);
            sessions.put("sess"+userId,session);
        }else{
            sessions.put("sess"+userId,session);
            //人数+1
            addOnlineCount();
        }

        try {
            Map<String,Object> map=new HashMap<>();
            map.put("mag","连接成功");
            sendMessage(userId,gson.toJson(map));
        } catch (IOException e) {
            System.out.println("对方网络异常!!!!!!");
        }
    }


    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        if(sessions.containsKey("sess"+userId)){
            sessions.remove("sess"+userId);
            //人数-1
            subOnlineCount();
        }
        System.out.println("用户"+userId+"退出,当前在线人数为:"+getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息*/
    @OnMessage
    public void onMessage(String message, Session session) {
        //解析发送的报文
        Map<String,String> xinxi=gson.fromJson(message,Map.class);
        touserid =xinxi.get("toUserId");
        content = xinxi.get("contentText");
        System.out.println("用户消息:"+userId+",报文:"+message);
        if(StringUtils.isNotBlank(message)){
            try {
                //执行发送操作
                fasong(touserid,content);

            }catch (Exception e){
                e.printStackTrace();
            }
        }

    }
    /**
     * @Author 翎墨袅
     * @Description //TODO 发送消息
     * @Param [error]
     * @return void
     **/
    public void fasong(String touid,String neirong) throws Exception{
        //传送给对应touid用户的websocket
        if(StringUtils.isNotBlank(touid)&&sessions.containsKey("sess"+touid)){
            sendMessage(touid,neirong);
            //这里可以保存数据库

        }else{
            sendMessage(userId,"请求的userId:"+touid+"不在该服务器上");
            //否则不在这个服务器上,进行mysql/redis保存
        }
    }

    /**
     *
     * @param error
     */
    @OnError
    public void onError(Throwable error) {
        System.out.println("用户错误:"+this.userId+",原因:"+error.getMessage());
        error.printStackTrace();
    }
    /**
     * 实现服务器主动推送
     */
    public static void sendMessage(String buid,String message) throws IOException {
        System.out.println("执行推送,被推送人id"+buid);
        Session session=sessions.get("sess"+buid);
        sessions.forEach((x,y)->{
            System.out.println(x);
            System.out.println(y);
        });
        System.out.println(session);
        if (session!=null)
            session.getBasicRemote().sendText(message);
    }

    //当前使用人数
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }
    //当前使用人数+1
    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }
    //当前使用人数-1
    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }

    /**
     * @Description: 数据推送,参数1目标uid,参数2推动的信息
     * @Param:
     * @return:
     * @Author: 翎墨袅
     * @Date: 2022/10/11
     *///外部使用方法WebSocketServer.tuixinxi(对方id, 要传输的map数据);
    public static boolean tuixinxi(String buid,Map xinxi){
        try {
            sendMessage(buid,gson.toJson(xinxi));
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
        return true;
    }


}


div

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>websocket通讯</title>
</head>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
    var socket;
    function openSocket() {
        if(typeof(WebSocket) == "undefined") {
            console.log("您的浏览器不支持WebSocket");
        }else{
            console.log("您的浏览器支持WebSocket");
            //实现化WebSocket对象,指定要连接的服务器地址与端口  建立连接
            var socketUrl="ws://localhost:9091/liaotian/"+$("#userId").val();//如果是https则wss加域名
            console.log(socketUrl);
            if(socket!=null){
                socket.close();
                socket=null;
            }
            socket = new WebSocket(socketUrl);
            //打开事件
            socket.onopen = function(msg) {
                console.log("websocket已打开",JSON.stringify(msg));
                //socket.send("这是来自客户端的消息" + location.href + new Date());
            };
            //获得消息事件
            socket.onmessage = function(msg) {
                console.log(msg.data);
                //发现消息进入    开始处理前端触发逻辑
            };
            //关闭事件
            socket.onclose = function(close) {
                console.log("websocket已关闭",JSON.stringify(close));
            };
            //发生了错误事件
            socket.onerror = function(error) {
                console.log("websocket发生了错误",JSON.stringify(error));
            }
        }
    }
    function sendMessage() {
        if(typeof(WebSocket) == "undefined") {
            console.log("您的浏览器不支持WebSocket");
        }else {
            console.log("您的浏览器支持WebSocket");
            // socket.send('{"toUserId":"'+$("#toUserId").val()+'","contentText":"'+$("#contentText").val()+'","type":"'+'"}');
			socket.send(`{
				"toUserId":"${$("#toUserId").val()}",
				"contentText":"${$("#contentText").val()}"
			}`)
        }
    }
</script>
<body>
<p>【userId】:<div><input id="userId" name="userId" type="text" value="10"></div>
<p>【toUserId】:<div><input id="toUserId" name="toUserId" type="text" value="20"></div>
<p>【toUserId】:<div><input id="contentText" name="contentText" type="text" value="hello websocket"></div>
<p>【操作】:<div><a onclick="openSocket()">开启socket</a></div>
<p>【操作】:<div><a onclick="sendMessage()">发送消息</a></div>
</body>

</html>




在这里插入图片描述
uniapp(这部分自己看着改吧,业务逻辑啥的没删)

<template>
	<view>
		<view class="box-1" id="list-box" :style="'height:'+contentViewHeight+'px'">
			<scroll-view id="scrollview" scroll-y="true" :scroll-top="scrollTop" @scroll="scroll" style="height: 100%;">
				<view id="msglistview" class="talk-list">
					<view v-for="(item,index) in talkList" :key="index">
						<view class="item flex_col" :class=" item.userId != uid ? 'push':'pull' ">
							<!-- <image :src="item.uidhead" mode="aspectFill" class="pic"></image> -->
							<view class="pic">
								{{item.uidname}}
							</view>
							<view class="content" v-if="item.type==1">{{item.contentText}}</view>
							<view class="content" v-else-if="item.type==2">
								<image :src="item.contentText" mode=""></image>
							</view>
							<view class="content" v-else>
								<audio style="text-align: left" :src="item.contentText" name="语音" author="无" :action="audioAction" controls></audio>
							</view>
						</view>
					</view>
				</view>
			</scroll-view>
		</view>
		<view class="box-2">
			<view class="flex_col">
				<image @tap="up" src="../../static/imgs/add.png" mode="widthFix" class="addimg"></image>
				<view class="flex_grow">
					<input type="text" class="contentbox" v-model="content" placeholder="请输入聊天内容" placeholder-style="color:#DDD;"
					 :cursor-spacing="6">
				</view>
				<button class="send" @tap="send">发送</button>
			</view>
			<view class="imgs" v-if="ishidden">
				<view class="photobox">
					<image @tap="choosePhoto" src="../../static/imgs/photoimg.png"></image>
					<view class="photoss">相册</view>
				</view>
				<jsfun-record class="photobox" voicePath="" maxTime="15" minTime="5" @okClick="saveRecord">
					<image src="../../static/imgs/voice.png"></image>
					<view class="photoss">录音</view>
				</jsfun-record>
			</view>
		</view>
		<uni-popup ref="popup" type="bottom">

		</uni-popup>

	</view>
</template>

<script>
	import uniPopup from '@/components/uni-popup/uni-popup.vue'
	import jsfunRecord from '@/components/jsfun-record/jsfun-record.vue'
	export default {
		components: {
			jsfunRecord,
			uniPopup
		},
		data() {
			return {
				audioAction: {
					method: 'pause'
				},
				talkList: [],
				content: '',
				info: {},
				myinfo: {},
				ishidden: false,
				scrollTop: 0,
				old: {
					scrollTop: 0
				},
				contentViewHeight: 0,
				chatstate: false,
				uid: 0,
				timer: null,
			}
		},
		onLoad(option) {

			let that = this
			that.uid = option.uid
			uni.getSystemInfo({
				success: function(res) { // res - 各种参数
					that.contentViewHeight = res.windowHeight - uni.getSystemInfoSync().screenWidth / 750 * (120);
				}
			});
			// 连接聊天服务器
			this.openSocket()
			// this.getHistoryMsg()
			// 获取顶部信息
			this.myUser()
		},
		methods: {
			// 获取录音文件
			saveRecord: function(recordPath) {
				console.log("===音频文件地址:" + recordPath + "===")
				//do... 可以使用 uni.uploadFile 接口上传到服务器
				uni.uploadFile({
					url: 'https://tjfx.breakday.cc/staff/upload',
					filePath: recordPath,
					name: 'file',
					success: (uploadFileRes) => {
						let obj = JSON.parse(uploadFileRes.data)
						console.log(obj, 123456789)
						if (obj.code == 1) {
							let imgurl = obj.data.fileName
							let obj = {
								type: 2,
								msg: imgurl,
								toUid: this.uid,
								fromUid: uni.getStorageSync("uid"),
								toCompany: this.info.companyid,
								fromCompany: uni.getStorageSync("companyid"),
								fromImage: this.myinfo.headimage
							}
							// 发送聊天信息
							uni.sendSocketMessage({
								data: JSON.stringify(obj)
							});
							that.talkList.push(obj)
							this.$forceUpdate()
							this.scrollToBottom()
							this.chatstate = true
							this.ishidden = false
						}
					}
				});
			},
			open() {
				this.$refs.popup.open()
			},
			openSocket() {
				let that = this
				// 连接socket服务器
				uni.connectSocket({
					url: "wss://tjfx.breakday.cc/imserver/" + uni.getStorageSync('uid')
				});
				// 检测连接是否成功
				uni.onSocketOpen(function(res) {
					console.log('WebSocket连接已打开!');
				});
				// 获取服务器信息
				uni.onSocketMessage(function(res) {

					if (obj.code == 200) {} else {
						let obj = JSON.parse(res.data)
						console.log(obj)
						that.talkList.push(obj)
						that.$forceUpdate()
						that.scrollToBottom()
						that.chatstate = true
					}
				});
				// 检测socket连接情况,断线重连
				uni.onSocketError(function(res) {
					console.log('WebSocket连接打开失败,请检查!');
					uni.connectSocket({
						url: "wss://tjfx.breakday.cc/imserver/" + uni.getStorageSync('uid')
					});
				});
				// 监测socket是否挂关闭
				uni.onSocketClose(function(res) {
					console.log('WebSocket 已关闭!');
					uni.connectSocket({
						url: "wss://tjfx.breakday.cc/imserver/" + uni.getStorageSync('uid')
					});
				});
			},
			// 上传图片
			choosePhoto: function() {
				let that = this
				uni.chooseImage({
					success: (chooseImageRes) => {
						const tempFilePaths = chooseImageRes.tempFilePaths;
						console.log(tempFilePaths)
						uni.uploadFile({
							url: 'https://tjfx.breakday.cc/staff/upload',
							filePath: tempFilePaths[0],
							name: 'file',
							success: (uploadFileRes) => {
								let obj = JSON.parse(uploadFileRes.data)
								if (obj.code == 1) {
									let imgurl = obj.data.fileName
									let obj = {
										type: 2,
										contentText: imgurl,
										toUserId: "",
										fromUserId: uni.getStorageSync("uid"),
										uidname: that.myinfo.staffname,
										uidhead: that.myinfo.staffimg,
									}
									// 发送聊天信息
									uni.sendSocketMessage({
										data: JSON.stringify(obj)
									});
									that.talkList.push(obj)
									this.$forceUpdate()
									this.scrollToBottom()
									this.chatstate = true
									this.ishidden = false
								}
							}
						});

					}
				})
			},
			// 显示下方工具
			up: function() {
				this.ishidden = !this.ishidden;
			},
			// 获取平台消息
			myUser() {
				let that = this
				that.$api.myselfinformation({
					"id": uni.getStorageSync('uid')
				}, {
					'Content-Type': 'application/json;charset=UTF-8',
					'token': uni.getStorageSync("token")
				}).then((res) => {
					this.myinfo = res.data.data.data;
					console.log(this.myinfo)
				}).catch((err) => {
					console.log('失败', err)
				})
			},
			// 获取历史消息
			getHistoryMsg() {
				let that = this
				this.$api.getchatrecord({
					// 传给后台的参数
					"fromUid": uni.getStorageSync("uid"),
					"toUid": that.uid
				}, {
					'Content-Type': 'application/json;charset=UTF-8',
					'token': uni.getStorageSync("token")
				}).then((res) => {
					let arr = res.data.data.list
					this.talkList = arr
					this.$forceUpdate()
					this.scrollToBottom()
					this.chatstate = true
				}).catch((error) => {
					console.log(error)
				})
			},
			// 滚动到底部
			scrollToBottom(t) {
				let that = this
				uni.getSystemInfo({
					success: function(res) { // res - 各种参数
						let query = uni.createSelectorQuery()
						query.select('#scrollview').boundingClientRect()
						query.select('#msglistview').boundingClientRect()
						query.exec((res) => {
							let mainheight = res[1].height
							that.old.scrollTop = mainheight
							that.scrollTop = mainheight
							that.gobottom()
						})
					}
				});
			},
			gobottom(t) {
				this.$nextTick(function() {
					this.scrollTop = this.old.scrollTop + 520
				});
				this.scrollTop = 0
			},
			scroll(e) {
				let that = this
				if (that.chatstate == true) {
					this.old.scrollTop = e.detail.scrollTop
					that.chatstate = false
				} else {

				}
			},
			// 发送文本信息
			send() {
				let that = this
				if (!this.content) {
					uni.showToast({
						title: '请输入有效的内容',
						icon: 'none'
					})
					return;
				}
				// 发送聊天信息
				let obj = {
					type: 1,
					contentText: this.content,
					toUserId: "",
					fromUserId: uni.getStorageSync("uid"),
					uidname: that.myinfo.staffname,
					uidhead: that.myinfo.staffimg,
				}
				uni.sendSocketMessage({
					data: JSON.stringify(obj)
				});
				// 插入自己的聊天记录
				this.talkList.push(obj)
				// 置空输入框
				this.content = '';
				this.$forceUpdate()
				this.scrollToBottom()
				this.chatstate = true
			}
		}
	}
</script>

<style lang="scss">
	@import "../../lib/global.scss";

	.status_bar {
		height: var(--status-bar-height);
		width: 100%;
	}

	page {
		background-color: #F3F3F3;
		font-size: 28rpx;
	}

	/* 加载数据提示 */
	.tips {
		position: fixed;
		left: 0;
		top: var(--window-top);
		width: 100%;
		z-index: 9;
		background-color: rgba(0, 0, 0, 0.15);
		height: 72rpx;
		line-height: 72rpx;
		transform: translateY(-80rpx);
		transition: transform 0.3s ease-in-out 0s;

		&.show {
			transform: translateY(0);
		}
	}

	.box-1 {
		width: 100%;
		// min-height: 100%;
		padding-bottom: 100rpx;
		box-sizing: content-box;

		/* 兼容iPhoneX */
		margin-bottom: 0;
		margin-bottom: constant(safe-area-inset-bottom);
		margin-bottom: env(safe-area-inset-bottom);
	}

	.box-2 {
		position: fixed;
		left: 0;
		width: 100%;
		bottom: 0;
		height: auto;
		z-index: 2;
		border-top: #e5e5e5 solid 1px;
		box-sizing: content-box;
		background-color: #F3F3F3;

		/* 兼容iPhoneX */
		padding-bottom: 0;
		padding-bottom: constant(safe-area-inset-bottom);
		padding-bottom: env(safe-area-inset-bottom);

		>view {
			padding: 0 20rpx;
			height: 100rpx;
		}

		.contentbox {
			background-color: #fff;
			height: 64rpx;
			line-height: 30rpx;
			padding: 0 20rpx;
			border-radius: 32rpx;
			font-size: 28rpx;
		}

		.send {
			background-color: #42b983;
			color: #fff;
			height: 64rpx;
			margin-left: 20rpx;
			border-radius: 32rpx;
			padding: 0;
			width: 120rpx;
			line-height: 62rpx;

			&:active {
				background-color: #5fc496;
			}
		}
	}

	.talk-list {

		/* 消息项,基础类 */
		.item {
			padding: 20rpx 20rpx 0 20rpx;
			align-items: flex-start;
			align-content: flex-start;
			color: #333;

			.pic {
				width: 92rpx;
				height: 92rpx;
				line-height: 92rpx;
				text-align: center;
				// border-radius: 50%;
				// border: #fff solid 1px;
			}

			.content {
				padding: 20rpx;
				border-radius: 4px;
				max-width: 500rpx;
				word-break: break-all;
				line-height: 52rpx;
				position: relative;
			}

			/* 收到的消息 */
			&.pull {
				.content {
					margin-left: 32rpx;
					background-color: #fff;

					&::after {
						content: '';
						display: block;
						width: 0;
						height: 0;
						border-top: 16rpx solid transparent;
						border-bottom: 16rpx solid transparent;
						border-right: 20rpx solid #fff;
						position: absolute;
						top: 30rpx;
						left: -18rpx;
					}
				}
			}

			/* 发出的消息 */
			&.push {
				/* 主轴为水平方向,起点在右端。使不修改DOM结构,也能改变元素排列顺序 */
				flex-direction: row-reverse;

				.content {
					margin-right: 32rpx;
					background-color: #a0e959;

					&::after {
						content: '';
						display: block;
						width: 0;
						height: 0;
						border-top: 16rpx solid transparent;
						border-bottom: 16rpx solid transparent;
						border-left: 20rpx solid #a0e959;
						position: absolute;
						top: 30rpx;
						right: -18rpx;
					}
				}
			}
		}
	}

	.user {
		position: fixed;
		top: 0;
		width: 750rpx;
		height: 102rpx;
		padding-top: var(--status-bar-height+50px);
		display: flex;
		align-items: center;
		border-bottom: 10rpx solid #eee;
		z-index: 10000;
	}

	.headPhoto,
	.userInfo {
		padding: 12rpx 0 12rpx 20rpx;
		font-size: 24rpx;
	}

	.fcolor {
		color: rgba(128, 128, 128, 1);
		font-size: 20rpx;
	}

	.headPhoto image {
		width: 78rpx;
		height: 78rpx;
		border-radius: 40rpx;
	}

	.imgs {
		width: 750rpx;
		min-height: 220rpx;
		display: flex;
	}

	.imgs image {
		width: 90rpx;
		height: 90rpx;
		margin: 0 auto 10rpx;
	}

	.photos {
		display: flex;
		min-height: 100rpx;
		justify-content: space-between;
	}

	.photobox {
		width: 130rpx;
		height: 130rpx;
		display: flex;
		align-items: center;
		flex-direction: column;
	}

	.photoss {
		height: 30rpx;
		color: rgba(80, 80, 80, 1);
		font-size: 28rpx;
		line-height: 30rpx;
		text-align: center;
		text-align: center;
	}

	.addimg {
		width: 48rpx;
		height: 48rpx;
		margin-right: 5rpx;
	}

	.content image {
		width: 164rpx;
		height: 164rpx;
	}
</style>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

翎墨袅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值