1.需求分析
经过几天学习,相信大家对于业务开发已经轻车熟路了,整体流程与以往一样:
- 需求和原型图分析
- 接口统计和设计
- 数据结构设计
- 接口的实现
1.1.产品原型
1.1.1.课程详情页
在用户已经登录的情况下,如果用户购买了课程,在课程详情页可以看到一个互动问答的选项卡:
问答选项卡如下:
- 问答列表
- 问答列表可以选择全部问题还是我的问题,选择我的问题则只展示我提问的问题。默认是全部
- 选择章节序号,根据章节号查看章节下对应问答。默认展示所有章节的问题
- 对于我提问的问题,可以做删除、修改操作
- 跳转逻辑
- 点击提问按钮,进入问题编辑页面
- 点击问题标题,进入问题详情页
- 点击问题下的回答,进入回答表单
点击提问或编辑按钮会进入问题编辑页面:
- 表单内容
- 课程:问题一定关联提问时所在的课程,无需选择
- 章节:可以选择提问知识点对应的章节,也可以不选
- 问题标题:一个概括性描述
- 问题详情:详细问题信息,富文本
- 是否匿名:用户可以选择匿名提问,其它用户不可见提问者信息
点击某个问题,则会进入问题详情页面:
- 页面内容
- 顶部展示问题相关详细信息
- 任何人都可以对问题做回复,也可以对他人的回答再次回复,无限叠楼。
- 也没渲染只分两层:
- 对问题的一级回复,称为回答
- 对回答的回复、对回复的回复,作为第二级,称为评论
- 问题详情页下面展示问题下的所有回答
- 点击回答下的详情才展示二级评论
- 可以对评论、回答点赞
1.1.2.视频学习页
另外,在视频学习页面中同样可以看到互动问答功能:
这个页面与课程详情页功能类似,只不过是在观看视频的过程中操作。用户产生学习疑问是可以快速提问,不用退回到课程详情页,用户体验较好。
- 页面逻辑
- 默认展示视频播放小节下的问答
- 用户可以在这里提问问题,自动与当前课程、当前视频对应章节关联。其它参数与课程详情页的问题表单类似。
- 问答列表默认只显示问题,点击后进入问题详情页才能查看具体答案
1.1.3.管理端问答管理页
除了用户端以外,管理端也可以管理互动问答,首先是一个列表页:
- 搜索
- 管理员可以搜索用户提出的所有问题
- 搜索结果可以基于页面过滤条件做过滤
- 问题状态:已查看、未查看两种。标示是否已经被管理员查看过。每当学员在问题下评论,状态重置为未查看
- 课程名称:由于问题是提问在课程下的,所以会跟课程关联。管理员输入课程名称,搜索该课程下的所有问题
- 提问时间:提出问题的时间
- 页面列表
- 默认按照提问时间倒序排列;点击回答数量时可以根据回答数量排序
- 课程分类:需要展示问题所属课程的三级分类的名称的拼接
- 课程所属章节:如果是在视频页面提问,则问题会与视频对应的章、节关联,则此处显示章名称、节名称。
- 课程名称:提问是针对某个课程的,因此此处显示对应的课程名称
- 回答数量:该问题下的一级回复,称为回答。此处显示问题下的回答的数量,其它评论不统计。
- 用户端状态:隐藏/显示。表示是否在用户端展示,对于一些敏感话题,管理员可以直接隐藏问题。
- 操作
- 点击查看:会将该问题标记为已查看状态,并且跳转到问题详情页
- 点击隐藏或显示:控制该问题是否在用户端显示。隐藏问题,则问题下的所有回答和恢复都被隐藏
点击查看按钮,会进入一个问题详情页面:
- 问题详情
- 页面顶部是问题详情,展示信息与问题列表页基本一致
- 点击评论,老师可以回答问题
- 点击隐藏/显示,可以隐藏或显示问题
- 回答列表
- 分页展示问题下的回答(一级回复)
- 可以对回答点赞、评论、隐藏
- 点击查看,则进入回答详情页
继续点击查看更多按钮,可以进入回答详情页:
- 回答详情
- 页面顶部是回答详情,展示信息与回答列表页基本一致
- 点击我来评论,老师可以评论该回答
- 点击隐藏/显示,可以隐藏或显示该回答,该回答下的所有评论也都会被隐藏或显示
- 评论列表
- 分页展示回答下的评论
- 可以对评论点赞、回复、隐藏
1.1.4.流程总结
整体来说,流程是这样的:
- 学员在学习的过程中可以随时提问问题
- 老师、其他学员都可以回答问题
- 老师、学员也都可以对回答多次回复
- 老师、学员也都可以对评论多次回复
- 老师可以在管理端管理问题、回答、评论的状态
业务流程并不复杂。
1.2.接口统计
理论上我们应该先设计所有接口,再继续设计接口对应的表结构。不过由于接口较多,这里我们先对接口做简单统计。然后直接设计数据库,最后边设计接口,边实现接口。
1.2.1.问题的CRUD
首先第一个页面,列表展示页:
结合原型设计图我们可以看到这里包含4个接口:
- 带条件过滤的分页查询
- 新增提问
- 修改提问
- 删除提问
这些都是基本的CRUD,应该不难。
1.2.2.问题的回答和评论
进入问答详情页再看:
可以看到页面中包含5个接口:
- 根据id查询问题详情
- 分页查询问题下的所有回答
- 分页查询回答下的评论
- 点赞/取消点赞某个回答或评论
- 回答某个提问、评论他人回答
除了点赞功能外,其它接口也都是基本的增删改查,并不复杂。
1.2.3.管理端接口
刚才分析的都是用户端的相关接口,这些接口部分可以与管理端共用,但管理端也有自己的特有需求。
管理端也可以分页查询问题列表,而且过滤条件、查询结果会有很大不同:
比较明显的有两个接口:
- 管理端分页查询问题列表:与用户端分页查询不通用,功能更复杂,查询条件更多
- 隐藏或显示指定问题
除此以外,这里有一个问题状态字段,表示管理员是否查看了该问题以及问题中的回答。默认是未查看状态;当管理员点击查看后,状态会变化为已查看;当学员再次回答或评论,状态会再次变为未查看。
因此,需要注意的是:
- 每当用户点击查看按钮,需要根据根据id查询问题详情,此时应标记问题状态为已查看
- 每当学员回答或评论时,需要将问题标记为未查看
管理端也会有回答列表、评论列表。另外,回答和评论同样有隐藏功能。
问题详情和回答列表:
还有评论列表:
总结一下,回答和评论包含的接口有:
- 管理端根据id查询问题详情
- 分页查询问题下的回答
- 分页查询回答下的评论
- 点赞/取消点赞某个回答或评论
- 隐藏/显示指定回答或评论
- 回答某个提问、评论他人回答、评论(与用户端共用)
1.2.4.总结 接口
综上,与问答系统有关的接口有:
2.数据结构
从原型图不难看出,这部分功能主要涉及两个实体:
- 问题
- 回答/评论:回答、评论可以看做一类实体
因此核心要设计的就是这两张表。
2.1.ER图
2.1.1.问题
首先是问题,通过新增提问的表单可以看出问题包含的属性:
2.1.2.回答、评论
回答和评论的属性基本一致,差别就是:
- 回答的对象是问题
- 评论的对象是其它回答或评论
ER图:
2.2.数据库表
结合ER图,表结构就非常清楚了,会包含两张表:
- 问题表
- 回复表:回答和评论都是回复,在一张表
2.2.1.问题表
首先是问题表:
CREATE TABLE IF NOT EXISTS `interaction_question` (
`id` bigint NOT NULL COMMENT '主键,互动问题的id',
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '互动问题的标题',
`description` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '问题描述信息',
`course_id` bigint NOT NULL COMMENT '所属课程id',
`chapter_id` bigint NOT NULL COMMENT '所属课程章id',
`section_id` bigint NOT NULL COMMENT '所属课程节id',
`user_id` bigint NOT NULL COMMENT '提问学员id',
`latest_answer_id` bigint DEFAULT NULL COMMENT '最新的一个回答的id',
`answer_times` int unsigned NOT NULL DEFAULT '0' COMMENT '问题下的回答数量',
`anonymity` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否匿名,默认false',
`hidden` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否被隐藏,默认false',
`status` tinyint DEFAULT '0' COMMENT '管理端问题状态:0-未查看,1-已查看',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '提问时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_course_id` (`course_id`) USING BTREE,
KEY `section_id` (`section_id`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='互动提问的问题表';
2.2.3.回答或评论
回答和评论合为一张表,称为评论表:
CREATE TABLE IF NOT EXISTS `interaction_reply` (
`id` bigint NOT NULL COMMENT '互动问题的回答id',
`question_id` bigint NOT NULL COMMENT '互动问题问题id',
`answer_id` bigint DEFAULT '0' COMMENT '回复的上级回答id',
`user_id` bigint NOT NULL COMMENT '回答者id',
`content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '回答内容',
`target_user_id` bigint DEFAULT '0' COMMENT '回复的目标用户id',
`target_reply_id` bigint DEFAULT '0' COMMENT '回复的目标回复id',
`reply_times` int NOT NULL DEFAULT '0' COMMENT '评论数量',
`liked_times` int NOT NULL DEFAULT '0' COMMENT '点赞数量',
`hidden` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否被隐藏,默认false',
`anonymity` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否匿名,默认false',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_question_id` (`question_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='互动问题的回答或评论';
3.问题相关接口
问题相关接口在管理端和用户端存在一些差异,在设计接口时一定要留意。另外,此处只带大家实现其中的部分接口:
- 新增互动问题
- 用户端分页查询问题
- 根据id查询问题详情
- 管理端分页查询问题
3.1.新增问题
3.1.1.接口分析
首先还是看原型图,新增的表单如下:
通过新增的问题的表单即可分析出接口的请求参数信息了,然后按照Restful的风格设计即可:
3.1.2.实体类
新增业务中无返回值,只需要设计出入参对应的DTO即可
3.1.3.代码实现
首先是tj-learning中的InteractionQuestionController:
@RestController
@RequestMapping("/questions")
@RequiredArgsConstructor
public class InteractionQuestionController {
private final IInteractionQuestionService questionService;
@ApiOperation("新增提问")
@PostMapping
public void saveQuestion(@Valid @RequestBody QuestionFormDTO questionDTO){
questionService.saveQuestion(questionDTO);
}
}
然后是tj-learning中的IInteractionQuestionService接口:
public interface IInteractionQuestionService extends IService<InteractionQuestion> {
void saveQuestion(QuestionFormDTO questionDTO);
}
最后是tj-learning中的InteractionQuestionServiceImpl实现类:
@Service
@RequiredArgsConstructor
public class InteractionQuestionServiceImpl extends ServiceImpl<InteractionQuestionMapper, InteractionQuestion> implements IInteractionQuestionService {
private final IInteractionQuestionDetailService detailService;
@Override
@Transactional
public void saveQuestion(QuestionFormDTO questionDTO) {
// 1.获取登录用户
Long userId = UserContext.getUser();
// 2.数据转换 - DTO转po
InteractionQuestion question = BeanUtils.toBean(questionDTO, InteractionQuestion.class);
// 3.补充数据(因为前端传来的DTO数据 不包含po所需要的全部数据 需要自己实现代码额外添加)
question.setUserId(userId);
// 4.保存问题
save(question);
// 5.问题详情
InteractionQuestionDetail detail = new InteractionQuestionDetail();
detail.setId(question.getId());
detail.setDescription(questionDTO.getDescription());
detailService.save(detail);
}
}
分页查询如何做的
用lambdaQuery()的链式编程,后面是拼条件,如果是in(是否在集合内)就用in(),如果是等于查询就用eq(),区间用between()。
.page()才是做分页,page方法中参数传的是MP的一个page对象:设置页码、每页大小、升序降序、排序字段。
@ApiModelProperty(value = "页码", example = "1")
@Min(value = 1, message = "页码不能小于1")
private Integer pageNo = DEFAULT_PAGE_NUM;
@ApiModelProperty(value = "每页大小", example = "5")
@Min(value = 1, message = "每页查询数量不能小于1")
private Integer pageSize = DEFAULT_PAGE_SIZE;
@ApiModelProperty(value = "是否升序", example = "true")
private Boolean isAsc = true;
@ApiModelProperty(value = "排序字段", example = "id")
private String sortBy;