Browse Source

uniapp 单人文字聊天、发送图片

master
xsx 3 years ago
parent
commit
f35311c805
  1. 2
      im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java
  2. 2
      im-server/src/main/java/com/bx/imserver/netty/tcp/TcpSocketServer.java
  3. 2
      im-server/src/main/java/com/bx/imserver/netty/ws/WebSocketServer.java
  4. 10
      im-ui/src/components/chat/ChatBox.vue
  5. 18
      im-uniapp/App.vue
  6. 60
      im-uniapp/common/request.js
  7. 19
      im-uniapp/components/friend-item/friend-item.vue
  8. 9
      im-uniapp/main.js
  9. 25
      im-uniapp/pages.json
  10. 6
      im-uniapp/pages/chat/chat.vue
  11. 81
      im-uniapp/pages/common/user-info.vue
  12. 172
      im-uniapp/pages/login/login.vue
  13. BIN
      im-uniapp/static/iconfont/iconfont.ttf
  14. 51
      im-uniapp/store/chatStore.js
  15. 5
      im-uniapp/store/friendStore.js
  16. 2
      im-uniapp/store/groupStore.js
  17. 9
      im-uniapp/store/index.js
  18. 7
      im-uniapp/store/userStore.js

2
im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java

@ -103,7 +103,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
String strJson = JwtUtil.getInfo(refreshToken);
Long userId = JwtUtil.getUserId(refreshToken);
String accessToken = JwtUtil.sign(userId,strJson,jwtProperties.getAccessTokenExpireIn(),jwtProperties.getAccessTokenSecret());
String newRefreshToken = JwtUtil.sign(userId,strJson,jwtProperties.getAccessTokenExpireIn(),jwtProperties.getAccessTokenSecret());
String newRefreshToken = JwtUtil.sign(userId,strJson,jwtProperties.getRefreshTokenExpireIn(),jwtProperties.getRefreshTokenSecret());
LoginVO vo =new LoginVO();
vo.setAccessToken(accessToken);
vo.setAccessTokenExpiresIn(jwtProperties.getAccessTokenExpireIn());

2
im-server/src/main/java/com/bx/imserver/netty/tcp/TcpSocketServer.java

@ -59,7 +59,7 @@ public class TcpSocketServer implements IMServer {
protected void initChannel(Channel ch) throws Exception {
// 获取职责链
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new IdleStateHandler(15, 0, 0, TimeUnit.SECONDS));
pipeline.addLast(new IdleStateHandler(30, 0, 0, TimeUnit.SECONDS));
pipeline.addLast("encode",new MessageProtocolEncoder());
pipeline.addLast("decode",new MessageProtocolDecoder());
pipeline.addLast("handler", new IMChannelHandler());

2
im-server/src/main/java/com/bx/imserver/netty/ws/WebSocketServer.java

@ -63,7 +63,7 @@ public class WebSocketServer implements IMServer {
protected void initChannel(Channel ch) throws Exception {
// 获取职责链
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new IdleStateHandler(15, 0, 0, TimeUnit.SECONDS));
pipeline.addLast(new IdleStateHandler(30, 0, 0, TimeUnit.SECONDS));
pipeline.addLast("http-codec", new HttpServerCodec());
pipeline.addLast("aggregator", new HttpObjectAggregator(65535));
pipeline.addLast("http-chunked", new ChunkedWriteHandler());

10
im-ui/src/components/chat/ChatBox.vue

@ -134,7 +134,7 @@
loadStatus: "loading"
}
// id
this.setTargetId(msgInfo, this.chat.targetId);
this.fillTargetId(msgInfo, this.chat.targetId);
//
this.$store.commit("insertMessage", msgInfo);
//
@ -182,7 +182,7 @@
loadStatus: "loading"
}
// id
this.setTargetId(msgInfo, this.chat.targetId);
this.fillTargetId(msgInfo, this.chat.targetId);
//
this.$store.commit("insertMessage", msgInfo);
//
@ -232,7 +232,7 @@
type: 3
}
// id
this.setTargetId(msgInfo, this.chat.targetId);
this.fillTargetId(msgInfo, this.chat.targetId);
this.$http({
url: this.messageAction,
method: 'post',
@ -252,7 +252,7 @@
this.showVoice = false;
})
},
setTargetId(msgInfo, targetId) {
fillTargetId(msgInfo, targetId) {
if (this.chat.type == "GROUP") {
msgInfo.groupId = targetId;
} else {
@ -269,7 +269,7 @@
type: 0
}
// id
this.setTargetId(msgInfo, this.chat.targetId);
this.fillTargetId(msgInfo, this.chat.targetId);
this.lockMessage = true;
this.$http({
url: this.messageAction,

18
im-uniapp/App.vue

@ -1,16 +1,18 @@
<script>
export default {
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
onLaunch() {
//
console.log("onLaunch")
// #ifdef H5
//
uni.navigateTo({
url:"/pages/login/login"
})
// #endif
}
}
</script>
<style lang="scss">
@import url('./static/icon/iconfont.css');
</style>

60
im-uniapp/common/request.js

@ -1,14 +1,14 @@
const BASE_URL = "http://192.168.43.6:8888"
const request = (options) => {
const request = (options) => {
const header = options.header||{};
const accessToken = uni.getStorageSync("accessToken");
if (accessToken) {
header.accessToken = accessToken;
const loginInfo = uni.getStorageSync("loginInfo");
if (loginInfo) {
header.accessToken = loginInfo.accessToken;
}
return new Promise(function(resolve, reject) {
uni.request({
url: BASE_URL + options.url,
url: process.env.BASE_URL + options.url,
method: options.method || 'GET',
header: header,
data: options.data || {},
@ -21,8 +21,7 @@ const request = (options) => {
});
} else if (res.data.code == 401) {
console.log("token失效,尝试重新获取")
const refreshToken = uni.getStorageSync("refreshToken");
if (!refreshToken) {
if (!loginInfo) {
uni.navigateTo({
url: '/pages/login/login'
});
@ -32,7 +31,7 @@ const request = (options) => {
method: 'PUT',
url: '/refreshToken',
header: {
refreshToken: refreshToken
refreshToken: loginInfo.refreshToken
}
})
// 换取token失败,跳转至登录界面
@ -42,10 +41,7 @@ const request = (options) => {
});
}
// 保存token
uni.setStorageSync("accessToken", data.accessToken);
uni.setStorageSync("refreshToken", data.refreshToken);
// 这里需要把headers清掉,否则请求时会报错,原因暂不详...
//response.config.headers=undefined;
uni.setStorageSync("loginInfo", data);
// 重新发送刚才的请求
return request(options)
} else {
@ -58,44 +54,6 @@ const request = (options) => {
}
},
fail(error) {
switch (error.response.status) {
case 400:
uni.showToast({
title: error.response.data,
type: 'error',
duration: 1500,
})
break
case 401:
uni.navigateTo({
url: '/pages/login/login'
});
break
case 405:
uni.showToast({
title: 'http请求方式有误',
icon: 'error',
duration: 1500
})
break
case 404:
case 500:
uni.showToast({
title: '服务器出了点小差,请稍后再试',
icon: 'error',
duration: 1500
})
break
case 501:
uni.showToast({
title: '服务器不支持当前请求所需要的某个功能',
icon: 'error',
duration: 1500
})
break
}
return reject(error)
}
});

19
im-uniapp/components/friend-item/friend-item.vue

@ -12,13 +12,12 @@
<script>
export default {
name: "frinedItem",
name: "frined-item",
data() {
return {}
},
methods:{
showFriendInfo(id){
console.log(id);
showFriendInfo(){
uni.navigateTo({
url: "/pages/common/user-info?id="+this.friend.id
})
@ -42,7 +41,7 @@
<style scope lang="scss">
.friend-item {
height: 100rpx;
height: 120rpx;
display: flex;
margin-bottom: 1rpx;
position: relative;
@ -59,8 +58,8 @@
display: flex;
justify-content: center;
align-items: center;
width: 80rpx;
height: 80rpx;
width: 100rpx;
height: 100rpx;
.head-image{
width: 100%;
@ -70,7 +69,7 @@
}
.text {
font-size: 30rpx;
font-size: 36rpx;
margin-left: 30rpx;
flex: 1;
display: flex;
@ -80,13 +79,9 @@
flex-shrink: 0;
overflow: hidden;
&>view {
display: flex;
justify-content: flex-start;
}
.online-status {
font-size: 22rpx;
font-size: 28rpx;
font-weight: 600;
&.online {

9
im-uniapp/main.js

@ -1,12 +1,19 @@
import App from './App'
import request from './common/request';
import emotion from './common/emotion.js';
import * as enums from './common/enums.js';
import * as socketApi from './common/wssocket';
import store from './store';
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
app.use(store);
app.config.globalProperties.$http = request
app.config.globalProperties.$http = request;
app.config.globalProperties.$wsApi = socketApi;
app.config.globalProperties.$emo = emotion;
app.config.globalProperties.$enums = enums;
return {
app
}

25
im-uniapp/pages.json

@ -7,23 +7,7 @@
}, {
"path": "pages/chat/chat",
"style": {
"navigationBarTitleText": "聊天消息",
"app-plus": {
"titleNView": {
"buttons": [{
"text": "&#xe6e0;",
"fontSrc": "/static/iconfont/iconfont.ttf",
"fontSize": "40rpx"
},
{
"text": "&#xe648;",
"fontSrc": "/static/iconFont/iconfont.ttf",
"fontSize": "40rpx"
}
]
}
}
"navigationBarTitleText": "聊天消息"
}
}, {
"path": "pages/friend/friend",
@ -34,13 +18,13 @@
"titleNView": {
"buttons": [{
"text": "&#xe6e0;",
"fontSrc": "/static/iconfont/iconfont.ttf",
"fontSrc": "/static/icon/iconfont.ttf",
"fontSize": "40rpx",
"width": "50rpx"
},
{
"text": "&#xe648;",
"fontSrc": "/static/iconFont/iconfont.ttf",
"fontSrc": "/static/icon/iconfont.ttf",
"fontSize": "30rpx",
"width": "50rpx"
}
@ -48,7 +32,6 @@
}
}
}
}, {
"path": "pages/group/group",
"style": {
@ -87,6 +70,8 @@
}
},{
"path": "pages/common/user-info"
},{
"path": "pages/chat/chat-box"
}],
"globalStyle": {
"navigationBarTextStyle": "black",

6
im-uniapp/pages/chat/chat.vue

@ -1,6 +1,10 @@
<template>
<view>
消息
<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
<view v-for="(chat,index) in $store.state.chatStore.chats" :key="index">
<chat-item :chat="chat" :index="index"></chat-item>
</view>
</scroll-view>
</view>
</template>

81
im-uniapp/pages/common/user-info.vue

@ -9,10 +9,12 @@
<text class="info-username">
{{userInfo.userName}}
</text>
<uni-icons v-show="userInfo.sex==0" class="sex-boy" type="person-filled" size="20" color="darkblue" ></uni-icons>
<uni-icons v-show="userInfo.sex==1" class="sex-girl" type="person-filled" size="20" color="darkred" ></uni-icons>
<uni-icons v-show="userInfo.sex==0" class="sex-boy" type="person-filled" size="20"
color="darkblue"></uni-icons>
<uni-icons v-show="userInfo.sex==1" class="sex-girl" type="person-filled" size="20"
color="darkred"></uni-icons>
</view>
<text >
<text>
昵称 {{userInfo.nickName}}
</text>
<text class="person-wx-name">
@ -22,21 +24,53 @@
</view>
<view class="line"></view>
<view class="btn-group">
<button v-show="isFriend" type="primary">发消息</button>
<button v-show="!isFriend" type="primary">加为好友</button>
<button v-show="isFriend" type="primary" @click="sendMessage()">发消息</button>
<button v-show="!isFriend" type="primary" @click="addFriend()">加为好友</button>
</view>
</view>
</template>
<script>
export default{
data(){
export default {
data() {
return {
userInfo:{}
userInfo: {}
}
},
methods: {
sendMessage() {
let chat = {
type: 'PRIVATE',
targetId: this.userInfo.id,
showName: this.userInfo.nickName,
headImage: this.userInfo.headImage,
};
this.$store.commit("openChat", chat);
uni.switchTab({
url:"/pages/chat/chat"
})
},
addFriend() {
this.$http({
url: "/friend/add?friendId=" + this.userInfo.id,
method: "POST"
}).then((data) => {
let friend = {
id: this.userInfo.id,
nickName: this.userInfo.nickName,
headImage: this.userInfo.headImageThumb,
online: this.userInfo.online
}
this.$store.commit("addFriend", friend);
uni.showToast({
title: '添加成功,对方已成为您的好友',
icon: 'none'
})
})
}
},
computed: {
isFriend(){
isFriend() {
let friends = this.$store.state.friendStore.friends;
let friend = friends.find((f) => f.id == this.userInfo.id);
return friend != undefined;
@ -44,64 +78,63 @@
},
onLoad(options) {
//
console.log(options.id)
const id = options.id;
this.$http({
url:"/user/find/"+id
}).then((userInfo)=>{
url: "/user/find/" + id
}).then((userInfo) => {
this.userInfo = userInfo;
})
}
}
</script>
<style lang="scss" scoped>
.user-info{
.content{
.user-info {
.content {
height: 200rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx;
.avatar{
.avatar {
display: flex;
justify-content: center;
align-items: center;
width: 160rpx;
height: 160rpx;
.head-image{
.head-image {
width: 100%;
height: 100%;
border-radius: 10%;
}
}
.info-item{
.info-item {
display: flex;
align-items: flex-start;
flex-direction: column;
padding-left: 40rpx;
flex: 1;
.info-primary{
.info-primary {
display: flex;
.info-username{
.info-username {
font-size: 40rpx;
font-weight: 600;
}
}
}
}
.line{
.line {
margin: 20rpx;
border-bottom: 1px solid #aaaaaa;
}
.btn-group{
.btn-group {
margin: 100rpx;
}
}

172
im-uniapp/pages/login/login.vue

@ -3,8 +3,7 @@
<view class="login-title">欢迎登录</view>
<uni-forms style="margin-top: 100px;" :modelValue="loginForm" :rules="rules" validate-trigger="bind">
<uni-forms-item name="userName">
<uni-easyinput type="text" v-model="loginForm.userName" prefix-icon="person" focus
placeholder="用户名" />
<uni-easyinput type="text" v-model="loginForm.userName" prefix-icon="person" focus placeholder="用户名" />
</uni-forms-item>
<uni-forms-item name="password">
<uni-easyinput type="password" v-model="loginForm.password" prefix-icon="locked" placeholder="密码" />
@ -39,9 +38,6 @@
}
}
}
},
onLoad() {
},
methods: {
submit() {
@ -49,18 +45,170 @@
url: '/login',
data: this.loginForm,
method: 'POST'
}).then( data=>{
uni.setStorageSync("accessToken", data.accessToken);
uni.setStorageSync("refreshToken", data.refreshToken);
this.$store.dispatch("initStore").then(()=>{
console.log("登录成功,准备跳转...")
}).then(data => {
console.log("登录成功,自动跳转到聊天页面...")
uni.setStorageSync("loginInfo", data);
this.init(data);
})
},
init(loginInfo) {
//
this.$store.dispatch("load").then(() => {
// websocket
this.initWebSocket(loginInfo);
}).catch((e) => {
console.log(e);
this.quit();
})
//
uni.switchTab({
url:"/pages/chat/chat"
url: "/pages/chat/chat"
})
},
initWebSocket(loginInfo) {
let userId = this.$store.state.userStore.userInfo.id;
this.$wsApi.createWebSocket(process.env.WS_URL, loginInfo.accessToken);
this.$wsApi.onopen(() => {
this.pullUnreadMessage();
});
this.$wsApi.onmessage((cmd, msgInfo) => {
if (cmd == 2) {
// 线
uni.showModal({
content: '您已在其他地方登陆,将被强制下线',
showCancel: false,
})
this.quit();
} else if (cmd == 3) {
//
msgInfo.selfSend = userId == msgInfo.sendId;
//
this.handlePrivateMessage(msgInfo);
} else if (cmd == 4) {
//
msgInfo.selfSend = userId == msgInfo.sendId;
//
this.handleGroupMessage(msgInfo);
}
})
},
pullUnreadMessage() {
//
this.$http({
url: "/message/private/pullUnreadMessage",
method: 'POST'
});
//
this.$http({
url: "/message/group/pullUnreadMessage",
method: 'POST'
});
},
handlePrivateMessage(msg) {
//
let friendId = msg.selfSend ? msg.recvId : msg.sendId;
let friend = this.$store.state.friendStore.friends.find((f) => f.id == friendId);
if (!friend) {
//
this.$http({
url: `/friend/find/${msg.sendId}`,
method: 'get'
}).then((friend) => {
this.insertPrivateMessage(friend, msg);
this.$store.commit("addFriend", friend);
})
} else {
this.insertPrivateMessage(friend, msg);
}
},
insertPrivateMessage(friend, msg) {
// webrtc
if (msg.type >= this.$enums.MESSAGE_TYPE.RTC_CALL &&
msg.type <= this.$enums.MESSAGE_TYPE.RTC_CANDIDATE) {
// //
// if (msg.type == this.$enums.MESSAGE_TYPE.RTC_CALL ||
// msg.type == this.$enums.MESSAGE_TYPE.RTC_CANCEL) {
// this.$store.commit("showVideoAcceptorBox", friend);
// this.$refs.videoAcceptor.handleMessage(msg)
// } else {
// this.$refs.videoAcceptor.close()
// this.$refs.privateVideo.handleMessage(msg)
// }
// return;
}
let chatInfo = {
type: 'PRIVATE',
targetId: friend.id,
showName: friend.nickName,
headImage: friend.headImage
};
//
this.$store.commit("openChat", chatInfo);
//
this.$store.commit("insertMessage", msg);
//
!msg.selfSend && this.playAudioTip();
},
handleGroupMessage(msg) {
//
let group = this.$store.state.groupStore.groups.find((g) => g.id == msg.groupId);
if (!group) {
//
this.$http({
url: `/group/find/${msg.groupId}`,
method: 'get'
}).then((group) => {
this.insertGroupMessage(group, msg);
this.$store.commit("addGroup", group);
})
} else {
this.insertGroupMessage(group, msg);
}
},
insertGroupMessage(group, msg) {
let chatInfo = {
type: 'GROUP',
targetId: group.id,
showName: group.remark,
headImage: group.headImageThumb
};
//
this.$store.commit("openChat", chatInfo);
//
this.$store.commit("insertMessage", msg);
//
!msg.selfSend && this.playAudioTip();
},
quit() {
uni.showToast({
title: "退出登录"
})
console.log("退出登录")
this.$wsApi.closeWebSocket();
uni.removeStorageSync("loginInfo");
uni.navigateTo({
url:"/pages/login/login"
})
},
playAudioTip() {
let audio = new Audio();
let url = "/static/audio/tip.wav";
audio.src = url;
audio.play();
}
},
mounted() {
console.log("login mounted")
let loginInfo = uni.getStorageSync("loginInfo");
if (loginInfo) {
// ,
this.init(loginInfo);
}
}
}

BIN
im-uniapp/static/iconfont/iconfont.ttf

Binary file not shown.

51
im-uniapp/store/chatStore.js

@ -1,15 +1,16 @@
export default {
state: {
activeIndex: -1,
chats: []
},
mutations: {
setChats(state,chats){
console.log(chats);
state.chats = chats;
},
openChat(state, chatInfo) {
let chat = null;
let activeChat = state.activeIndex>=0?state.chats[state.activeIndex]:null;
for (let i in state.chats) {
if (state.chats[i].type == chatInfo.type &&
state.chats[i].targetId === chatInfo.targetId) {
@ -34,25 +35,11 @@ export default {
};
state.chats.unshift(chat);
}
// 选中会话保持不变
if(activeChat){
state.chats.forEach((chat,idx)=>{
if(activeChat.type == chat.type
&& activeChat.targetId == chat.targetId){
state.activeIndex = idx;
}
})
}
},
activeChat(state, idx) {
state.activeIndex = idx;
state.chats[idx].unreadCount = 0;
uni.setStorageSync("chats",state.chats);
},
removeChat(state, idx) {
state.chats.splice(idx, 1);
if (state.activeIndex >= state.chats.length) {
state.activeIndex = state.chats.length - 1;
}
uni.setStorageSync("chats",state.chats);
},
removeGroupChat(state, groupId) {
for (let idx in state.chats) {
@ -113,6 +100,7 @@ export default {
}
// 新的消息
chat.messages.push(msgInfo);
uni.setStorageSync("chats",state.chats);
},
deleteMessage(state, msgInfo){
@ -141,6 +129,7 @@ export default {
break;
}
}
uni.setStorageSync("chats",state.chats);
},
updateChatFromFriend(state, friend) {
for (let i in state.chats) {
@ -151,6 +140,7 @@ export default {
break;
}
}
uni.setStorageSync("chats",state.chats);
},
updateChatFromGroup(state, group) {
for (let i in state.chats) {
@ -161,11 +151,26 @@ export default {
break;
}
}
},
resetChatStore(state) {
state.activeIndex = -1;
state.chats = [];
uni.setStorageSync("chats",state.chats);
}
},
actions:{
loadChat(context) {
return new Promise((resolve, reject) => {
console.log(".....")
uni.getStorage({
key:"chats",
success(res) {
context.commit("setChats",res.data);
resolve()
},
fail() {
resolve();
}
});
})
}
}
}

5
im-uniapp/store/friendStore.js

@ -62,7 +62,7 @@ export default {
}
},
actions: {
initFriendStore(context) {
loadFriend(context) {
return new Promise((resolve, reject) => {
request({
url: '/friend/list',
@ -71,15 +71,12 @@ export default {
context.commit("setFriends", friends);
context.dispatch("refreshOnlineStatus");
resolve()
console.log("friendstore")
}).catch((res) => {
reject();
console.log("friendstore reject")
})
});
},
refreshOnlineStatus(context) {
console.log(context.state.friends)
if (context.state.friends.length > 0 ) {
let userIds = [];
context.state.friends.forEach((f) => {

2
im-uniapp/store/groupStore.js

@ -37,7 +37,7 @@ export default {
}
},
actions: {
initGroupStore(context) {
loadGroup(context) {
return new Promise((resolve, reject) => {
request({
url: '/group/list',

9
im-uniapp/store/index.js

@ -14,11 +14,12 @@ const store = createStore({
},
state: {},
actions: {
initStore(context) {
load(context) {
const promises = [];
promises.push(this.dispatch("initUserStore"));
promises.push(this.dispatch("initFriendStore"));
promises.push(this.dispatch("initGroupStore"));
promises.push(this.dispatch("loadUser"));
promises.push(this.dispatch("loadFriend"));
promises.push(this.dispatch("loadGroup"));
promises.push(this.dispatch("loadChat"));
return Promise.all(promises);
}
},

7
im-uniapp/store/userStore.js

@ -11,11 +11,6 @@ export default {
mutations: {
setUserInfo(state, userInfo) {
// 切换用户后,清理缓存
if(userInfo.id != state.userInfo.id){
console.log("用户切换")
this.commit("resetChatStore");
}
state.userInfo = userInfo;
},
setUserState(state, userState) {
@ -23,7 +18,7 @@ export default {
},
},
actions:{
initUserStore(context){
loadUser(context){
return new Promise((resolve, reject) => {
request({
url: '/user/self',

Loading…
Cancel
Save