赞
踩
1.登录注册功能:使用kaptcha去生成验证码,使用邮件完成注册,Redis优化验证码的保存,解决分布式session问题,使用拦截器拦截用户请求,将用户信息绑定在ThreadLocal上
2.构建Trie数据结构,实现对发表帖子评论的敏感词过滤
3.利用AJAX支持对帖子评论,也支持对评论进行回复
4.利用AOP对service的业务代码实现日志记录
5.利用Redis的zset并结合Redis实现点赞关注的功能
6.点赞关注后的系统通知,实时性不需要特别高,使用kafka实现异步的发送系统通知
7.使用ElasticSearch实现对帖子的搜索功能,以及结果的高亮显示
字段 | 类型 | 备注 |
---|---|---|
id | int | 主键,自增 |
username | varchar | 用户名,创建索引 |
password | varchar | 用户密码 |
salt | varchar | 加密盐值 |
varchar | 用户邮箱,创建索引 | |
type | int | 用户类型:0 普通、1 管理员、2 版主 |
status | int | 用户状态:0 未激活、1 已激活 |
activation_code | varchar | 激活码 |
header_url | varchar | 用户头像地址 |
create_time | timestamp | 注册时间 |
字段 | 类型 | 备注 |
---|---|---|
id | int | 主键、自增 |
user_id | int | 评论的用户 id,创建索引 |
entity_id | int | 评论实体 id,创建索引 |
entity_type | int | 评论实体类型:1 帖子评论、2 评论回复 |
target_id | int | 评论目标 id |
content | text | 评论内容 |
status | int | 评论状态:0 有效、1 无效 |
create_time | timestamp | 评论发表时间 |
字段 | 类型 | 备注 |
---|---|---|
id | int | 主键、自增 |
user_id | int | 发帖的用户 id,创建索引 |
title | varchar | 帖子表标题 |
content | text | 帖子内容 |
type | int | 帖子类型:0 普通、1 置顶 |
comment_count | int | 评论数量 |
status | int | 帖子状态:0 普通、1 精华、2 拉黑 |
create_time | timestamp | 评论发表时间 |
字段 | 类型 | 备注 |
---|---|---|
id | int | 主键、自增 |
user_id | int | 登录用户 id |
ticket | varchar | 登录凭证,随机字符串 |
status | int | 登录状态:0 有效、1 无效 |
expired | timestamp | 过期时间 |
字段 | 类型 | 备注 |
---|---|---|
id | int | 主键、自增 |
from_id | int | 发消息的 id,创建索引 |
to_id | int | 收消息的 id,创建索引 |
conversation_id | varchar | 会话 id,由通信双方 id 拼接,创建索引 |
content | text | 消息内容 |
status | int | 消息状态:0 未读、1 已读、2 删除 |
create_time | timestamp | 消息发送时间 |
构建 SpringBoot 的 maven 项目,引入 mysql 和 mybatis 依赖。
在 application.properties
配置文件中:
创建 community 数据库和数据库表。
用户相关操作:
@Mapper
注解<sql id = "xxx">
标签,通过 <include refid="xxx"/>
引用。功能拆分:开发社区首页,显示前 10 个帖子。开发分页组件,分页显示所有帖子。
用到的表是 discuss_post 数据库表,包括帖子 id、发帖人 id、标题、内容、类型、状态、发帖时间、评论数量(为了提高效率,避免关联查询,因此冗余存储)、分数(用于进行热度排名)。
帖子相关操作:
@Mapper
注解。
<if>
里使用,必须使用 @Param
加别名。disscusspost-mapper.xml
。
where status != 2
拉黑的帖子不展现。<if test="userId!=0">
userID 为 0 时不使用,按照类型,发帖时间排序。创建 DiscussPostService 类,可以分页查询帖子和帖子数量。
创建 UserService 类,实现根据 id 查询用户功能,因为显示帖子时不显示用户 id,而是显示用户名。
把静态资源 css、html、img、js 放到 static 目录下。
把模板 mail、site、index.html 放到 template 目录下。
创建 HomeController,getIndexPage
方法,用 map 集合把帖子和用户封装到一起。
修改 index.html
,使用 <th:text="${map.xxx.xxx}"
动态替换。
【问题】使用帖子关联查询用户时,给查询用户的 findUserById
方法传入了帖子的 getId
方法,应该是 getUserId
方法。
创建 Page 实体类,封装分页信息,包括当前页码、显示限制、帖子总数、查询路径等。显示的起始页不能小于 1,最大页不能超过 total。
在 index.html
中,当 page.rows > 0
时显示分页信息。
如果 page.current
等于 1 或 page.total
,代表是首页或末页,此时不能点击上一页和下一页,用 disabled
属性实现。
引入 spring-boot-starter-mail
依赖。
在配置文件配置主机、端口、发送邮箱、授权码等。
创建 MailClient 类,调用 JavaMailSender 发送邮件。
使用 thymeleaf 发送 HTML 邮件,调用 TemplateEngine 把信息封装到 HTML 模板。
【问题】发送邮件成功但没接收到,在垃圾箱中可找到。
把 register.html 地址关联到首页的注册 href 属性。
设置域名、创建 CommunityUtil 工具类,在工具类创建生产随机字符串和 MD5 加密方法。
创建 LoginController,创建 getRegisterPage
方法,跳转注册页面。
在 UserService 中创建 register
方法,判断注册信息合规后插入数据库,发送激活邮件。
在 LoginController 创建 register
方法,调用 UserService 的 register
方法。
创建接口 CommunityConstant,定义激活码的三种状态,成功、重复、失败,让 UserService 和 LoginController 实现该接口。
点击激活邮件的 url 【本地服务器的url】后,服务器通过 LoginController 的 activation
方法查询数据库用户,如果 url 中的激活码和设置的一样,就把用户 status 改为 1。
在 pom.xml
导入 kaptcha 的 jar 包。
创建配置类 KaptchaConfig,设置验证码的大小、范围、长度等。
在 LoginController 类新增 getKaptcha
方法生成验证码图片。
在 login.html
中,将刷新验证码的链接绑定 refresh_kaptcha
方法,通过 id 选择器获取 img 组件,重新访问 getKaptcha
方法生成验证码图片。
【问题】由于访问同一个生成验证码路径,需要在 url 参数加上一个随机数字,保证会重新请求获取新图片。
登录成功时,需要生成一个登录凭证发送给客户端。凭证可以在多个业务中连续地验证用户的登陆状态,凭证信息存储在 login_ticket 数据库表中,status 的 0 和 1 表示有效和无序,expire 表示过期时间。
创建对应 login_ticket 表的 LoginTicket 实体类,对应 login_ticket 数据库表。
创建 LoginTicketMapper 接口,通过 @Insert
、@Select
、@Update
注解来插入、查询、更新凭证。
在 UserServce
login
方法,验证账户合规后将凭证信息插入数据库,添加登录凭证到 map 中。logout
方法,将对应凭证设为无效。在 LoginController
login
方法,判断验证码正确后调用 UserServce 的 login
方法,如果 map 包含 ticket 代表登录成功,重定向跳转首页,否则添加错误信息并跳回登录页。logout
方法,判断验证码正确后调用 UserServce 的logout
方法,跳转至登录页。在 login.html
绑定登录链接,index.html
绑定退出登录链接。
【问题】登录成功后,创建了凭证,但忘记将凭证信息插入数据库。
创建 CookieUtil 工具类,通过 name 查询对应 cookie 的 value。
在 UserService 中新增 findLoginTicket
方法,根据 ticket 查询 LoginTicket。
创建 HostHolder 类用来模拟 session 的功能,利用 ThreadLocal 实现,存储用户信息。
创建 LoginTicketInterceptor 拦截器,实现 HandlerInterceptor 接口。
preHandle
方法中通过 CookieUtil 的 getValue
方法查询是否有凭证 cookie,如果有则通过 UserService 的 findloginTicket
方法查询用户 ID,再通过用户 ID 查询用户。最后将用户放入 hostHolder 中。postHandle
方法中通过 hostHolder 的 get
方法获取用户,并将其存入视图中。afterCompletion
方法中清除 hostHolder 中存放的用户信息。创建 WebMvcConfig 配置类,实现 WebMvcConfigurer接口,配置 LoginTicketInterceptor,拦截除了静态资源之外的所有路径。
在 UserService 新增 updateHeader
方法,更改指定用户的头像。
创建 UserController
新增 getSettingPage
方法访问账户设置 setting.html
,并在 index.html
的账号设置按钮关联该链接。
新增 uploadHeader
方法更新用户头像,如果上传出现错误将错误信息存在 Model 对象中。
如果没有错误,生成一个文件对象 dest,利用 MultipartFile 接口的 transferTo
方法将用户上传文件导入 dest,并从 hostHolder 中取出用户,更新用户的头像路径。
新增 getHeader
方法获取用户头像,利用文件输入流读取图片数据,利用 HttpServletResponse 的字节输出流再进行输出。
调整 setting.html
的 form 表单, method=“post”,enctype=“multipart/form-data”,并设置提交路径。
在 UserService 中新增 changePassword
方法,判断原密码是否正确,正确则修改密码并返回 1,否则返回 0。
在 UserController 中新增 changePassword
方法,根据 UserService 的 changePassword
方法的返回值判断原密码是否成功修改,封装为 JSON 数据并返回。
在 setting.html
中
changePassword
方法发送 POST 请求,得到 JSON 数据并解析,如果状态码为 0 提示错误,如果状态码为 1 弹出修改成功提示。【问题】js 的虚拟路径问题,需要加上 ../
。
【问题】使用 ajax 请求时,表单按钮类型必须是 button,不能是 submit,否则 405 报错。
【问题】使用 ajax 请求时,Controller 中方法的返回值必须是 JSON 数据,并且需要加上 @ResponseBody
。
【问题】使用 ajax 请求时,回调函数需要先对返回的 JSON 数据进行解析再使用。
利用拦截器,实现只处理带有自定义注解的方法,防止用户在未登录情况下通过 url 访问没有权限的页面。
创建 @LoginRequired
自定义注解,作用范围在方法上,有效期为运行时。
在 UserController 中需要在登录状态下调用的方法,访问设置页面、修改密码、上传头像等加上自定义注解。
创建 LoginRequiredInterceptor 拦截器,在 preHandle
方法中判断方法是否加了 @LoginRequired
注解,如果加了注解并且此时从 hostHolder 中获取不到用户则拒绝访问。
在 WebMvcConfig 配置类配置 LoginRequiredInterceptor,拦截除了静态资源之外的所有路径。
利用字典树数据结构解决。
三个指针,一个指针首先指向前缀树根节点;另外两个指针指向待过滤信息的头部。交替移动对比,不匹配时候放入结果集,匹配的时候替换为指定符号。
创建 SensitiveFilter 类
@PostConstruct
注解,在构造方法执行后初始化字典树。filter
方法,利用双指针进行匹配,过滤敏感词。【问题】判断子节点空时,直接添加了一个 new 的子节点,没有将对象赋值给子节点变量。
引入 fastjson 依赖,在 CommunityUtil 中新增 getJSONString
方法封装 JSON 信息。
在 DisscussPostMapper 接口新增 insertDiscussPost
方法,并在 disscusspost-mapper.xml
配置 insert 语句。
在 DiscussPostService 新增 addDiscussPost
方法调用 DisscussPostMapper 的 insertDiscussPost
方法,其中需要进行对标题内容和发帖内容进行 HTML 转义以及过滤敏感词。
创建 DiscussPostController 类,新增 addDiscussPost
方法,调用 DiscussPostService 的 addDiscussPost
方法发帖。
在 index.html
中为发帖按钮绑定函数,利用 Ajax 向 DiscussPostController 的 addDiscussPost
方法发送 POST 请求。
在 DisscussPostMapper 接口新增 selectDiscussPostById
方法,在 disscusspost-mapper.xml
配置 select 语句。
在 DiscussPostService 新增 findDiscussPostById
方法调用 DisscussPostMapper 的 selectDiscussPostById
方法。
在 DiscussPostController 新增 getDiscussPost
方法,调用 DiscussPostService 的 findDiscussPostById
方法查询帖子内容,将 DiscussPost 对象和 User 对象(通过 userId 查询,不在 DAO 层关联查询)数据存放到 Model 对象,返回模板 discuss-detail
。
在 discuss-detail.html
取出 Model 对象存放的数据绑定到对应组件显示。
创建 comment 表对应的实体类 Comment。
创建 CommentMapper 接口
selectCommentsByEntity
方法,根据实体查询一页的评论数据。selectCountByEntity
方法,根据实体查询评论的数量。comment-mapper.xml
配置 select 语句。创建 CommentService 类
findCommentByEntity
方法,调用 CommentMapper 的 selectCommentByEntity
方法。findCommentCount
方法,调用 CommentMapper 的 selectCountByEntity
方法。在 DiscussPostController 的 getDiscussPost
方法中增加查询帖子评论和回复的逻辑,将结果存储在 Model 对象。
【问题】sql 的 xml 文件中绑定参数时,应传入实体类属性名,拼错成数据库字段名(entityId 写成 entity_id)。
在 CommentMapper 接口新增 insertComment
方法,添加评论数据,在 comment-mapper
配置对应 sql。
在 DiscussPostMapper 接口新增 updateCommentCount
方法,增加评论数量,在 discusspost-mapper
配置对应 sql。
在 DiscussPostService 类新增 updateCommentCount
方法,调用 DiscussPostMapper 的 updateCommentCount
方法。
在 CommentService 类新增 addComment
方法,调用 CommentMapper 的 insertComment
新增评论,并调用 DiscussPostService 的 updateCommentCount
更新评论数量,使用 @Transactional
注解保证事务。
创建 CommentController 类,新增 addComment
方法,从 hostHolder 获取用户信息,然后调用 CommentService 的 addComment
方法添加评论。
【问题】sql 的 xml 文件中绑定参数时,应传入实体类属性名,拼错成数据库字段名(entityId 写成 entity_id)。
创建对应 message 表的实体类 Message。
创建 MessageMapper 接口,增加查询会话列表、会话数量、私信列表、私信数量、未读私信数量等方法,在 message-mapper.xml
中配置对应的 sql。
创建 MessageService,调用 MessageMapper 中的方法。
创建 MessgaeController
getLetterList
方法,将会话列表信息存储到 Model 对象,返回 letter
视图。getLetterDetail
方法,将每个会话具体的私信信息存储到 Model 对象,返回 letter-datail
视图。在 MessageMapper
insertMessage
方法插入私信记录,在 message-mapper.xml
配置 insert 语句。updateMessgae
方法修改私信状态,在 message-mapper.xml
配置 update 语句,利用 foreach 动态 sql。在 MessageService
addMessage
发送私信方法,过滤敏感词后,调用 MessageMapper 的 insertMessage
。readMessage
方法读取信息,调用MessageMapper 的 updateMessgae
更新私信的状态为 1。在 MessageController
getLetterIds
方法,将私信集合中未读私信的 id 添加到 List 集合并返回,在 getLetterDetail
方法调用该方法设置已读。sendLetter
发送私信方法,设置私信信息后调用 MessageService 的 addMessage
发送。在 HomeController 中增加 getErrorPage
方法,返回错误页面。
创建 ExceptionAdvice 类
@ControllerAdvice
注解,表示该类是 Controller 的全局配置类。handleException
方法,加上 @ExceptionHandler
注解,该方法在 Controller 出现异常后调用,处理捕获异常。如果是异步请求返回一个 JSON 数据,否则重定向至 HomeController 的 getErrorPage
方法。在 pom.xml
引入 aspectj 的依赖。
创建 ServiceLogAspect 类,添加 @Aspect
切面注解,配置切入点表达式,拦截所有 service 包下的方法,利用 @Before
记录日志。
创建 RedisKeyUtil 工具类
:
以及实体获得赞的 key 前缀常量 like:entity
。getEntityLikeKey(int entityType,int entityId)
方法,通过实体类型和实体 id 生成对应实体获得赞的 key。创建业务层的 LikeService 类
like
点赞方法,首先通过 RedisKeyUtil 工具类的 getEntityLikeKey
方法获得实体点赞的 key,然后通过 RedisTemplate 对象对 set 集合的 isMember
方法查询 userId 是否存在于对应 key 的 set 集合中,如果存在则移除出点赞的用户集合,如果不存在则添加到点赞的用户集合。findEntityLikeCount
方法查询实体的点赞数量,通过调用 set 集合的 size
方法查询元素个数。findEntityLikeStatus
方法查询某用户对某实体的点赞状态,逻辑如 like
方法,通过 set 集合的 isMember
方法实现。创建表现层的 LikeController 类
like
点赞方法,调用业务层的 like
方法进行点赞、调用 findEntityLikeCount
和 findEntityLikeStatus
查询点赞数量和点赞状态,封装到 map 集合,然后通过工具类封装成 JSON 数据返回。(更新首页帖子点赞数量)在表现层的 HomeController 类
getIndexPage
方法在通过 LikeService 类的方法获得点赞数量,存储到 map 集合。对点赞功能进行重构
在 RedisUnitl 工具类
like:user
getUserLikeKey(int userId)
方法,通过用户 id 生成对应用户获得赞的 key。在 LikeService 中
like
方法,在参数列表中加入 entityUserId 表示被点赞用户的 id,用来更新用户的被点赞数量。
execute
方法实现事务,保证被点赞用户点和点赞用户的数据更新一致。通过 isMember
方法查询用户的点赞状态,之后通过 mutli
方法开启事务。remove
方法将当前用户从点赞用户的集合中移除,调用 decrement
方法将被点赞用户的被点赞数减 1;当用户未点赞时,调用 add
方法将当前用户添加到点赞用户的集合,调用 increment
方法将被点赞用户的被点赞数加 1。findUserLikeCount
方法,以用户 id 作为 key,调用 get
方法查询用户所获得的点赞数。在 LikeController 中给 like
方法增加 entityUserId 参数即可。
在 RedisUnitl 工具类
followee
和 follower
getFolloweeKey(int userId, int entityType)
方法,通过用户 id 和实体类型生成用户关注实体的 key。getFollowerKey(int entityType, int entityId)
方法,通过实体类型和实体 id 生成实体用户粉丝的 key。创建业务层的 FollowService 类
新增
follow
方法,当用户关注某实体时,
add
方法将当前实体 id 和时间作为 value 和 score加入用户的关注集合。add
方法将当前用户 id 和时间作为 value 和 score 加入实体的粉丝集合。新增
unfollow
方法,当用户取消关注某实体时,
remove
方法将当前实体从用户的关注集合移除。remove
方法将用户从实体的粉丝集合移除。在业务层的 FollowService 类
findFolloweeCount
方法,调用 zset 的 zcard
方法查询某用户关注的实体数量。findFollowerCount
方法,调用 zset 的 zcard
方法查询某实体的粉丝数量。hasFollowed
方法,根据 zset 的 zscore
方法返回值查询当前用户是否关注某实体。在 UserController 中新增 getProfilePage
方法获取个人主页。
findUserLikeCount
查询用户获赞数,并添加到 Model 中。findFolloweeCount
、findFollowerCount
、hasFollowed
方法分别查询关注数量、粉丝数量、用户是否关注三项信息并添加到 Model 对象中存储。在业务层的 FollowService 类
findFollowees
方法,查询用户关注列表,主要通过 zset 的 reverseRange
获取 value 即关注用户的 userId,再查询出其 user,之后通过 score
获取关注时间,存入 map 集合,将 map 添加到 list 列表返回。findFollowers
方法,查询用户粉丝列表,主要通过 zset 的 reverseRange
获取 value 即粉丝的 userId,再查询出其 user,之后通过 score
获取关注时间,存入 map 集合,将 map 添加到 list 列表返回。在表现层的 FollowController 类
getFollowees
方法,获取关注列表,存入 Model 对象。getFollowers
方法,获取粉丝列表,存入 Model 对象。存储验证码
在 RedisUntil 工具类
kaptcha
getKaptchaKey
方法,通过一个用户凭证(由于未登录,利用 cookie 实现)获得对应验证码的 key 值(利用 string 存储验证码)。在表现层的 LoginController 类
getKaptcha
方法,将验证码存入 redis,key 值是当前随机生成的一个字符串,同时将该字符串存入 cookie。login
方法,从 cookie 中获得随机字符串,生成验证码的 key 值,然后获取对应的 value 值即验证码。存储登录凭证
在 RedisUntil 工具类
ticket
getTicketKey
方法,通过字符串获得登录凭证的对应 key 值(利用 string 存储)。在业务层的 UserService 类
login
方法,将登录凭证存入 redis 中。logout
方法,先从 redis 中获取登录凭证对象,将状态设为无效再重新存储进 redis。findLoginTicket
方法,根据 ticket 字符串获得对应登录凭证的 key,然后从 redis 查询登录凭证。缓存用户信息
在 RedisUntil 工具类
user
getUserKey
方法,通过用户 id 获得用户的对应 key 值(利用 string 存储)。在业务层的 UserService 类
getCache
,从缓存获取用户信息。initCache
,从 MySQL 查询用户信息并存入 redis。clearCache
,用户信息变更(更新头像,激活)时清除缓存。findUserById
方法,首先调用 getCache
从缓存获取用户信息,如果获取为 null 则调用 initCache
。在项目中,会有一些不需要实时执行但是是非常频繁的操作或者任务,为了提升网站的性能,可以使用异步消息的形式进行发送,再次消息队列服务器kafka来实现。
项目中的搜索服务采用的是ES来实现。ES分片
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。