uni-app 即时聊天:朋友圈
功能设计简图
关于个人动态的操作
个人动态表的部分操作会导致朋友圈动态表的改变,例如发布动态和删除动态
关于朋友圈动态的操作
表设计
个人动态表
javascript
const mongoose = require("mongoose")
// 动态表
const dynamicSchema = new mongoose.Schema({
userID: { type: mongoose.Schema.Types.ObjectId, ref: "user" }, //用户id
logList: [
{ text: String, imgList: Array, originalList: Array, comments: Array, like: Array, date: Date, address: String }
],
// imgList 存储压缩图的数组(为什么添加这个属性:因为在加载朋友圈图片时如果直接加载原图片速度会很慢,所以在上传图片的时候需要进行图片压缩,在加载的时候先加载压缩图,点击预览时加载原图片,图片压缩的办法在后面会提到)
// originalList 存储原始图片的数组
// text 文字内容
// comments 评论
// like 点赞
// date 日期
// address 位置
})
module.exports = mongoose.model("dynamic", dynamicSchema);
朋友圈动态表
javascript
const mongoose = require("mongoose")
// 朋友圈动态表(包括好友和自己的动态)
const socialSchema = new mongoose.Schema({
userID: { type: mongoose.Schema.Types.ObjectId, ref: "user" }, //用户id
dynamicList: [
{ friend: String, date: Date }
// friend 存储用户id
// date 动态发布的时间
],
})
module.exports = mongoose.model("social", socialSchema);
评论通知表
javascript
const mongoose = require("mongoose")
// 评论通知表
const comNotifySchema = new mongoose.Schema({
userID: String, //用户id
notify_list: [],
// notify_list传入的对象,示例如下
// {
// fromUser: id, // 点赞或者评论用户的id
// fromName: String, // 点赞或者评论用户的名字
// toUser: id, // 评论谁或者是回复谁(id) ,点赞时为null
// toName: String, // 评论谁或者是回复谁(名字),点赞时为null
// date, logDate,
// type: "like" || "comment", // 类型:评论,点赞
// content: null || String, // 内容:评论则存在对应的内容,点赞则为null
// unRead: false || true // 用户是否已读
// }
})
module.exports = mongoose.model("comNotify", comNotifySchema);
动态发表
效果图示
思路简析
在发表动态的时候,先要判断动态里是否有图片,如果有图片则先上传图片(没有图片直接发表动态),后端接收图片的时候会进行图片压缩,并返回两个图片地址(压缩图地址,原图地址),因为uni-app不支持多张图片同时上传只能一张一张上传,所以前端需要判断所有图片是否都上传成功,都上传成功后则执行动态发布的请求,后端发表动态时会执行三个步骤:添加到个人动态表,添加到朋友圈表(自己和好友),添加到动态通知表(好友),执行完这三个步骤,则动态发布成功
后端
图片压缩
图片压缩需要安装 images 模块
javascript
const images = require("images")
// 上传朋友圈图片
"POST /upload/dynamic": async ctx => {
// let data = ctx.req.body
let url = basePath + ctx.req.body.savePath + "/" + ctx.req.file.filename
let imgPath = "public/" + ctx.req.body.savePath + "/" + ctx.req.file.filename
let zipUrl = compression(imgPath, ctx.req.body.savePath, ctx.req.file.filename, 200)
ctx.body = {
imgUrl: zipUrl, // 压缩图地址
url // 原图片地址
}
},
// 图片压缩的方法
function compression(filePath, savePath, fileName, num) {
//根据文件路径读取文件,返回文件列表
let zipPath = "public/" + savePath + "/" + "zip" + fileName //压缩后保存的路径
let number = num ? num : 150 //缩放的图片像素宽度
images(filePath) //Load image from file
//加载图像文件
.size(number) //Geometric scaling the image to 400 pixels width
//等比缩放图像到150像素宽
.save(zipPath, { //Save the image to a file, with the quality of 50
quality: 100 //保存图片到文件,图片质量为100
});
return basePath + savePath + "/" + "zip" + fileName //压缩后保存的路径
}
动态发布
javascript
// 发布动态
exports.published = async data => {
let { token, text, imgList, comments, originalList, like, date, address } = data
let tokenRes = verifyToken(token)
let user = await User.findById(tokenRes.id)
// 添加到我的动态表中
let tokenDynamic = await Dynamic.findOne({ userID: tokenRes.id })
if (tokenDynamic) { // 个人动态表存在
let logList = tokenDynamic.logList
logList.unshift({
text, imgList, originalList, comments, like, date, address
})
res = await Dynamic.updateOne({ userID: tokenRes.id }, { $set: { logList } })
} else { // 个人动态表不存在
res = await Dynamic.create({
userID: tokenRes.id,
logList: [{
text, imgList, originalList, comments, like, date, address
}]
})
}
if (res.userID || res.nModified == 1) { // 将动态成功存储在个人动态表中后
let friends = await Friend.findOne({ userID: tokenRes.id }) // 获取好友表
let friend_list = friends.friend_list
friend_list.push({ //将token用户也添加到好友表中
user: tokenRes.id,
nickName: ""
})
// 遍历好友表将动态添加到好友的朋友圈动态表中,因为循环体中包含了 async 和 await 所以必须使用Promise.all包裹
// 如果直接遍历会导致循环体中的代码失效
let result = await Promise.all(friend_list.map(async item => {
let d_list = await Social.findOne({ userID: item.user })
if (d_list) {
let dynamicList = d_list.dynamicList
dynamicList.unshift({
friend: tokenRes.id, date,
})
let d_res = await Social.updateOne({ userID: item.user }, { $set: { dynamicList } })
} else {
let d_res = await Social.create({
userID: item.user,
dynamicList: [
{ friend: tokenRes.id, date }
]
})
}
// 添加到好友动态通知表中
if (item.user != tokenRes.id) {
let dy = await DyNotify.findOne({ userID: item.user })
if (dy) { // 动态通知表存在
let notify_list = dy.notify_list
notify_list[0] = {
fromUser: user._id,
fromImg: user.avatars
}
let d_res = await DyNotify.updateOne({ userID: item.user }, { $set: { notify_list } })
} else { // 动态通知表不存在
let d_res = await DyNotify.create({
userID: item.user,
notify_list: [
{
ormUser: user._id,
fromImg: user.avatars
}
]
})
}
let socketUser = await userSocket.findOne({ userId: item.user })
// 已经将socket.io对象挂载到global对象上,所以可以直接调用
io.to(socketUser.socketId).emit("getDyNotify")
}
}))
return { status: 1, msg: "发布成功" }
} else {
return { status: 0, msg: "发布失败" }
}
}
前端
javascript
<template>
<view class="content">
<comtarbar class="comtarbar">
<template #center>
<view class="title">编辑</view>
</template>
<template #right>
<view class="btn" @tap="uploadImg">发表</view>
</template>
</comtarbar>
<view class="edit_box">
<view class="textarea-box"><textarea v-model="content" placeholder="请输入文字" /></view>
<view class="e_title">
<view class="title">添加图片</view>
<view v-if="imglist.length > 2"><view class="btn" @tap="deleteAll">移除全部</view></view>
</view>
<view class="imagebox">
<view class="image_item" v-for="(item, index) in imglist" :key="index">
<image @tap="previewImg(index)" class="c_image" :src="item" mode="aspectFill"></image>
<image @tap="deleteImg(index)" class="yc" src="../../../static/images/edit/yc.png" mode="widthFix"></image>
</view>
<view class="image_item" v-if="imglist.length < 9" @tap="choose">
<image class="c_image" src="../../../static/images/group/addimage.png" mode="widthFix"></image>
</view>
</view>
<view class="e_title"><view class="title">添加位置</view></view>
<view class="localtion">
<view class="localtion_btn" @tap="getLocaltion">定位</view>
<view v-if="address != ''" class="address">{{ address }}</view>
</view>
</view>
</view>
</template>
<script>
import request from '../../../api/request.js';
import { mapGetters } from 'vuex';
export default {
data() {
return {
content: '',
imglist: [],
address: '',
path: ''
};
},
onLoad(options) {
this.path = options.path;
},
computed: {
...mapGetters(['getUser'])
},
methods: {
// 选择图片
choose() {
uni.chooseImage({
count: 9, //默认9
sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera '], //从相册选择
success: res => {
res.tempFilePaths.map(item => {
if (this.imglist.length < 9) {
this.imglist.push(item);
}
return item;
});
}
});
},
// 移除图片
deleteImg(index) {
this.imglist.splice(index, 1);
},
// 移除全部图片
deleteAll() {
this.imglist = [];
},
// 预览图片
previewImg(index) {
uni.previewImage({
urls: this.imglist,
current: index,
longPressActions: {
itemList: ['发送给朋友', '保存图片', '收藏'],
success: function(data) {
console.log('选中了第' + (data.tapIndex + 1) + '个按钮,第' + (data.index + 1) + '张图片');
},
fail: function(err) {
console.log(err.errMsg);
}
}
});
},
// 获取位置
getLocaltion() {
uni.chooseLocation({
success: res => {
this.address = res.address;
}
});
},
// 上传图片
uploadImg() {
// 如果有图片
if (this.imglist.length > 0) {
let list = [];
let originalList = [];
this.imglist.map(item => {
//遍历上传图片
uni.uploadFile({
url: 'http://localhost:3000/upload/dynamic',
filePath: item,
name: 'file',
formData: {
token: uni.getStorageSync('token'),
savePath: 'dynamic'
},
success: uploadFileRes => {
let { imgUrl, url } = JSON.parse(uploadFileRes.data);
list.push(imgUrl);
originalList.push(url);
if (list.length == this.imglist.length) {
//图片上传完毕后发布动态
this.published(list, originalList);
}
}
});
return item;
});
} else {
//没有图片
this.published();
}
},
// 发布动态
async published(list, originalList) {
let res = await request({
url: '/dynamic/published',
data: {
token: uni.getStorageSync('token'),
text: this.content,
imgList: list ? list : [],
originalList: originalList ? originalList : [],
comments: [],
like: [],
date: new Date(),
address: this.address
},
method: 'POST'
});
res = res[1].data;
if (res.status) {
uni.showToast({
title: res.msg,
duration: 1000
});
setTimeout(() => {
uni.navigateBack({
delta: 1
});
if (this.path == 'dynamic') {
uni.$emit('refresh'); //刷新数据
uni.navigateTo({
url: '../dynamic/dynamic'
});
} else {
uni.$emit('refreshSocial'); //刷新数据
uni.$emit('refresh'); //刷新数据
uni.navigateTo({
url: '../individual/individual?id=' + this.getUser._id
});
}
}, 1300);
} else {
uni.showToast({
title: res.msg,
icon: 'none',
duration: 1000
});
}
}
}
};
</script>
删除动态
思路:删除动态前端需要传递token和date,只需要这两个参数就可找到对应的动态并执行删除操作,需要执行删除的表有两个:个人动态表和朋友圈动态表,其中朋友圈动态表包括用户自身和好友的
后端
javascript
// 删除动态
exports.deleteDynamic = async data => {
let { token, date } = data
let tokenRes = verifyToken(token)
let tokenDynamic = await Dynamic.findOne({ userID: tokenRes.id })
// 从动态表中删除该动态
let logList = tokenDynamic.logList
let index = logList.findIndex(item => {
return new Date(item.date).getTime() == new Date(date).getTime()
})
// 删除图片
let imgList = logList[index].imgList
let originalList = logList[index].originalList
let newImgList = [...imgList, ...originalList]
let basePath = "http://localhost:3000"
// let basePath = "http://yemengs.cn"
if (newImgList.length > 0) {
newImgList.forEach(element => {
let path = element.replace(basePath, "public")
if (fs.existsSync(path)) {
fs.unlinkSync(path);
}
console.log(path)
});
}
logList.splice(index, 1)
// 更新个人动态表
let res = await Dynamic.updateOne({ userID: tokenRes.id }, { $set: { logList } })
let friends = await Friend.findOne({ userID: tokenRes.id })
if (friends) {
let friend_list = friends.friend_list
if (friend_list.length > 0) {
// 将动态从自己和好友的朋友圈动态表中移除
let result = await Promise.all(friend_list.map(async item => {
let social = await Social.findOne({ userID: item.user })
let dynamicList = social.dynamicList
let index = dynamicList.findIndex(item2 => {
return item2.friend.toString() == tokenRes.id.toString() && new Date(item2.date).getTime() == new Date(date).getTime()
})
if (index > -1) {
dynamicList.splice(index, 1)
let updateSocial = await Social.updateOne({ userID: item.user }, { $set: { dynamicList } })
}
}))
}
}
if (res.nModified > 0) {
return { status: 1, msg: "删除成功" }
} else {
return { status: 0, msg: "删除失败" }
}
}
前端
javascript
// 删除动态
deleteLog(date) {
uni.showModal({
title: '提示',
content: '确定删除该动态吗',
success: async res => {
if (res.confirm) {
let res = await request({
url: '/dynamic/delete',
data: {
date,
token: uni.getStorageSync('token')
},
method: 'POST'
});
res = res[1].data;
if (res.status == 1) {
// 本地删除数据 ,为了不刷新数据造成scroll-view跳转的情况
let index = this.social.findIndex(item => {
return new Date(item.date).getTime() == new Date(date).getTime();
});
if (index > -1) {
this.social.splice(index, 1);
}
uni.$emit('refresh'); //刷新数据
}
} else if (res.cancel) {
}
}
});
},
评论与点赞
效果图
评论
后端
javascript
// 评论
exports.comment = async data => {
let { token, id, date, toUser, toName, content } = data
let tokenRes = verifyToken(token)
let user = await User.findById(tokenRes.id)
let idDynamic = await Dynamic.findOne({ userID: id })
let logList = idDynamic.logList
let index = logList.findIndex(item => {
return new Date(item.date).getTime() == new Date(date).getTime()
})
logList[index].comments.unshift({
fromUser: user._id,
fromName: user.name,
toUser,
toName,
content,
})
// 更新动态评论
let res = await Dynamic.updateOne({ userID: id }, { $set: { logList } })
if (res.nModified == 1) {
// 添加到通知表
let arr = []
if (toUser == id && user._id != id) { //当toUser和id相同时并且fromUser和id不同时,只需通知一个人(动态的主人)
// console.log("第一种情况")
arr = [{ to: id, id: tokenRes.id, name: user.name }]
} else if (user._id == id && toUser != id) { //当fromUser和id相同而toUser不同时,只需通知一个人(toUser)
// console.log("第二种情况", toName)
arr = [{ to: toUser, id: tokenRes.id, name: user.name }]
} else if (toUser == id && user._id == id) {
// console.log("第三种情况")
} else { //不同时,则需要通知两个人
// console.log("第四种情况")
arr = [{ to: id, id: tokenRes.id, name: user.name }, { to: toUser, id: tokenRes.id, name: user.name }]
}
let pro_result = await Promise.all(arr.map((async item => {
let comNotify = await ComNotify.findOne({ userID: item.to })
let result = null
if (comNotify) {
let obj = {
fromUser: item.id,
fromName: item.name,
toUser,
toName,
date: new Date(),
logDate: date,
type: "comment",
content,
text: logList[index].text,
unRead: false,
id
}
let notify_list = comNotify.notify_list
notify_list.unshift(obj)
result = await ComNotify.updateOne({ userID: id }, { $set: { notify_list } })
} else {
let notify_list = [{
fromUser: item.id,
fromName: item.name,
toUser,
toName,
date: new Date(),
logDate: date,
type: "comment",
content,
text: logList[index].text,
unRead: false,
id
}]
result = await ComNotify.create({ userID: item.to, notify_list })
}
if (result.userID || result.nModified > 0) {
let socketUser = await userSocket.findOne({ userId: item.to })
// 已经将socket.io对象挂载到global对象上,所以可以直接调用
io.to(socketUser.socketId).emit("getComNotify")
}
})))
return { status: 1, msg: "评论成功" }
} else {
return { status: 0, msg: "评论失败" }
}
}
前端
javascript
// 评论
async comment(content) {
this.showComment = false; // 隐藏评论组件
// 本地添加数据(减少请求)
this.social.map(item => {
if (item.id == this.id && new Date(item.log.date).getTime() == new Date(this.date).getTime()) {
item.log.comments.unshift({
fromUser: this.getUser._id,
fromName: this.getUser.name,
toUser: this.toUser,
toName: this.toName,
content
});
}
return item;
});
// 向服务器发送数据
let res = await request({
url: '/dynamic/comment',
data: {
token: uni.getStorageSync('token'),
id: this.id,
date: this.date,
toUser: this.toUser,
toName: this.toName,
content
},
method: 'POST'
});
if(res[1].data.status){
uni.showToast({
title: "评论成功",
duration:1000
});
}else{
uni.showToast({
title: "发表评论失败",
icon:"none",
duration:1000
});
}
},
点赞
后端
javascript
// 点赞
exports.giveALike = async data => {
// id:动态所属用户的id
let { token, id, date } = data
let tokenRes = verifyToken(token)
let user = await User.findById(tokenRes.id)
let idDynamic = await Dynamic.findOne({ userID: id })
let logList = idDynamic.logList
let index = logList.findIndex(item => {
return new Date(item.date).getTime() == new Date(date).getTime()
})
// 在点赞的数组中查找该用户是否已经点过赞
let likeIndex = logList[index].like.findIndex(item2 => {
return item2.id.toString() == user._id.toString()
})
let res = null
if (likeIndex > -1) { // 已经点过赞则执行取消点赞操作
logList[index].like.splice(likeIndex, 1)
res = await Dynamic.updateOne({ userID: id }, { $set: { logList } })
} else { // 没有则执行点赞操作
logList[index].like.unshift({ id: user._id, nickName: null })
res = await Dynamic.updateOne({ userID: id }, { $set: { logList } })
if (res.nModified > 0) {
if (tokenRes.id != id) {
// 添加到通知表
let comNotify = await ComNotify.findOne({ userID: id })
let result = null
if (comNotify) {
let obj = {
fromUser: tokenRes.id,
fromName: user.name,
toUser: null,
toName: null,
date: new Date(),
logDate: date,
type: "like",
content: null,
text: logList[index].text,
unRead: false,
id
}
let notify_list = comNotify.notify_list
notify_list.unshift(obj)
result = await ComNotify.updateOne({ userID: id }, { $set: { notify_list } })
} else {
let notify_list = [{
fromUser: tokenRes.id,
fromName: user.name,
toUser: null,
toName: null,
date: new Date(),
logDate: date,
type: "like",
content: null,
text: logList[index].text,
unRead: false,
id
}]
result = await ComNotify.create({ userID: id, notify_list })
}
if (result.userID || result.nModified > 0) { // 点赞成功,通知用户
let socketUser = await userSocket.findOne({ userId: id })
global.io.to(socketUser.socketId).emit("getComNotify")
}
}
}
}
return {}
}
前端
javascript
// 点赞触发的事件
async giveALike(id, date) {
// index 是 点赞和评论组件的索引值
this.index = -1;
// 本地添加数据
this.social.map(item => {
if (item.id == id && new Date(item.log.date).getTime() == new Date(date).getTime()) {
// 判断点赞数组中是否存在该用户
let index = item.log.like.findIndex(item => {
//通过或来判断,因为存到数据库是存的id,本地存的是name
return item.id == this.getUser._id;
});
if (index == -1) {
//如果不存在则添加
item.log.like.unshift({ id: this.getUser._id, nickName: this.getUser.name });
} else {
//存在则删除
item.log.like.splice(index, 1);
}
}
return item;
});
// 向服务器发送数据
let res = await request({
url: '/dynamic/like',
data: {
token: uni.getStorageSync('token'),
id, // 动态所属用户的id
date
},
method: 'POST'
});
},
获取个人动态
获取个人动态主要分为两种情况:第一种就是用户自己获取自己的个人动态,第二种是用户查看朋友的个人动态。第一种情况是可以直接获取的,第二种情况就要进行一些条件判断:1.动态点赞的用户是否跟其是朋友关系,不是则进行限制(非好友关系不可见)。2. 评论的用户跟其是否是朋友关系,不是则进行限制操作(非好友关系不可见)
后端
javascript
// 获取个人动态
exports.acquire = async data => {
let { token, id, page, limit } = data
let tokenRes = verifyToken(token)
let tokenUser = await User.findById(tokenRes.id)
let obj = {}
let result = await Friend.findOne({ userID: tokenRes.id }).populate("friend_list.user", "avatars")
let friend_list = []
if (result) {
friend_list = result.friend_list
}
if (tokenRes.id == id) {//是用户自己
obj.avatars = tokenUser.avatars
obj.id = tokenUser._id
obj.nickName = tokenUser.name
} else {
let index = friend_list.findIndex(item => { //在好友表中查找并获取好友信息(昵称和用户头像)
return item.user._id == id
})
if (index > -1) { //是好友
let friend = friend_list[index]
obj.avatars = friend.user.avatars
obj.id = friend.user._id
obj.nickName = friend.nickName
} else { //不是好友
let idUser = await User.findById(id)
obj.avatars = idUser.avatars
obj.id = id
obj.nickName = idUser.name
}
}
let res = await Dynamic.findOne({ userID: id })
if (res) {
let logList = res.logList
let count = logList.length
let maxPage = Math.ceil(count / limit)
if (page > maxPage) {
return { logList: [], avatars: obj.avatars, name: obj.nickName }
}
let skip = (page - 1) * limit
let spliceLogList = logList.splice(skip, limit)
let oldLogList = []
let mapRes = await Promise.all(spliceLogList.map(async item => {
let like = [] //存储点赞里是好友关系的用户
// 遍历点赞,在朋友中寻找,不是好友关系则屏蔽
item.like.map(item4 => {
if (item4.id == tokenRes.id) { //是token用户自身
item4.nickName = tokenUser.name
like.push(item4)
} else { //不是token用户自身则从好友列表中查找
friend_list.map(item5 => {
if (item5.user._id.toString() == item4.id.toString()) {
item4.nickName = item5.nickName
like.push(item4)
}
return item5
})
}
return item4
})
item.like = like
let comCount = item.comments.length //评论的个数
let comments = [] //存储评论里是好友关系的用户
// 遍历评论,在朋友中寻找,不是好友关系则屏蔽
for (let i = 0; i < item.comments.length; i++) {
if (i > 4) {
break
}
let item6 = item.comments[i]
// 下面两个变量是为了确定回复者和被回复者与token用户是否是好友关系
let isFriends1 = false
let isFriends2 = false
if (item6.fromUser == tokenRes.id) { //是token用户自身
item6.fromName = tokenUser.name
isFriends1 = true
}
if (item6.toUser == tokenRes.id) { //是token用户自身
item6.toUser == tokenUser.name
isFriends2 = true
}
if (isFriends1 != true || isFriends2 != true) {
friend_list.map(item7 => { //判断回复者的好友关系
if (item7.user._id.toString() == item6.fromUser.toString()) {
item6.fromName = item7.nickName
isFriends1 = true
}
if (item7.user._id.toString() == item6.toUser.toString()) {
item6.toName = item7.nickName
isFriends2 = true
}
return item7
})
}
if (isFriends1 && isFriends2) {//当两个人与token用户都是好友关系时才显示
comments.push(item6)
}
}
// item.comments.map(item6 => {
// return item6
// })
item.comments = comments
let newObj = {
comCount,
avatars: obj.avatars,
id: obj.id,
nickName: obj.nickName,
text: item.text,
imgList: item.imgList,
originalList: item.originalList,
comments: item.comments,
like: item.like,
date: item.date,
address: item.address,
_id: item._id,
}
// let newObj = Object.assign(obj, item)
// console.log(newObj)
oldLogList.push(newObj)
// console.log(newDynamicList)
return item
}))
// 排序(因为以上map遍历以及内嵌了await 导致执行顺序会乱)
function listSort(a, b) {
return new Date(b.date).getTime() - new Date(a.date).getTime()
}
let newLogList = oldLogList.sort(listSort)
return { logList: newLogList, avatars: obj.avatars, name: obj.nickName }
} else {
return { logList: [], avatars: obj.avatars, name: obj.nickName }
}
}
前端
javascript
// 获取个人动态
async acquire() {
let res = await request({
url: '/dynamic/acquire',
data: {
token: uni.getStorageSync('token'),
id: this.id,
limit: this.limit,
page: this.page
}
});
let logList = res[1].data.logList;
this.avatars = res[1].data.avatars;
this.name = res[1].data.name;
if (logList.length > 0) {
this.social.push(...logList);
}
},
获取动态详情
获取动态详情的情况跟获取个人动态的情况是相似的都需要判断是谁在获取动态,不是动态所属的人查看则需要进行对应的条件判断
后端
javascript
// 获取动态详情
exports.singleDynamic = async data => {
let { token, id, date } = data
let tokenRes = verifyToken(token)
let tokenUser = await User.findById(tokenRes.id)
let obj = {}
let result = await Friend.findOne({ userID: tokenRes.id }).populate("friend_list.user", "avatars")
let friend_list = []
if (result) {
friend_list = result.friend_list
}
if (tokenRes.id == id) {//是用户自己
obj.avatars = tokenUser.avatars
obj.id = tokenUser._id
obj.nickName = tokenUser.name
} else {
let index = friend_list.findIndex(item => { //在好友表中查找并获取好友信息(昵称和用户头像)
return item.user._id == id
})
if (index > -1) { //是好友
let friend = friend_list[index]
obj.avatars = friend.user.avatars
obj.id = friend.user._id
obj.nickName = friend.nickName
} else {
let idUser = await User.findById(id)
obj.avatars = idUser.avatars
obj.id = id
obj.nickName = idUser.name
}
}
let res = await Dynamic.findOne({ userID: id })
let logIndex = res.logList.findIndex(item => {
return new Date(item.date).getTime() == new Date(date).getTime()
})
let log = res.logList[logIndex]
let like = [] //存储点赞里是好友关系的用户
// 遍历点赞,在朋友中寻找,不是好友关系则屏蔽
log.like.map(item4 => {
if (item4.id == tokenRes.id) { //是token用户自身
item4.nickName = tokenUser.name
like.push(item4)
} else { //不是token用户自身则从好友列表中查找
friend_list.map(item5 => {
if (item5.user._id.toString() == item4.id.toString()) {
item4.nickName = item5.nickName
like.push(item4)
}
return item5
})
}
return item4
})
log.like = like
let comments = [] //存储评论里是好友关系的用户
// 遍历评论,在朋友中寻找,不是好友关系则屏蔽
log.comments.map(item6 => {
// 下面两个变量是为了确定回复者和被回复者与token用户是否是好友关系
let isFriends1 = false
let isFriends2 = false
if (item6.fromUser == tokenRes.id) { //是token用户自身
item6.fromName = tokenUser.name
isFriends1 = true
}
if (item6.toUser == tokenRes.id) { //是token用户自身
item6.toUser == tokenUser.name
isFriends2 = true
}
if (isFriends1 != true || isFriends2 != true) {
friend_list.map(item7 => { //判断回复者的好友关系
if (item7.user._id.toString() == item6.fromUser.toString()) {
item6.fromName = item7.nickName
isFriends1 = true
}
if (item7.user._id.toString() == item6.toUser.toString()) {
item6.toName = item7.nickName
isFriends2 = true
}
return item7
})
}
if (isFriends1 && isFriends2) {//当两个人与token用户都是好友关系时才显示
comments.push(item6)
}
return item6
})
log.comments = comments
let newObj = {
avatars: obj.avatars,
id: obj.id,
nickName: obj.nickName,
text: log.text,
imgList: log.imgList,
originalList: log.originalList,
comments: log.comments,
like: log.like,
date: log.date,
address: log.address,
_id: log._id,
}
return newObj
}
前端
javascript
// 获取动态详情
async getSingleDynamic(id, date) {
let res = await request({
url: '/dynamic/single',
data: {
token: uni.getStorageSync('token'),
id,
date
}
});
this.log = res[1].data;
}
获取朋友圈
效果图
后端
朋友圈动态表只存储用户的id和date,所以在获取的时候必须要根据id和date到对应用户的个人动态表里面查找
javascript
const Social = require("../model/socialModel") // 朋友圈动态表
const Friend = require("../model/friendModel") // 好友表
const Dynamic = require("../model/dynamicModel") // 个人动态表
const User = require("../model/userModel") // 用户表
const { verifyToken } = require("../tool/token")
// 获取朋友圈
exports.acquire = async data => {
let { token, page, limit } = data
let tokenRes = verifyToken(token)
let social = await Social.findOne({ userID: tokenRes.id })
let tokenUser = await User.findById(tokenRes.id)
if (social) {
let dynamicList = social.dynamicList
if (dynamicList.length == 0) { //
return {
dynamicList
}
} else {
let count = dynamicList.length
let maxPage = Math.ceil(count / limit)
if (page > maxPage) {
return []
}
let skip = (page - 1) * limit
let spliceDynamicList = dynamicList.splice(skip, limit)
let result = await Friend.findOne({ userID: tokenRes.id }).populate("friend_list.user", "avatars")
if (result) {
let friend_list = result.friend_list
let oldDynamicList = []
let mapRes = await Promise.all(spliceDynamicList.map(async item => {
let obj = {}
if (item.friend == tokenRes.id) {//是用户自己
obj.avatars = tokenUser.avatars
obj.id = tokenUser._id
obj.nickName = tokenUser.name
} else { //是好友
let index = friend_list.findIndex(item2 => { //在好友表中查找并获取好友信息(昵称和用户头像)
return item2.user._id == item.friend
})
if (index > -1) {
let friend = friend_list[index]
obj.avatars = friend.user.avatars
obj.id = friend.user._id
obj.nickName = friend.nickName
}
}
let f_dynamic = await Dynamic.findOne({ userID: item.friend })
let logIndex = f_dynamic.logList.findIndex(item3 => { //查找动态
// 根据时间查找
return new Date(item3.date).getTime() == new Date(item.date).getTime()
})
if (logIndex > -1) {
let log = f_dynamic.logList[logIndex]
let like = [] //存储点赞里是好友关系的用户
// 遍历点赞,在朋友中寻找,不是好友关系则屏蔽
log.like.map(item4 => {
if (item4.id == tokenRes.id) { //是token用户自身
item4.nickName = tokenUser.name
like.push(item4)
} else { //不是token用户自身则从好友列表中查找
friend_list.map(item5 => {
if (item5.user._id.toString() == item4.id.toString()) {
item4.nickName = item5.nickName
like.push(item4)
}
return item5
})
}
return item4
})
log.like = like
let comments = [] //存储评论里是好友关系的用户
// 遍历评论,在朋友中寻找,不是好友关系则屏蔽
for (let i = 0; i < log.comments.length; i++) {
let item6 = log.comments[i]
// 下面两个变量是为了确定回复者和被回复者与token用户是否是好友关系
let isFriends1 = false
let isFriends2 = false
if (item6.fromUser == tokenRes.id) { //是token用户自身
item6.fromName = tokenUser.name
isFriends1 = true
}
if (item6.toUser == tokenRes.id) { //是token用户自身
item6.toUser == tokenUser.name
isFriends2 = true
}
if (isFriends1 != true || isFriends2 != true) {
friend_list.map(item7 => { //判断回复者的好友关系
if (item7.user._id.toString() == item6.fromUser.toString()) {
item6.fromName = item7.nickName
isFriends1 = true
}
if (item7.user._id.toString() == item6.toUser.toString()) {
item6.toName = item7.nickName
isFriends2 = true
}
return item7
})
}
if (isFriends1 && isFriends2) {//当两个人与token用户都是好友关系时才显示
comments.push(item6)
}
}
obj.comCount = comments.length //评论的条数
log.comments = comments.splice(0, 5) //截取返回
obj.log = log
oldDynamicList.push(obj)
}
return item
}))
// 排序(因为以上map遍历以及内嵌了await 导致执行顺序会乱)
function listSort(a, b) {
return new Date(b.log.date).getTime() - new Date(a.log.date).getTime()
}
let newDynamicList = oldDynamicList.sort(listSort)
return newDynamicList
} else {
return {
dynamicList
}
}
}
} else { //social不存在直接返回
return {
dynamicList: []
}
}
}
个人空间
###效果图
留言
表设计
留言表
javascript
const mongoose = require("mongoose")
// 留言表
//一条留言只能空间所属用户和留言用户可以回复信息 空间所属用户的留言只有空间所属用户可以回复
const messageSchema = new mongoose.Schema({
userID: String, //用户id
messages: [
{ "user": { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, content: String, "nickName": String, children: Array, date: Date }
],
})
module.exports = mongoose.model("message", messageSchema);
留言通知表
javascript
const mongoose = require("mongoose")
// 留言通知表
const messNotifySchema = new mongoose.Schema({
userID: String, //用户id
notify_list: [],
// notify_list 传入的对象格式如下
// { "user": id, content: String, "nickName": String, type: String, date: Date ,unRead:true||false}
//type:"message"||"reply" :留言 | 回复
})
module.exports = mongoose.model("messNotify", messNotifySchema);
关于留言的相关操作
发布留言
后端
javascript
// 发布留言
exports.published = async data => {
// id是所属空间的用户(对谁留言)
let { token, id, content } = data
let tokenRes = verifyToken(token)
let idMessage = await Message.findOne({ userID: id })
let result = null
if (idMessage) {//留言表存在
let messages = idMessage.messages
messages.unshift({
user: tokenRes.id,
content,
nickName: null,
children: [],
date: new Date()
})
result = await Message.updateOne({ userID: id }, { $set: { messages } })
} else {//留言表不存在
result = await Message.create({
userID: id,
messages: [{
user: tokenRes.id,
content,
nickName: null,
children: [],
date: new Date()
}]
})
}
if (result.userID || result.nModified) { // 留言发表成功
if (tokenRes.id !== id) {
// 成功发表留言后将该留言存储在留言通知表中
let res = await MessNotify.findOne({ userID: id })
let resNotify = null
if (res) { //留言通知表存在
let notify_list = res.notify_list
notify_list.unshift({
user: tokenRes.id,
content,
nickName: null,
type: "message",
date: new Date(),
unRead: false
})
resNotify = await MessNotify.updateOne({ userID: id }, { $set: { notify_list } })
} else {//留言通知表不存在
resNotify = await MessNotify.create({
userID: id,
notify_list: [{
user: tokenRes.id,
content,
nickName: null,
type: "message",
date: new Date(),
unRead: false
}]
})
}
if (resNotify.userID || resNotify.nModified > 0) {
socketUser = await userSocket.findOne({ userId: id })
io.to(socketUser.socketId).emit("message")
}
}
return { status: 1, msg: "发表成功" }
} else {
return { stats: 0, msg: "发表失败" }
}
}
前端
javascript
async message(){
let res = await request({
//新留言
url: '/message/published',
data: {
token: uni.getStorageSync('token'),
id: this.id,
content
},
method: 'POST'
});
if (res[1].data.status) {
// 本地添加数据,避免刷新页面(减少请求)
this.messages.unshift({
user: {
_id:this.getUser._id,
avatars:this.getUser.avatars
},
content,
nickName: this.getUser.name,
children: [],
date: new Date()
});
}
}
回复留言
后端
javascript
// 回复留言
exports.reply = async data => {
let { token, id, messageID, message, date, content } = data
let tokenRes = verifyToken(token)
let idMessage = await Message.findOne({ userID: id })
let messages = idMessage.messages
let index = messages.findIndex(item => {
return messageID.toString() == item.user.toString() && new Date(item.date).getTime() == new Date(date).getTime()
})
messages[index].children.unshift({
user: tokenRes.id,
date: new Date(),
content
})
let result = await Message.updateOne({ userID: id }, { $set: { messages } })
if (result.nModified > 0) {
let toUser = null //一条留言只能空间所属用户和留言用户可以回复信息
if (tokenRes.id == messageID) { //当token用户和留言用户(messageID)是同一个人时,则toUser就是id(空间用户)
toUser = id
} else {//当token用户和留言用户(messageID)不是同一个人时,则toUser就是messageID(留言用户)
toUser = messageID
}
// 成功发表留言后将该留言存储在留言通知表中
let res = await MessNotify.findOne({ userID: toUser })
let resNotify = null
if (res) {
let notify_list = res.notify_list
notify_list.unshift({
user: tokenRes.id,
content,
nickName: null,
type: "reply",
date: new Date(),
message,
unRead: false
})
resNotify = await MessNotify.updateOne({ userID: toUser }, { $set: { notify_list } })
} else {
resNotify = await MessNotify.create({
userID: toUser,
notify_list: [{
user: tokenRes.id,
content,
nickName: null,
type: "reply",
date: new Date(),
message,
unRead: false
}]
})
}
if (resNotify.userID || resNotify.nModified > 0) {
socketUser = await userSocket.findOne({ userId: id })
io.to(socketUser.socketId).emit("message")
}
return { status: 1, msg: "回复成功" }
}
}
前端
javascript
async reply(){
//回复
let res = await request({
url: '/message/reply',
data: {
token: uni.getStorageSync('token'),
id: this.id,
messageID: this.params.messageID,
date: this.params.date,
content,
message: this.params.message
},
method: 'POST'
});
if (res[1].data.status) {
// 本地添加数据,避免刷新页面
let index = this.messages.findIndex(item => {
return this.params.messageID.toString() == item.user._id.toString() && new Date(item.date).getTime() == new Date(this.params.date).getTime();
});
this.messages[index].children.unshift({
user: {
_id:this.getUser._id,
avatars:this.getUser.avatars
},
name: this.getUser.name,
date: new Date(),
content
});
}
}
获取留言
原则:非好友关系,留言不可见
后端
javascript
// 获取留言
exports.acquire = async data => {
let { token, id, page, limit } = data
let tokenRes = verifyToken(token)
let user = await User.findById(tokenRes.id)
let idMessage = await Message.findOne({ userID: id }).populate("messages.user", ["name", "avatars"])
if (idMessage) {
let messages = idMessage.messages
if (messages.length == 0) {
return {
messages
}
} else {
let mount = messages.length
let maxPage = Math.ceil(mount / limit)
if (page > maxPage) {
return {
messages: [],
}
} else {
let skip = (page - 1) * limit
let oldMessage = messages.splice(skip, limit)
let tokenFriend = await Friend.findOne({ userID: tokenRes.id })
let friend_list = tokenFriend.friend_list
let newMessage = []
oldMessage.map(async item => {
let obj = {
user: item.user,
content: item.content,
_id: item._id,
date: item.date
}
obj.children = []
if (item.user._id.toString() == tokenRes.id) { // 留言是token用户发表的
obj.nickName = user.name
item.children.map(item3 => {//遍历回复
if (item3.user.toString() == tokenRes.id.toString()) {
obj.children.push({
user: item3.user,
name: user.name,
content: item3.content
})
} else {
let index = friend_list.findIndex(item5 => {
return item3.user.toString() == item5.user.toString()
})
obj.children.push({
user: item3.user,
name: friend_list[index].nickName,
content: item3.content
})
}
})
newMessage.push(obj)
} else { // 留言不是token用户发表的
let index = friend_list.findIndex(item2 => {
return item2.user.toString() == item.user._id.toString()
})
if (index > -1) { // 能在好友表中找到,则添加(非好友关系,留言不可见)
obj.nickName = friend_list[index].nickName
item.children.map(item3 => { //遍历回复
if (item3.user.toString() == tokenRes.id.toString()) {
obj.children.push({
user: item3.user,
name: user.name,
content: item3.content
})
} else {
let index = friend_list.findIndex(item5 => {
return item3.user.toString() == item5.user.toString()
})
obj.children.push({
user: item3.user,
name: friend_list[index].nickName,
content: item3.content
})
}
})
newMessage.push(obj)
}
}
})
return {
messages: newMessage,
}
}
}
} else {
return {
messages: []
}
}
}
前端
javascript
// 获取留言
async acquireMessage() {
let res = await request({
url: '/message/acquire',
data: {
token: uni.getStorageSync('token'),
id: this.id,
page: this.mess_page,
limit: this.mess_limit
}
});
this.messages = res[1].data.messages;
},
访客
访客表设计
javascript
const mongoose = require("mongoose")
// 访问表
const visitorsSchema = new mongoose.Schema({
userID: String, //用户id
count: Number,
visitors: [
{ "user": { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, "nickName": String, date: String, unRead: Boolean }
],
// unRead : 用户是否已读该条访客记录
})
module.exports = mongoose.model("visitor", visitorsSchema);
后端
javascript
const Visitor = require("../model/visitorsModel") // 访客表
const Friend = require("../model/friendModel")
const User = require("../model/userModel")
const userSocket = require("../model/userSocketModel")
const { verifyToken } = require("../tool/token")
// 记录访客
exports.record = async data => {
let { token, id } = data
let tokenRes = verifyToken(token)
let idVisitors = await Visitor.findOne({ userID: id })
let tokenUser = await User.findById(tokenRes.id)
let result = null
if (idVisitors) {
let visitors = idVisitors.visitors
let count = idVisitors.count
let index = visitors.findIndex(item => {
return item.user.toString() == tokenRes.id.toString()
})
if (index > -1) {
// 时间差
let timeDifference = (new Date().getTime() - new Date(visitors[index].date).getTime()) / 1000 / 60
// 同一个用户访问时间差大于10分钟时才记录
let judge = timeDifference > 10
if (!judge) {
return {}
}
}
count++
visitors.unshift({
user: tokenRes.id,
nickName: tokenUser.name,
date: new Date(),
unRead: false
})
result = await Visitor.updateOne({ userID: id }, { $set: { count, visitors } })
} else {
result = await Visitor.create({
userID: id,
visitors: [{
user: tokenRes.id,
nickName: tokenUser.name,
date: new Date(),
unRead: false
}],
count: 1
})
}
if (result.nModified || result.userID) {
socketUser = await userSocket.findOne({ userId: id })
io.to(socketUser.socketId).emit("refreshVisitor")
return { status: 1, msg: "记录成功" }
} else {
return { status: 0, msg: "记录失败" }
}
}
// 获取访客
exports.acquire = async data => {
let { token, page, limit } = data
let tokenRes = verifyToken(token)
let idVisitors = await Visitor.findOne({ userID: tokenRes.id }).populate("visitors.user", ["avatars", "name"])
if (!idVisitors) { //不存在时
return {
newVisitor: 0,
visitors: [],
count: 0
}
} else {
let visitors = idVisitors.visitors
let newVisitor = 0
for (let i = 0; i < visitors.length; i++) {
if (visitors[i].unRead) {
break
} else {
newVisitor++
}
}
let mount = visitors.length
let maxPage = Math.ceil(mount / limit)
if (page > maxPage) {
return {
visitors: [],
count: idVisitors.count
}
} else {
let skip = (page - 1) * limit
let oldVisitors = visitors.splice(skip, limit)
let tokenFriend = await Friend.findOne({ userID: tokenRes.id })
let newVisitors = []
oldVisitors.map(async item => {
let index = tokenFriend.friend_list.findIndex(item2 => {
return item2.user.toString() == item.user._id.toString()
})
if (index > -1) {
newVisitors.push({
user: item.user,
nickName: tokenFriend.friend_list[index].nickName,
date: item.date,
isFriend: true,
unRead: item.unRead
})
} else {
newVisitors.push({
user: item.user,
nickName: item.user.name,
date: item.date,
isFriend: false,
unRead: item.unRead
})
}
})
return {
newVisitor,
visitors: newVisitors,
count: idVisitors.count
}
}
}
}
// 删除访客
exports.remove = async data => {
let { token, id, date } = data
let tokenRes = verifyToken(token)
let tokenVisitors = await Visitor.findOne({ userID: tokenRes.id })
let visitors = tokenVisitors.visitors
let index = visitors.findIndex(item => {
return item.user == id && new Date(item.date).getTime() == new Date(date).getTime()
})
visitors.splice(index, 1)
let res = await Visitor.updateOne({ userID: tokenRes.id }, { $set: { visitors } })
if (res.nModified > 0) {
return { status: 1, msg: "删除成功" }
} else {
return { status: 0, msg: "删除失败" }
}
}
// 更新新访客
exports.update = async data => {
let { token } = data
let tokenRes = verifyToken(token)
let tokenVisitors = await Visitor.findOne({ userID: tokenRes.id })
let visitors = tokenVisitors.visitors
for (let i = 0; i < visitors.length; i++) {
if (visitors[i].unRead) {
break
} else {
visitors[i].unRead = true
}
}
let result = await Visitor.updateOne({ userID: tokenRes.id }, { $set: { visitors } })
if (result.nModified > 0) {
return { status: 1, msg: "更新成功" }
} else {
return { status: 0, msg: "更新失败" }
}
}