本文已参与「新人创作礼」活动,一起开启掘金创作之路。
需求
将学校以班级为单位制作一个简易的朋友圈,默认用户在一个班级里,用户可以在朋友圈里发动态,支持9宫格,对动态进行评论,回复其他用户的评论,可删除自己的评论,对动态进行点赞,可取消自己的点赞等功能实现,这里的班级成员相当于朋友圈里的好友。
需求整理如下:
- 班级圈里的用户均可以发布动态, 支持定位、图片等。
- 班级圈里的用户均可以查看、评论、删除自己的动态。
- 班级圈里的用户可以对动态进行互相点赞、评论、回复。
- 班级圈里的用户只能删除自己的动态、评论以及取消自己的点赞。
一、设计数据库
一共只需要三张表即可, 默认班级表(sys_clas)存在,用户表含有class_id:
- friend_message: 朋友圈动态表。包含动态内容,动态发布所在的位置,9宫格图片,发布的时间。
- friend_message_comments: 朋友圈评论表。包含 动态id、评论内容、用户id(是谁评论的), 回复用户id(给哪个用户回复,如果该字段为空,表示对该动态进行评论,如果有值,那么表示对该发布该动态的人进行回复)
- sys_user: 系统用户表。
在设计动态表是,应该先了解一下什么是时间线。
什么是timeline?
微博动态的设计有一个叫timeline的东西, 简单地说就是将好友或关注发送的动态归纳到一条属于本用户的时间线上, 用户通过刷新拉取上一次时间点到最新时间点的东西,前端只需要将获取到最新的数据追加到页面的最前面即可。
完整的sql:
/*
Navicat Premium Data Transfer
Source Server : local_mysql
Source Server Type : MySQL
Source Server Version : 50725
Source Host : localhost:3306
Source Schema : friend_ship
Target Server Type : MySQL
Target Server Version : 50725
File Encoding : 65001
Date: 12/04/2021 15:02:12
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for friend_message
-- ----------------------------
DROP TABLE IF EXISTS `friend_message`;
CREATE TABLE `friend_message` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`description` varchar(5000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`enabled` tinyint(1) NOT NULL,
`create_datetime` datetime(6) NULL DEFAULT NULL,
`update_datetime` datetime(6) NULL DEFAULT NULL,
`delete_datetime` datetime(6) NULL DEFAULT NULL,
`content` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '动态内容',
`location` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '动态位置',
`image` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '动态图片',
`no_comment` tinyint(1) NOT NULL COMMENT '是否允许评论',
`friend_class_id` int(11) NOT NULL COMMENT '班级id',
`user_id` int(11) NOT NULL COMMENT '用户id',
`time` bigint(20) NOT NULL COMMENT '时间线上的时间点',
PRIMARY KEY (`id`) USING BTREE,
INDEX `friend_message_friend_class_id_44f6ff0a_fk_school_class_id`(`friend_class_id`) USING BTREE,
INDEX `friend_message_user_id_2e2d7a4f_fk_sys_user_id`(`user_id`) USING BTREE,
CONSTRAINT `friend_message_user_id_2e2d7a4f_fk_sys_user_id` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of friend_message
-- ----------------------------
INSERT INTO `friend_message` VALUES (1, '1', 1, '2021-03-25 11:50:32.000000', '2021-03-25 13:46:54.967786', '2021-03-25 13:46:54.967786', '今天天气真不错', '上海浦东新区', '/resource', 0, 1, 1, 864426018297202);
INSERT INTO `friend_message` VALUES (2, '', 1, '2021-03-25 13:51:59.774310', '2021-03-25 13:51:59.774310', NULL, '今天真是一个好天气,适合出去玩', '上海浦东新区', '[]', 0, 1, 1, 864426018297202);
INSERT INTO `friend_message` VALUES (3, '', 1, '2021-03-25 13:55:27.396234', '2021-03-25 13:55:27.396234', NULL, '今天真是一个好天气,适合出去玩,我又发了一条动态', '我在北京天安门', '[]', 0, 1, 45, 864426018297202);
INSERT INTO `friend_message` VALUES (4, '', 1, '2021-03-25 14:12:36.385859', '2021-03-25 14:12:36.385859', NULL, '今天真是一个好天气,适合出去玩,我又发了一条动态', '我在天津', '[\"/resource/1\", \"/resource/2\"]', 0, 1, 45, 864426018297202);
INSERT INTO `friend_message` VALUES (5, '', 1, '2021-04-06 22:26:15.429701', '2021-04-06 23:38:42.103458', '2021-04-06 23:38:42.103449', '测试的', '', '[\"/20210406222609/523037/Screenshot_20210406-113207.png\"]', 0, 1, 45, 864426018297202);
-- ----------------------------
-- Table structure for friend_message_comments
-- ----------------------------
DROP TABLE IF EXISTS `friend_message_comments`;
CREATE TABLE `friend_message_comments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`description` varchar(5000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`enabled` tinyint(1) NOT NULL,
`create_datetime` datetime(6) NULL DEFAULT NULL,
`update_datetime` datetime(6) NULL DEFAULT NULL,
`delete_datetime` datetime(6) NULL DEFAULT NULL,
`content` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '评论内容',
`message_id` int(11) NOT NULL COMMENT '动态id',
`user_id` int(11) NOT NULL COMMENT '用户id',
`reply_to_id` int(11) NULL DEFAULT NULL COMMENT '被回复的用户id',
PRIMARY KEY (`id`) USING BTREE,
INDEX `friend_message_comments_message_id_f9d21e99_fk_friend_message_id`(`message_id`) USING BTREE,
INDEX `friend_message_comments_user_id_f9b2c710_fk_sys_user_id`(`user_id`) USING BTREE,
INDEX `friend_message_comments_reply_to_id_dada096c_fk_sys_user_id`(`reply_to_id`) USING BTREE,
CONSTRAINT `friend_message_comments_message_id_f9d21e99_fk_friend_message_id` FOREIGN KEY (`message_id`) REFERENCES `friend_message` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `friend_message_comments_reply_to_id_dada096c_fk_sys_user_id` FOREIGN KEY (`reply_to_id`) REFERENCES `sys_user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `friend_message_comments_user_id_f9b2c710_fk_sys_user_id` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`description` varchar(5000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`enabled` tinyint(1) NOT NULL,
`create_datetime` datetime(6) NULL DEFAULT NULL,
`update_datetime` datetime(6) NULL DEFAULT NULL,
`delete_datetime` datetime(6) NULL DEFAULT NULL,
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '昵称',
`mail` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱',
`gender` int(11) NULL DEFAULT NULL COMMENT '性别',
`age` int(11) NULL DEFAULT NULL COMMENT '年龄',
`role` int(11) NOT NULL COMMENT '角色',
`phone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '手机号',
`pwd` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码',
`birthday` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生日',
`class_id` int(11) NULL DEFAULT NULL COMMENT '班级id',
`last_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '姓名',
`card` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '身份证号',
`head_image` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '头像',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `sys_user_phone_role_a0e25d98_uniq`(`phone`, `role`) USING BTREE,
INDEX `phone_index`(`phone`) USING BTREE,
INDEX `last_name_index`(`last_name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 46 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, NULL, 1, '2021-04-12 14:59:06.000000', '2021-04-12 14:59:07.000000', NULL, '张三', '123456@qq.com', 1, 23, 1, '158888888888', '123456', '2000-05-13', 1, '张三', '421182555555555', '/default');
INSERT INTO `sys_user` VALUES (45, NULL, 1, '2021-04-12 14:59:06.000000', '2021-04-12 14:59:07.000000', NULL, '李四', '123456@qq.com', 1, 23, 1, '15666666666', '123456', '2000-05-13', 1, '李四', '421182555555555', '/default');
SET FOREIGN_KEY_CHECKS = 1;
二、动态功能实现
1. 发布动态
发布动态的只需要注意2个问题。
- 参数需要带上class_id。
- 将发布的时间戳存入到 friend_message表里的time字段里。 请求参数:
{
"content": "今天真是一个好天气,适合出去玩,我又发了一条动态",
"location": "我在天津",
"images": [
"/resource/1",
"/resource/2"
]
}
2. 上拉获取更多动态
上拉获取动态,可以通过设置一次拉取一定数量的数据来达到上拉获取更多动态,前端每次请求的数量通过分页实现, 例如每次请求的size为10条。 假设当前用户为李四,李四发布了一条动态,通过接口获取后返回数据结构
{
"code": 0,
"message": "OK",
"data": [
{
"id": 4,
"user": {
"id": 7,
"last_name": "李四",
"head_image": "/resource/123"
},
"friend_class": 2,
"content": "今天真是一个好天气,适合出去玩,我又发了一条动态",
"location": "我在天津",
"image": [
"/resource/resource/1",
"/resource/resource/2"
],
"no_comment": false,
"create_datetime": "2021-03-25 14:12:36",
"is_own_message": true,
"comments": [
{
"id": 1,
"user": {
"id": 45,
"last_name": "李四"
},
"content": "我觉得今天天气很不错",
"reply_to": null,
"is_own_comment": true
},
{
"id": 2,
"user": {
"id": 1,
"last_name": "张三"
},
"content": "回复 李四: 我也觉得今天天气不错",
"reply_to": 45,
"is_own_comment": false
}
],
"like": {
"users": [
{
"id": 1,
"last_name": "张三"
},
{
"id": 45,
"last_name": "李四"
}
],
"is_own_like": false
}
}
],
"total": 1,
"elapsed": 76
}
权限控制
此处查询时添加了三个布尔值的字段 is_own_message, is_own_comment和 is_own_like, 值为true时,表示是自己发布的动态、评论的内容和点赞。通过此这三个字段我们可以实现前端页面上实现权限功能: 只能删除自己的动态、只能删除自己的评论、只能取消自己的点赞。
3. 下拉获取最新动态
在每次下拉时,获取最新的班级圈的时间戳。根据班级id进行筛选,获取到max(tiime)。 获取到最大的time, 例如 1618196528, 那么就可以根据该 1618196528 和 class_id去查询出比该时间戳大的动态。sql:
SELECT
`friend_message`.`id`,
`friend_message`.`description`,
`friend_message`.`enabled`,
`friend_message`.`create_datetime`,
`friend_message`.`update_datetime`,
`friend_message`.`delete_datetime`,
`friend_message`.`user_id`,
`friend_message`.`friend_class_id`,
`friend_message`.`content`,
`friend_message`.`location`,
`friend_message`.`image`,
`friend_message`.`no_comment`,
`friend_message`.`time`
FROM
`friend_message`
WHERE
( `friend_message`.`delete_datetime` IS NULL AND `friend_message`.`friend_class_id` = 2 AND `friend_message`.`time` < 1618196527 )
ORDER BY
`friend_message`.`time` DESC
三、点赞、评论、回复功能实现。
1. 点赞和取消点赞
点赞和取消点赞可以说是非常容易实现的,我们可以借助redis来实现点赞和取消点赞。将动态id作为key,用户id作为value, 此文借助redis的集合来进行存取。 前端需根据is_own_like判断只有当前用户才能够取消点赞。
conn = redis_util.get_redis_connection()
# 点赞
conn.sadd("friend_message:" + str(dynamic_id), user_id)
# 取消点赞
conn.srem("friend_message:" + str(dynamic_id), user_id)
# 获取点赞数
conn.sinter("friend_message:" + str(dynamic_id))
2. 评论和回复
添加: 回复的时候在参数里带一个 reply_to_id即可。
def post(self, request, dynamic_id=None):
r = Result()
try:
user_id = self.auth(request)
content = request.data["content"]
message = FriendMessage.objects.filter(id=dynamic_id).first()
if not message:
raise Exception("动态已删除!")
if len(content) == 0:
raise Exception("请输入内容!")
user = SysUser.objects.filter(id=user_id).first()
if "reply_to_id" in request.data:
# 回复某个用户
reply_to_id = request.data["reply_to_id"]
replay_to = SysUser.objects.filter(id=reply_to_id).first()
FriendComment.objects.create(message=message, user=user, content=content, reply_to=replay_to)
else:
FriendComment.objects.create(message=message, user=user, content=content)
except Exception as e:
r.error(e)
return self.s_result(r)
查看:
由于朋友圈里的评论和回复没有设置分级,即评论和回复的显示都是平级,因此在查询评论和回复的时候,不需要设置父子关系,因此在设计评论表的时候,给一个reply_to_id就能够区分comments表里的记录是评论还是回复。
查看评论和回复代码查看:
def get_comments(dynamic_id, user_id):
comments = FriendComment.objects.filter(message__id=dynamic_id).order_by(
"create_datetime").values(
"id", "user__id", "user__last_name", "content", "reply_to__id", "reply_to__last_name")
data = []
for x in comments:
comment = {
"id": x["id"],
"user": {
"id": x["user__id"],
"last_name": x["user__last_name"]
},
"content": " 回复 " + x["reply_to__last_name"] + ": " + x["content"] if x[
"reply_to__last_name"] else "" + x["content"],
"reply_to": x["reply_to__id"],
"is_own_comment": True if x["user__id"] == int(user_id) else False
}
# find_reply_to(dynamic_id, comment, user_id)
data.append(comment)
return data
如果有不懂的朋友,可以私聊,吐血总结,喜欢的支持一下!