Memory 缘忆交友社区-开发文档
本文最后更新于:7 个月前
介绍
- 项目概述:对项目的整体简介,包括项目的目的、背景和主要功能。
- 目标受众:定义项目的受众群体,如用户、开发人员、测试人员等。
技术栈
前端技术栈
Ant Design Vue
@vue/cli 脚手架
Vant UI 组件库
TypeScript
Axios 请求库
后端
- Java SpringBoot 2.7.x 框架
- MySQL 数据库
- MyBatis-Plus
- MyBatis X 自动生成
- Redis 缓存(Spring Data Redis 等多种实现方式)
- Redisson 分布式锁
- Easy Excel 数据导入
- Spring Scheduler 定时任务
- Swagger + Knife4j 接口文档
- Gson:JSON 序列化库
- 相似度匹配算法
数据库:列出用于存储用户数据、队伍数据和博客数据的数据库系统,如MySQL、MongoDB等。
通信技术:列出用于实现即时聊天功能的通信技术,如WebSocket、Socket.io等。
功能模块
- 用户认证模块
- 用户注册功能:包括用户的基本信息和账户创建。
- 用户登录功能:验证用户提供的用户名和密码。
- 用户权限管理:根据用户角色和权限限制用户的操作范围。
- 队伍管理模块
- 创建队伍功能:允许用户创建新的队伍,并添加队伍的相关信息。
- 加入队伍功能:允许用户查找并加入现有的队伍。
- 好友管理模块
- 查找好友功能:用户可以查找其他用户并添加为好友。
- 好友聊天功能:提供好友之间的即时文本聊天功能。
- 队内聊天模块
- 队伍内部聊天功能:允许队伍成员之间进行实时文本交流。
- 用户推荐模块
- 标签匹配功能:根据用户的标签来为用户推荐与他相似度最高的其他用户。
- 博客模块(日后增加)
- 发布博客功能:用户可以发布自己的博客。
- 点赞、收藏功能:用户可以对博客进行点赞和收藏。
- 博客评论功能:用户可以对博客进行评论。
用户模块
用户登录/注册
查看用户信息
添加好友
- 添加好友后,数据库应该同时保存两条记录:我是你的好友,你是我的好友
查看在线好友信息
- 查看在线好友列表
- 列表展示在线好友信息:昵称、头像、是否在线
- 如果不在线,显示最近在线时间(或显示多久前在线:X天前、X小时前、X个月前)
- 可点击私聊
- 提供修改好友备注功能
好友私聊
如何实现好友间私聊?
这其中要思考的场景有:发送消息、接收消息、查看消息
在前后端搭建好socket环境、前端能够发送消息,服务端能正常转发消息到指定用户id的前提下,我们考虑如何实现好友间私聊
即:用户间通信
- 用户发送消息(message),消息中携带的内容:发送者id,目标id,消息内容,发送时间
- 服务器收到消息,按以发送者id为key值,将 message 存储至Redis中,再转发 message 到目标用户
- 目标用户在线时,可以立刻获取到发来的message,如若离线时,则需查看Redis中的存放的消息
本来想着服务器如何给指定用户sid转发消息,好像不太好实现
在客户端尝试连接服务端socket,并成功连接后,服务端的 webSocketSet 存放了 在线连接用户的信息
按现在的情况,服务端只能校验该消息是哪个用户所发的,即校验 webSocketSet中存放的sid,能否在已连接用户中找到发送消息的用户,并选择转发消息
那只好是服务器同意转发至客户端,客户端根据sendId和receiveId选择是否展示
用户列表展示
- 用户不应该直接看到用户列表,用户可以添加好友,而添加好友的途径有三个:
- 通过每日推荐,相似度匹配算法,每周给用户推荐兴趣爱好相似的用户,可以添加为好友畅聊
- 通过博文,在博客中,用户可以自由发表博文,对博主感兴趣即可申请添加好友
- 在搜索界面,根据不同的搜索参数:userAccoount,tags(用户标签),搜索到条件匹配的匹配用户
队伍模块
队伍创建问题
- 用户如何创建队伍,应该有以下场景:
- 在大厅发起组队,用户即可在组队大厅搜索到组队队伍(目前只想到这个方式)
- 创建队伍后,队长可以进行的操作:
- 修改队伍信息
- 退出队伍、解散队伍
- 分享队伍,可以向好友发送组队邀请
队伍加入问题
私密队伍如何加入?目前想到的场景:
当用户创建私密队伍后,其他用户将无法直接在大厅中看到该队伍,并申请加入。私密队伍的加入场景可以设计如下:
邀请制加入:队长可以通过私信、邮件或其他方式向指定的用户发送邀请,邀请他们加入私密队伍。通过邀请链接或者邀请码,被邀请用户可以直接加入队伍。
好友推荐:队长可以在队伍详情中选择通过好友推荐的方式,将私密队伍信息分享给指定好友。被推荐用户可以看到私密队伍,并有选项选择是否加入。
特殊条件加入:队长可以设置一些特殊的条件,供用户满足后才能加入私密队伍。例如,要求用户达到一定的等级、完成特定任务或通过一定的测试等。
在这些场景下,你可以使用数据库来存储队伍的私密信息,例如邀请码或特殊条件。当用户申请加入私密队伍时,你可以将用户输入的邀请码或满足的特殊条件与数据库中存储的信息进行匹配验证。如果验证通过,用户就可以成功加入私密队伍。
- 申请加入队伍:
- 点击队伍标签,可展示更多信息(抽屉实现),可申请加入队伍
- 传入参数未:该队伍信息,后台处理申请请求,处理逻辑如下
- 该队伍未满员
- 该成员不再该队伍中
- 更新队伍已加入人数
- 更新user-team表关系,添加记录
队伍退出
队内私聊
队伍推荐
- 即队伍成员可分享该队伍的信息,加大队伍曝光率
博客模块
- 这个网站首页就应该是博客模块
- 创建博文表
博文展示
可筛选最新博文、最热门博文、
展示所有博文
- 博文卡片展示信息:标题、简介、标签、发布时间、点赞量、收藏量、评论量、作者头像
- 点击进入博文详情页:与博文卡片展示内容相同,另附博文详细内容
发表博文
- 用户可在博文编辑页,编辑博文:
- 标题、简介、标签、内容
- 点击发表,发表博文;点击存入草稿,存入草稿
点赞博文
收藏博文
评论博文
- 核心
界面设计
- 用于展示项目各个功能模块的界面设计,包括页面布局、交互流程和UI元素等。
- 主页
- 用户注册页:实现用户注册
- 用户登录页:实现用户登录
- 用户列表页:展示在线用户列表
- 用户信息页:展示用户信息
- 队伍列表页:展示队伍列表
- 队内聊天
- 好友聊天
- 博客列表页:展示已发布的博客
- 个人博客页:个人发布的博客展示
开发计划
- 列出项目开发的计划和里程碑,包括每个功能模块的开发时间和测试时间。
Day1
- 实现主页页面的简单开发 的 ✔ (2023/09/11晚)
- 在线用户列表页、队伍信息页 ✔
Day2
用户登录页
- 简单的实现了登录页的开发,用户需输入账户密码进行登录,这样作双向绑定:✔ (2023/09/10早)
1
2
3
4<a-form-item label="用户名" name="username"
:rules="[{ required: true, message: '请输入用户名' }]">
<a-input :placeholder="'请输入用户名'" v-model:value="userAccount"/>
</a-form-item>1
const userAccount = ref("");
- 登录成功之后跳转至主页
优化了主页页面显示
- 通过监听tab标签页的变化,发起相应的请求 ✔
- 用户登录后,展示头像、昵称; ✔
- 用户未登录,提示登录,可点击按钮跳转至登录页面 ✔
新增展示所有队伍功能,展示队伍列表 ✔
- 默认仅展示公开队伍,可开启展示加密队伍 ✔
- 前端监听开关状态,发送带参(isSecret)请求,是否需要获取加密队伍: ✔
1
2
3
4
5
6
7
8
9
10// 开关 队伍状态改变
const checked = ref<boolean>(false);
// 监听开关状态变化
watchEffect(() => {
if (checked.value) {
getTeamList(true);
} else {
getTeamList(false);
}
});- 后台校验逻辑
1
2
3
4
5
6if (!teamList.getIsSecret()) {
tqw.eq("status", TeamStatusEnum.PUBLIC.getValue());
} else {
tqw.eq("status", TeamStatusEnum.PUBLIC.getValue())
.or().eq("status", TeamStatusEnum.SECRET.getValue());
}队伍列表页面应该展示的信息有:
- 队伍名、队伍描述、队伍名片(牌面)✔
- 队伍已加入人数,可供用户判断是否满员(使用进度条实现),若满员,则显示已满员(爆红)✂
1
2
3
4<div>
<a-slider id="test" v-model:value="item.joinNum" :max="item.maxNum" disabled/>
<span>{{item.joinNum}}/{{item.maxNum}}</span>
</div>- 加标签,显示该用户是否已加入该队伍;
- 没加入,可加入
- 已加入,可进入队伍聊天
队伍卡片可点击进入,队伍信息展示页:(使用抽屉组件实现)
- Ant Design Vue (antdv.com)
- 队伍名、队伍描述 ✔
- 队伍已加入成员的头像排列(使用头像组件实现)、队伍已加入人数 ✔
问题解决
- 如何实现:点击队伍卡片,从右侧弹出抽屉,展示该队伍的详细信息
- 进行如下绑定(每个card绑定一个drawer)(2023/9/10晚)
1 |
|
1 |
|
Day3
- 实现用户间互相添加好友,可在“我的好友”中查看 (2023/09/11晚)
- 用户申请加入队伍,可在“我的队伍”中查看
- 用户创建队伍、退出队伍
- 在线用户列表中,排除当前用户:
1 |
|
- 添加用户信息标签展示
- 点击可展示更多信息(抽屉组件实现)
- 可申请添加为好友
- 申请添加好友时,请求参数为该好友的id,传回后端将登录用户和好友id,好友表新增一条记录
1
2
3
4
5
6
7
8
9
10// 申请添加好友
const addFriends = (friend: userInfo) => {
myAxios.post("/friends/add", {
id: friend.id
}).then((res) => {
message.success("成功发送好友申请")
}).catch(() => {
console.log("加入失败")
});
};
- 展示我的好友列表
1 |
|
1 |
|
1 |
|
- 添加好友信息逻辑:
- 传递参数:该好友信息、申请用户信息
- 该用户好友数未上限(目前没有上限,日后优化)
- 更新好友表,添加一条记录
- 优化队伍列表展示
- 目前还没有实现搜索队伍、搜索用户功能,展示队伍列表逻辑如下:
- 默认展示公开队伍,可选择展示加密队伍,大厅不支持用户直接加入私密队伍
- 显示已加入的队伍,排列顺序考前,不允许用户加入该队伍
- 加入加密队伍需要输入密码,加入公开队伍则无
- 目前还没有实现搜索队伍、搜索用户功能,展示队伍列表逻辑如下:
Day4
- 在博客系统开发前,可以考虑将后端代码分模块
1 |
|
优化登录/退出登录体验
登录后
- 后端,存储用户信息到session中,设置用户状态为在线(ONLINE),并返回该信息到前端
1
request.getSession().setAttribute(USER_LOGIN_STATE, safetyUser);
- 前端:前端存储登录用户信息到变量currentUser中,用于其他业务逻辑中的校验当前登录用户权限
1
const currentUser = ref({});
登出后
- 后端,删除session中的登录用户信息,设置用户状态为在线(OFFLINE)
1
request.getSession().removeAttribute(USER_LOGIN_STATE);
- 前端,currentUser置为空
- 好友在线列表,展示好友的在线状况
1 |
|
- 想解决同一个浏览器只能登录一个账号的问题,但这个问题解决不了的,看看AI的解释:
1 |
|
- 添加好友,同时添加两条记录:
1 |
|
- 完成加入队伍逻辑实现
- 展示我创建的队伍、我加入的队伍
- 要明确,我创建的队伍,team表中保存有队长userId字段,仅需在team表中,根据userId字段,直接查询出对应记录的team信息
- 我加入的队伍,在user_team表中,根据userId,查询到对应的teamId,在team表中,根据teamId获取到team信息
后台返回的队伍信息,应该包括队长信息,而不是队长id(待完成)
- 创建teamVO,封装参数userName,即队长昵称
- 我封装了一个方法,传入一个teamList,将所有的team转换为teamVO,最后返回:
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/**
* 转换teamList为teamVOList
*
* @param teamList teamList
* @return teamVOList
*/
public List<TeamVO> getTeamVOByTeam(List<Team> teamList) {
List<TeamVO> teamVOList = teamList.stream().map(team -> {
Long userId = team.getUserId();
String userName = userService.getById(userId).getUsername();
TeamVO teamVO = new TeamVO();
teamVO.setId(team.getId());
teamVO.setName(team.getName());
teamVO.setDescription(team.getDescription());
teamVO.setMaxNum(team.getMaxNum());
teamVO.setUserName(userName);
teamVO.setImgUrl(team.getImgUrl());
teamVO.setJoinNum(team.getJoinNum());
teamVO.setStatus(team.getStatus());
teamVO.setExpireTime(team.getExpireTime());
teamVO.setCreateTime(team.getCreateTime());
teamVO.setUpdateTime(team.getUpdateTime());
teamVO.setIsDelete(team.getIsDelete());
return teamVO;
}).collect(Collectors.toList());
return teamVOList;
}
Day5
- 实现用户私聊,相关配置我已做好,详情可参考这篇博客:SpringBoot集成WebSocket进行前后端通信_谢小涛的博客-CSDN博客
后续该功能完成后,会详细记录 (2023/09/13早)
点击进入私聊,展示聊天信息
对于聊天信息的展示,还有几分疑惑:
- 用户发送消息后,服务器转发给指定用户应该怎样实现?
- 还是说存储到Redis中后,用户在私聊的聊天页面中,直接从Redis中查找自己与对方的消息,再展示出来呢?
- 这些问题我还感到很迷惑,一时想不明白如何实现,待解决
现在待解决的问题还很多:
Day6
1 |
|
1 |
|
1 |
|
- 如何实现在用户聊天过程中,实时更新聊天记录内容呢?需解决两个问题:
- 用户发送消息后,立刻展示在页面(发送消息给服务器,服务器存储信息到redis后,再立刻查询该消息,并返回到前台)
- 对方发送消息后,立刻展示在页面
- 这个实现还在思考中,需要前端不断查询redis,消息发生被更新就立刻同步到前台吗?太不现实了,很浪费资源
- 那就应该是服务器主动转发消息了
本来想着服务器如何给指定用户sid转发消息,好像不太好实现
在客户端尝试连接服务端socket,并成功连接后,服务端的 webSocketSet 存放了 在线连接用户的信息
按现在的情况,服务端只能校验该消息是哪个用户所发的,即校验 webSocketSet中存放的sid,能否在已连接用户中找到发送消息的用户,并选择转发消息
那只好是服务器同意转发至客户端,客户端根据sendId和receiveId选择是否展示
1 |
|
Day7
实时双向通信终于测试成功,详情可见 踩坑记录 栏目的 **双向通信调试成功 ** (2023/09/15早)
完成用户私聊功能、完善组队功能,开发队伍聊天功能
计划优化整个网站的页面效果
- 点击好友私聊,跳转至聊天页面
- 首先要开发一个聊天页面:聊天窗口、可支持输入聊天内容、点击发送
- 携带好友id进入聊天页面,确认消息的接收方
- 用户登录以后,就应该连接到服务器的socket服务了,这样既可以实时接收消息,也可以在后台接收消息、标注为未读
1 |
|
用户能够发送消息给指定用户,实现了只有指定用户才能接收到该消息的功能
优化消息列表展示
Vue前端页面实现真麻烦,难倒我了,本来获取当前页面路由很简单的,搞了半天。。
页面更新
经过一周的沉淀,想明白了整个网站的页面布局
点击私聊,可跳转至聊天页面
- 本来打算这个功能是在tab页之间跳转的,但是tab组件仅支持监听tab页变化,获取key值,不支持根据key值,跳转tab页
- 索性直接把页面大改版,做成三个主页面:
- 用户功能页(找用户、找队伍)
- 聊天页(私聊、队内聊天、大厅聊天)
- 博客页(博客展示、评论、点赞、收藏)
Day8
修改数据库
- 这个账户和昵称我一直认为不合理
好友列表 点击 私聊 跳转至聊天大厅的特定聊天窗口处(2023/09/16早)
- 传递好友id,聊天大厅 tab页接收id参数
1
2
3
4
5
6
7
8
9
10// 监听页面变化
const goToTab = () => {
router.push({
name:"chat",
path:"/chat",
query: {
chatUserId:12345
}
})
}1
2
3
4
5
6const route = useRoute();
const chatTabName = ref({});
// 钩子函数
onMounted(() => {
chatTabName.value = route.query
})1
<a-tab-pane key="1" :tab="chatTabName.chatUserId">
原本打算做这样的实现:
- 点击好友私聊后,能够跳转至聊天大厅,并创建一个特有的tab标签页,用来做与该好友的聊天页面
- 只要传递好友username作为参数,将tab页命名为username,即可创建特有的tab标签页
- 但是每次只能由一个好友聊天窗口,像这样:
- 这是因为每次只能追加一个tab页面,而退出该页面后,添加的tab页组件就没了,不能保存下来
- 有关这样的问题,肯定是有解决办法的,好像有个 keep-alive 标签就是干这个的,不过不深究了
现在索性改为这样实现了:
- 直接罗列出所有的好友聊天列表,每次点击私聊后不是根据传递的参数新增一个tab页,而是定位到对应的标签页
- 成功实现点击私聊后,跳转至聊天大厅中的对应聊天窗口
- 正式着手开发双向实时通信,还有个小BUG,我自己的消息,会在每个聊天窗口显示
- 查找聊天消息时,应该在每个聊天窗口发出请求,请求参数为当前用户id和聊天对象id,查询这些消息
业务逻辑好像出问题了:
- 现在用户给指定用户发送消息后,确实保证了只有该用户能收到消息
- 但是收到消息如何持久化到聊天记录中呢?不能光打印一下就完事了对吧
- 那就是要重新查询该聊天窗口的消息,并返回给前端
- 怎么查询?根据两用户的id查询:你给我发的消息,我给你发的消息
成功解决核心问题,基本实现了实时双向聊天通信功能 🌞🌞🌞🌞🌞
Day9
了解Java并发编程,批量插入用户数据(2023/09/17晚)
每周推荐用户:
- 相似度匹配算法
- 根据用户标签,对比标签的相似度,得出相似度降序排行,取出前5条数据,即每周推荐用户为5个
- 如何实现每周推荐5个呢?写个定时任务,只要服务器开启,每隔一周查询一次相似用户,并更新到数据库中
实现用户推荐,改为了这样的实现:
- 思考设计博文表,同时优化通信功能(2023/09/17晚)
优化页面计划:
- 页头,不使用 a-page-header 组件了,自己加个毛玻璃效果挺好看的
- 卵石紫 - 中国色 - 中国传统颜色 (zhongguose.com)
Day10:
1 |
|
1 |
|
Day11
实现计算每周匹配用户的匹配度
1
2// 计算编辑距离
long distance = AlgorithmUtils.minDistance(tagList, userTagList)1
2
3
4
5
6
7
8
9
10
11
12
13
14// 计算匹配度
double percentage = getPercentage(distance);
/**
* 计算匹配度
*
* @param distance 编辑距离
* @return 匹配度
*/
public double getPercentage(long distance) {
// 计算匹配度的百分比
return (1 - (distance - MIN_DISTANCE) / (MAX_DISTANCE - MIN_DISTANCE)) * 100;
}
1
2
3
4@Data
public class UserVO extends User {
private double percentage;
}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/**
* 转换 userList 为 userVOList
*
* @param userList userList
* @return teamVOList
*/
public List<UserVO> getUserVOByUser(List<User> userList, List<Double> distanceList) {
return IntStream.range(0, userList.size())
.mapToObj(i -> {
User user = userList.get(i);
double distance = distanceList.get(i);
UserVO userVO = new UserVO();
userVO.setPercentage(distance);
userVO.setId(user.getId());
userVO.setUserAccount(user.getUserAccount());
userVO.setUsername(user.getUsername());
userVO.setUserPassword(user.getUserPassword());
userVO.setAvatarUrl(user.getAvatarUrl());
userVO.setGender(user.getGender());
userVO.setPhone(user.getPhone());
userVO.setEmail(user.getEmail());
userVO.setUserStatus(user.getUserStatus());
userVO.setIsOnline(user.getIsOnline());
userVO.setCreateTime(user.getCreateTime());
userVO.setUpdateTime(user.getUpdateTime());
userVO.setIsDelete(user.getIsDelete());
userVO.setUserRole(user.getUserRole());
userVO.setPlanetCode(user.getPlanetCode());
userVO.setTags(user.getTags());
userVO.setProfile(user.getProfile());
return userVO;
})
.collect(Collectors.toList());
}将匹配度百分比保留了小数点后两位:
1 |
|
- 已成功定位到相关问题:
- 在聊天大厅刷新页面后,向后端发送获取聊天记录列表时,没有携带当前登录用户id,即senderId = null
- 这里发现一个问题:
- 之前的聊天页面,只能通过点击私聊才能跳转,而跳转时,路由中是携带了聊天对象的id
- 在经过页面更新以后,可以直接从主页面或者博客页面跳转,这时候就没有聊天用户id了
- 所以传获取消息时,receiverId参数总为undefined
- 解决办法:
- 对于私聊跳转,页面刷新,不能依赖于获取路由参数,而应该事先将变量保存,直接取到变量值即可
- 而对于大厅跳转,则应该根据选中的tab标签页,获取对应key值
- 应该做好tab标签页key值的初始化以及动态更新的工作
根本解决不了,就应该在聊天大厅新增一个页面或窗口,大厅跳转进来是直接选中这个窗口的,
然后通过选中tab标签页,给activeKey赋值,这时候刷新页面就能停留在对应标签页上了
好极了,新增文件传输助手,页面跳转直接选中该tab标签页:
1 |
|
1 |
|
- 何时跳转文件传输助手页?
- 其他大厅首次跳转,进入聊天大厅页面,即 chatTabName.value.chatTabName === undefined
- 刷新页面,仅当当前 activeId 为currentId时
Day12
- 彻底解决刷新页面后,连接丢失的问题
1 |
|
- 如上,我将currentId存放进localStorage中,这样在页面刷新后,可以从localStorage中取到当前用户id,重新发起连接
1 |
|
1 |
|
- 页面刷新之后,选中刚刚选中的tab页:
1 |
|
- 回收前面的解决连接丢失的问题,因为直接这行代码就没问题:
1 |
|
1 |
|
Day13
未登录时,进入聊天大厅获取当前登录用户的id时,会报异常
1
2
3
4
5
6
7
8// 前往博客社区
const goToChat = () => {
if (!currentUser.value) {
message.warning("请先登录")
} else {
router.push("/chat")
}
}优化用户体验:
- 未登录时,不能跳转至聊天大厅
- 未登录时,不能查看个人信息
简单地优化了页面的表现张力:(2023/09/21晚)
Day14
一篇博客应该有什么信息呢?
完成博客列表优化:
现阶段优化指南
博客页面优化:
- 如何实现每个博文绑定自己的Like、Collect、Comment,待解决
- 博文应该带标签,点击对应标签,就可跳转至搜索的博文内容搜索结果
- 那就是要开发一个博文查找功能,开发博文查找页面,根据输入字段,查询匹配title、description的博文
- 待隔壁MemorySearch聚合搜索平台成功落地之后,会在MemoryChat博客社区中引入ES,进行快速查找
- 开发博客编写页面,用户能够在线编写提交博文
- 用户收藏夹:多个收藏集,集合中博文的排列列表与搜索博文的结果展示,设想一致(掘金就这么干的)
- 评论功能,这个应该是核心,不过我肯定一样能轻松搞定
队伍功能优化:
私聊体验优化:
用户中心大厅优化
- 将来计划把博客社区搞为默认首页,那用户中心是用来干什么的呢?(稍等一下,我问问AI)
- 可以展示什么内容呢?介绍下这个网站的内容
Day15
搜索队伍、搜索用户、搜索博客将来统一处理
队伍状态(是否加密,显示) ✔
加密队伍,设置密码 ✔
1 |
|
今日待解决:
- 私密队伍需输入密码才可申请加入 ✔😈
- 实时消息没有获取到发送人的详细信息 😈
- 发布队内公告 ✔😈
- 首次进入页面时,聊天消息未渲染的成功 ✔😈
- 博文列表太长了,应该加个每页数量和延时加载效果
- 识别Markdown格式 ✔
- 博文收藏页、博文搜索页
Day16
1 |
|
组件撑不开盒子,老有这个问题
- 妈的,组件存在浮动、定位属性,当然撑不开了
成功解决点赞、收藏功能(每篇文章独有)
解决这个问题的关键就是,为每个文章都分配独有的判定点赞、判定收藏变量:
在获取博文列表时,为每个博文增加这几个字段:
1 |
|
1 |
|
1 |
|
Day17
封装消息的内容
搞没了,前端拿取每条消息的每个id再发送请求,简直做梦,还是搞后端吧
现在把头像换成登录用户的头像
简单地优化了双向通信的消息气泡展示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<div :class="messageClass(item.senderId)">
<!--发送者-->
<div style="position: absolute;top: 6px">
<a-avatar size="large" :src="currentUser.avatarUrl"/>
</div>
<div style="margin-left: 24px;font-size: medium">
<a-list-item>
<div style="font-size: smaller">
<!--发送时间-->
<a-list-item-meta :description="item.sendTime">
</a-list-item-meta>
</div>
<!--消息内容-->
{{ item.content }}
</a-list-item>
</div>
</div>1
2
3
4
5
6
7
8
9
10
11
12.senderMsg {
position: relative;
left: 1150px
}
.receiverMsg {
position: relative;
}
.receiverMsg {
position: relative;
}1
2
3
4
5
6
7
8// 动态变换收发双方消息气泡
const messageClass = (senderId) => {
if (currentUserId === senderId) {
return 'senderMsg'; // 如果当前用户的ID等于消息发送者的ID,
} else {
return 'receiverMsg'; // 其他情况返回空字符串
}
}最终效果:
1 |
|
- 简单实现了申请加密队伍需要填写密码的功能:
1 |
|
1 |
|
- 申请添加好友/进入队伍之前,都应该有弹窗进行反复确认
Day18
- 简单的博文收藏页:
1 |
|
1 |
|
- 解决一下默认打开用户中心 -> 我的页面时,没有渲染出已加入的队伍列表
1 |
|
1 |
|
Day19
完善昨晚的发布公告,遇到了些许小问题:
- 由于编辑公告的弹窗是写在list列表中的,即每个team都有自己的公告弹窗,所以不应该使用一个变量来控制弹窗的出现/消失:
1
2
3
4
5
6// 弹窗
const visible = ref<boolean>(false);
// 展示弹窗
const showModal = (team: any) => {
visible.value = true;
};- 而应该为每个team,都分配独有的控制弹窗出现的变量(showEditWindow):
1
2
3
4
5
6
7
8
9
10
11
12myAxios.get("/team/created", {
params: {
"loginUserId": userId
}
}).then((res) => {
createdTeamList.value = res.data.records.map((team: any) => {
return {
...team,
showEditWindow: false
};
});
})- 为每一个team绑定:
1
2
3
4
5
6
7<a-button size="large" danger @click="showModal(item)">发布公告</a-button>
<div style="position: relative">
<a-modal v-model:visible="item.showEditWindow" title="队伍公告" @ok="updateAnnouncement(item)">
<p>请编辑队伍公告</p>
<a-textarea v-model:value="announcement" style="height: 100px"/>
</a-modal>
</div>1
2
3
4// 公告弹窗
const showModal = (team: any) => {
team.showEditWindow = true;
};这个思路是普适的,在获取后台传回的数组信息时,为数组内每一个元素都封装自己的独有的元素
在实现每篇文章的点赞、收藏,以及申请加入队伍中,都是这样的思路(2023/09/28午)
新增文章评论功能
后端新建评论表:
这个评论表该如何构建呢?表字段:
- 评论id、子评论id、发评者id、评论内容、评论时间
文章如何绑定评论?
- 设计一张文章/评论表(太庞大了,每篇文章与其评论的关系都要一一列举)
- 文章添加字段:评论字段(comments),存储评论id,当然,仅存取一级评论id
- 二级评论id当然要从一级评论的字段中找了,子评论字段(comments)存储二级评论id
Day20
1 |
|
1 |
|
MemoryChat开发计划:
经验分享
- 如何实现点击好友私聊后,跳转至聊天窗口的对应聊天窗口?
- 实现流程:
- 进入聊天大厅,直接查询我的所有好友信息
- 聊天大厅直接以tab标签页的形式,展示了所有好友的聊天窗口,其中,各个tab页的key值为好友的id
- 点击私聊,携带好友id作为参数,从我的好友列表跳转至聊天大厅
- 跳转至聊天大厅,直接获取传递的id参数,并将其值赋给tab页的 activeKey
- tabs标签页默认选中key值为 activeKey 的tab页
- 接下来就是在各个聊天窗口,实现双向实时通信了
网页中支持Markdown语法写博客
要在博客网站中支持Markdown语法写博客并展示,你可以使用第三方的Markdown解析库来解析Markdown文本,并将解析后的内容展示在网页上(2023/09/24晚)
首先,你需要引入一个适用于Vue的Markdown解析库,例如
markdown-it
。可以通过NPM安装该库:
1 |
|
- 然后,在你的组件中,你可以导入并实例化
markdown-it
,将Markdown文本作为输入,使用.render()
方法将其转换为HTML并展示在网页上
1 |
|
1 |
|
在上面的示例代码中,我们导入了
markdown-it
库- 然后在
mounted
钩子中实例化了MarkdownIt
对象 - 并将Markdown文本
this.articleInfo.content
传递给其.render()
方法来解析为HTML并赋值给parsedContent
- 然后我们使用
v-html
指令将解析后的内容展示在网页上。
- 然后在
这样,无论用户使用Markdown语法还是普通的HTML编写博客内容,页面都会正确展示
最终效果:
踩坑记录
JSON转换错误
1 |
|
- 前端返回的 mes 是 JSON字符串,我看见控制台输出的内容是,以为是个对象,结果取到的属性值是undefined:
1 |
|
- 调试了半天,检查了下 mes 的类型,才发现是个JSON字符串,奶奶的
1 |
|
1 |
|
- 最后优化代码,同时处理两种情况:服务器首次连接成功后的响应(普通字符串) / 服务器转发的消息(JSON字符串)
1 |
|
双向通信调试成功
- 首先解决上面小坑,那个代码写的还有点问题,前端还得分两次校验是否为JSON字符串
- 于是我把后端服务器首次响应时的响应结果,转换成JSON字符串了
- 又写了好多打印输出代码,好歹完成了核心功能:服务器向指定用户转发消息(2023/09/15午)
1 |
|
这里还有好多小坑:(2023/09/15午)
- 服务器根据在线人数(即连接的客户端数量),决定转发多少条消息,所以控制台输出了不止一条消息(重复的消息)
- 在socket服务下修改了代码之后,有时需重启浏览器才生效,这种情况还是第一次遇到
- 当然了,为了测试实时双向通信,我分别开启了Edge浏览器和Goole浏览器进行测试
部署和维护
- 列出将项目部署到生产环境所需的步骤和配置信息,包括服务器环境要求和数据库设置。
- 提供项目的维护和支持方式,如bug提交、技术支持联系方式等。
TODO
添加好友
- 用户添加好友上限字段,一个用户应该有好友上限
- 用户不应该直接添加好友,应该发送好友申请,待对方同意后,二者成为好友关系(如何判断?)
1 |
|
- 添加成功/失败后的反馈
- 添加好友后,可支持修改好友昵称
分页查询
- 后台的分页查询未优化,分页参数写死为 currentPage:1,pageSize:20
异常处理
- 抛出的异常可以设置独有的错误码,前端妥善解决,优化用户体验
- 可以考虑封装一个简单的异常处理工具(ThrowUtils)
用户体验
- 发出添加好友、申请入队请求后,抽屉关闭(?)
- 用户在线问题需要优化,应该存放进Redis中,设置过期时间为 2hour ,现在是直接从数据库中取得
- 而最大的问题是,用户登录后,修改用户状态为在线,仅用户执行登出操作后,才会修改用户状态为下线
- 用户进入聊天页面,保持聊天窗口滚动条保持在最底部
- 私聊时,展示用户昵称
- 聊天消息目前是设置2hour后过期,这其实比较不合理,但是处于测试阶段,测试的通信记录开销还蛮大的,删了挺好
- 日后再考虑如何优化:定期删除(7天) + 用户手动清空聊天记录,这个更加合理
聊天页面刷新,会拿不到登录用户的信息,待修复
- 私聊操作应该更加多元化,可以发送文本消息、表情、甚至是收发文件
Redis
- 考虑封装一个RedisUtils,统一所有的 Redis 操作
Tab标签页
好友私聊
- 区分聊天双方,可以在聊天窗口内添加用户昵称,也可以实现:双方的消息气泡在窗口的不同侧
- 收到消息可以弹出消息提示,提示未读消息
- 刷新聊天大厅,与服务器的连接丢失