微头条业务流程
微头条业务流程
- 1.用户业务
- 1.1 用户登录业务
- 1.2 获取用户信息服务
- 1.3 用户注册业务
- 2. 微头条页面业务
- 2.1 导航栏回显
- 2.2 显示分页查询首页头条信息(重点!!)
- 动态SQL元素解析
- MyBatis Plus分页插件
- 分页逻辑
- 2.3 根据id查询头条详情(重点!!)
- `LEFT JOIN`操作:
- 乐观锁机制的作用
- 3.文章业务
- 3.1发布文章
- 3.2 修改文章(考虑并发)
- 注意事项
- 3.3 删除文章
- 注意事项
- 3.4显示文章回显(容易被忽略)
1.用户业务
1.1 用户登录业务
思考:
- 用户未注册时登录,数据库没有该用户信息,导致用户名查询失败,返回一个失败的结果。并且提醒用户去注册!
- 用户注册了,数据库有用户信息,查询用户信息过关,下一步进行密码验证,验证的时候,注意密码的格式,因为用户输入的密码是明文,而数据库存储的是
Md5
加密后的密文,在进行密码验证时,需要将明文密码加密后,进行比较。 - 成功,将该用户
Uid
生成一个token
返回给前端,用于用户后续访问业务时,不需要重复进行登陆验证。 - 失败提醒用户密码错误!重搞。
功能概述
此方法用于处理用户登录请求,主要步骤包括:
- 根据提供的用户名查询数据库,获取用户信息。
- 验证用户提供的密码是否与数据库中存储的MD5加密后的密码匹配。
- 如果验证成功,生成JWT Token并返回给调用
流程:
- 数据库查询
- 使用
LambdaQueryWrapper
构建构建一个查询Sql,通过eq
方法指定username
字段与传入的用户名相等。 - 调用
userMapper.selectOne()
执行SQL查询,获取用户记录。
- 使用
- 密码验证
- 检查数据库返回的用户对象是否为空,若为空则返回
USERNAME_ERROR
错误码。 - 若用户对象非空,比较用户提供的明文密码经MD5加密后的值与数据库中存储的密码是否一致。
- 检查数据库返回的用户对象是否为空,若为空则返回
- Token生成与返回
- 密码验证成功后,使用
jwtHelper.createToken()
方法,传入用户UID生成JWT Token。 - 将生成的Token封装至Map中,并通过
Result.ok()
方法构建成功响应,返回给前端。
- 密码验证成功后,使用
注意!
- 密码处理:用户输入的密码应被视为明文,在与数据库存储的密码比较前需先进行MD5加密。
- Token格式:返回前端之前,
Token
格式需要和前端比对是否一致。
1.2 获取用户信息服务
思考:
- 用户登录后,想要查看用户信息,需要通过数据库查询该用户的
Uid
后返回数据的。所以,这个Uid
就是前端携带的token
,当你访问信息时,必须携带token
,此时想要查看业务,就要验证! - 第一关,验证
token
令牌有效期,如果没问题,通过token
获取Uid
。 - 第二关,查询(验证)该
Uid
是否存在于数据库中,存在进行下一步 - 第三关,返回数据时,需要注意,这个数据是前端需要的,所以返回数据的格式也是前端命名好的,保持格式一致!
功能描述
客户端发送请求,提交token请求头,后端根据token请求头获取登录用户的详细信息并响应给客户端进行存储
- 验证
token
是否在有效期内。 - 从
token
中解析出用户ID。 - 根据用户ID查询数据库,获取用户信息。
- 返回用户信息(排除密码)。
流程:
- Token有效性检查
- 使用
jwtHelper.isExpiration(token)
方法检查token
是否已过期。 - 若
token
过期,则直接返回NOTLOGIN
错误码,表示用户未登录状态。
- 使用
- 解析用户UID
- 通过
jwtHelper.getUserId(token)
方法从token
中提取用户UID。
- 通过
- 数据库查询
- 使用
userMapper.selectById(userId)
方法根据用户ID查询用户信息。
- 使用
- 构建响应结果
- 若查询到的用户信息非空,清除用户对象的密码字段,避免泄露敏感信息。
- 注意! 将用户信息封装至Map时,看清楚!存入的键值对是否是前端需要的内容!通过
Result.ok()
方法构建成功响应,返回给前端。
1.3 用户注册业务
思考:
- 注册用户,首先需要查看用户名是否被占用,其次需要对密码进行Md5加密,最后,将用户信息添加到数据库中,返回给前端一个200即可
流程:
- 用户名唯一性校验
- 使用
LambdaQueryWrapper
构建查询条件,通过eq
方法指定username
字段与待注册的用户名相等。 - 调用
userMapper.selectCount()
执行SQL查询,统计符合条件的用户数量。 - 若查询结果大于0,表明用户名已被占用,返回
USERNAME_USED
错误码。
- 使用
- 密码加密
- 在存储用户信息前,使用
MD5Util.encrypt()
方法对用户密码进行MD5加密处理,增强账户安全性。
- 在存储用户信息前,使用
- 数据库插入操作
- 调用
userMapper.insert()
方法将处理后的用户信息插入数据库。 - 打印插入操作影响的行数,用于调试或监控。
- 调用
- 响应构建
- 插入操作完成后,构建成功响应,返回给前端。
2. 微头条页面业务
2.1 导航栏回显
进入新闻首页,查询所有分类并动态展示新闻类别栏位
思考:直接查询分类的所有信息,返回给前端就行,注意返回数据的格式!
方法1:
方法2:
2.2 显示分页查询首页头条信息(重点!!)
客户端:向服务端发送查询关键字,新闻类别,页码数,页大小
服务端:根据条件搜索分页信息,返回:含页码数,页大小,总页数,总记录数,当前页数据等信息,并根据时间降序,浏览量降序排序
思考分析:
- 前端需要返回一大堆信息,看json数据后,发现
pageData
是根据关键字查询的信息pageInfo
显示的是页面的信息
- 分析
pageData
,他是一个[]
包围着很多个{}
,所以是列表嵌套着Map
集合,所以返回前端数据的是一个List《map》对象 - 根据前端请求参数,获取关键字,通过关键字进行查询,注意!查询SQL需要自定义!
- 同时,我
mapper
调用完selectMyPage
方法后返回的结果是一个IPage
类型的数据,然后通过实现类Page
调用这些数据存进一个List《map》的列表中。 - 此时
PageData
的工作完毕,下面是pageInfo
的业务PageInfo
里包含两部分:PageData
和页面的数据,页面数据直接可以get
到,PageInfo
是一个Map
所以直接put
就行了。
动态SQL元素解析
TIMESTAMPDIFF
函数:MySQL特有的函数,用于计算两个日期或时间之间的差值。在这个查询中,它被用来计算每条记录的create_time
与当前时间NOW()
之间的时间差,单位是小时。<if>
标签:MyBatis的动态SQL标签之一,用于根据条件决定是否包含某些SQL片段。- 第一个
<if>
标签检查portalVo.keyWords
是否非空且长度大于0,如果条件成立,则在SQL中添加基于title
字段的模糊搜索条件,搜索包含portalVo.keyWords
的记录。 - 第二个
<if>
标签检查portalVo.type
是否不等于0,如果条件成立,则在SQL中添加基于type
字段的精确匹配条件。
- 第一个
SQL执行逻辑
当selectMyPage
方法被调用时,MyBatis Plus会做以下几件事:
- 根据
page
对象的pageNum
和pageSize
属性,自动修改SQL语句以包含LIMIT子句,从而实现分页查询。 - 根据
portalVo
对象的属性动态生成SQL语句的WHERE子句。 - 执行修改后的SQL语句,获取数据。
- 将查询结果填充到
IPage
对象中,包括当前页的数据记录和分页信息。
MyBatis Plus分页插件
IPage接口与Page类:
IPage<T>
是MyBatis Plus提供的分页接口,它扩展了PageInfo<T>
,包含了分页查询所需的主要信息,如当前页码、每页数量、总页数、总记录数等。Page<T>
是IPage<T>
的一个实现类,用于创建分页查询的实例,可以设置当前页码和每页记录数。
分页逻辑
在MyBatis Plus中,IPage
接口和它的实现类Page
被设计用于处理分页查询。当你创建一个Page
实例并调用Mapper中的分页查询方法时,MyBatis Plus会自动在底层执行分页逻辑,包括SQL语句的修改以适应分页需求。
IPage<Map> selectMyPage(IPage page,@Param("portalVo") PortalVo portalVo);
headlineMapper.selectMyPage(page, portalVo);
这行代码实际上是在调用Mapper接口中的selectMyPage
方法,该方法接受两个参数:一个IPage<Map>
类型的page
对象和一个portalVo
对象,后者包含了分页查询所需的额外参数,如关键词和分类类型。
在Mapper的XML文件中,<select id="selectMyPage">
定义了自定义的SQL语句,其中使用了TIMESTAMPDIFF
函数来计算时间差,并且通过<if>
标签实现了动态SQL,根据portalVo
对象的属性决定是否添加额外的过滤条件。
因此!!当你调用page.getRecords()
时,你实际上是在访问IPage
对象中已经填充的数据记录,这些记录是通过执行自定义SQL语句并应用分页逻辑后得到的。getRecords()
方法返回的是一个List<Map>
,其中每一项Map
代表了一条查询结果,键对应于SQL语句中选择的列名。
业务流程:
- 分页查询
- 创建
IPage<Map>
类型的page
对象,设置当前页码和每页显示的记录数。 - 调用
headlineMapper.selectMyPage(page, portalVo)
执行自定义SQL分页查询,填充page
对象。
- 创建
- 数据提取与封装
- 从
page
对象中提取查询结果,即当前页的数据记录,转换为List<Map>
类型。 - 提取分页信息,包括当前页码、页大小、页总数、总记录数,封装至
Map
对象中。
- 从
- 响应构建
- 将分页数据和信息封装至
Map
对象中,构建为pageInfo
。 - 最终通过
Result.ok()
方法构建成功响应,返回给前端。
- 将分页数据和信息封装至
2.3 根据id查询头条详情(重点!!)
- 用户点击"查看全文"时,向服务端发送新闻id
- 后端根据新闻id查询完整新闻文章信息并返回
- 后端要同时让新闻的浏览量+1
思考分析:
- 由于每一篇文章都有对应的
Hid
,所以查看文章是通过Hid
查询的,前端点击查看全文后,会给后端传入一个hid
,这个hid
就是当前用户点击的新闻id
,直接查询返回一个Map类型的Headline
对象即可。 - 注意!后端要同时让新闻的浏览量+1!要考虑并发问题!如果没有适当的并发控制,可能会导致阅读量更新不准确。
- 并且数据同步是需要更新的,所以还有一步,
new
一个Headline
对象,把当前查询结果的值传入Headline
中,在通过Hid
进行更新。因为乐观锁机制的缘故,需要把Version字段传入Headline
中,这样,并发才能解决!
LEFT JOIN
操作:
- 首先与
news_type
表左连接,连接条件是news_headline
的type
字段等于news_type
的tid
字段。 - 然后与
news_user
表左连接,连接条件是news_headline
的publisher
字段等于news_user
的uid
字段。 - 左连接保证了即使关联表中没有匹配记录,主表的记录也会被返回,同时关联表的字段会被填充为NULL。
乐观锁机制的作用
- 检测并发修改: 当多个用户几乎同时尝试更新同一条记录时,乐观锁机制可以检测到是否有其他事务在当前事务开始后修改了这条记录。这是通过检查版本号(
version
字段)是否与上次读取时相同来实现的。 - 防止数据冲突: 在上述代码中,当一个用户读取头条详情并准备更新阅读量时,如果在此期间另一个用户也修改了同一条记录,乐观锁机制将阻止第一个用户的更新操作,因为第一个用户的版本号不再匹配数据库中的最新版本号。
- 维护数据一致性: 通过在更新操作中包含版本号的检查,乐观锁确保只有当数据未被其他事务修改时,更新才会成功。这样就避免了可能的数据不一致问题,提高了系统的数据完整性和可靠性。
在代码中,乐观锁的实现是通过以下步骤完成的:
- 读取头条详情: 使用
headlineMapper.queryDetailMap(hid)
查询头条详情,其中包括了版本号version
。 - 更新阅读量: 创建
Headline
对象,设置其hid
、version
和pageViews
字段。这里的version
字段就是用于乐观锁检查的版本号。 - 尝试更新数据: 调用
headlineMapper.updateById(headline)
更新头条记录。在MyBatis Plus中,updateById
方法会自动生成包含版本号检查的SQL语句,以确保版本号未变时才执行更新。 - 处理更新结果: 如果更新成功,说明没有其他事务在此期间修改了数据;如果更新失败(通常是由于版本号不匹配),则需要处理这种冲突情况,例如抛出异常或提示用户数据已变更。
业务流程:
- 用户行为触发:
- 用户在前端界面点击某头条新闻的“查看全文”。
- 后端处理请求:
- 后端接收请求,从中提取头条的
hid
(头条ID)。
- 后端接收请求,从中提取头条的
- 查询头条详情:
- 调用
headlineMapper.queryDetailMap(hid)
,执行SQL查询,获取头条的详细信息,包括当前阅读量。
- 调用
- 阅读量更新:
- 根据查询结果,创建
Headline
对象,将阅读量加1,并尝试更新数据库记录,利用版本号检查处理并发问题。
- 根据查询结果,创建
- 响应前端:
- 成功更新后,将头条详情作为响应返回给前端,展示头条全文内容。
3.文章业务
3.1发布文章
- 用户在客户端输入发布的新闻信息完毕后
- 发布前先请求后端的登录校验接口验证登录
- 登录通过则提交新闻信息
- 后端将新闻信息存入数据库
思考:
- 发布文章是在用户登录完成后操作业务,所以发布前需要验证用户登录的
token
,如果验证成功,通过token
生成用户ID存入数据库。 - 注意!提交文章意味着修改文章的创建和更新时间,所以时间也要存入数据库。同时,访问量也需要初始化0,新发表的文章是没有访问量的!
3.2 修改文章(考虑并发)
思考:
- 重点!更新数据首先考虑并发,
- 通过文章的
Hid
查询该文章的Version
,将Version
存入headline
中(这里的headline
是前端传过来的) - 注意!修改文章同时修改了文章时间!把当前时间传入即可
- 用
mapper
进行update
即可。(这里用的是updateById,如果传入的是一个对象,MybatisPlus会自动识别该对象的id字段!)
- 通过文章的
注意事项
- 乐观锁机制:
- 使用版本号进行乐观锁控制,防止并发修改冲突,确保数据一致性。
- 时间戳更新:
- 每次更新头条信息时,都应更新
updateTime
字段,以记录最近一次修改的时间。
- 每次更新头条信息时,都应更新
- 完整性检查:
- 在更新前,确保
headline
对象的所有必填属性都已正确填充,避免因属性缺失导致的更新失败。
- 在更新前,确保
3.3 删除文章
思考:
- 通过前端单击删除按钮后,传入
hid
字段,后端拿到后直接删即可。
注意事项
- 别全删了= =
3.4显示文章回显(容易被忽略)
- 前端先调用登录校验接口,校验登录是否过期
- 登录校验通过后 ,则根据新闻id查询新闻的完整信息并响应给前端
思考:
- 已知前端完成登录校验,只需要通过前端给的
Hid
进行查询,返回Json
数据中,键是headline
!值就是headline
对象!