# nowcoderCommunity
**Repository Path**: Trace001/nowcoder-community
## Basic Information
- **Project Name**: nowcoderCommunity
- **Description**: 仿牛客网社区论坛
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 1
- **Created**: 2023-08-19
- **Last Updated**: 2023-08-19
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
技术架构:
- Spring Boot
- Spring、Spring MVC、MyBatis。
- Redis、Kafka、Elasticsearch
- Spring Security、Spring Actuator
开发工具:
- 构建工具: Apache Maven
- 集成开发工具: IntelliJ IDEA
- 数据库:MySQL、Redis
- 应用服务器:Apache Tomcat
- 版本控制工具: Git
分页:th:href="@{${page.path}(current=${i})}"
# 一、用户增删改查
# 二、社区首页
# 三、发送邮件

```java
@Autowired
private JavaMailSender mailSender;
//发件人
@Value("${spring.mail.username}")
private String from;
/**
* 发送邮件
* @param to 收件人
* @param subject 主题
* @param content 内容
*/
public void sendMail(String to, String subject, String content) {
try {
//得到邮件主体对象
MimeMessage message = mailSender.createMimeMessage();
//构建邮件主体
MimeMessageHelper helper = new MimeMessageHelper(message);
//设置发件人
helper.setFrom(from);
//设置收件人
helper.setTo(to);
//设置主题
helper.setSubject(subject);
//设置内容
helper.setText(content, true);
//发送邮件
mailSender.send(helper.getMimeMessage());
log.info("邮件发送成功...");
} catch (MessagingException e) {
log.error("发送邮件失败:" + e.getMessage());
}
}
```
```java
@Autowired
private MailClient mailClient;
//模板引擎
@Autowired
private TemplateEngine templateEngine;
public void testHtmlMail() {
//获取Thymeleaf模版
Context context = new Context();
//构建参数
context.setVariable("username", "sunday");
//将模版内容转为字符串类型并将参数传入
String content = templateEngine.process("/mail/demo", context);
System.out.println(content);
mailClient.sendMail("1620389556@qq.com", "HTML", content);
}
```
```html
邮件示例
欢迎你, !
```
# 四、开发注册、登录
## 1、注册
- 访问注册页面
- 点击顶部区域内的链接,打开注册页面。
- 提交注册数据
- 通过表单提交数据。
- 服务端验证账号是否已存在、邮箱是否已注册。
- 服务端发送激活邮件。
- 激活注册账号
- 点击邮件中的链接,访问服务端的激活服务。
## 2、生成验证码
使用Kaptcha
- 导入jar包
- 编写Kaptcha配置类-生成随机字符、生成图片
## 3、登录
- 访问登录页面
- 点击顶部区域内的链接,打开登录页面。
- 登录
- 验证账号、密码、验证码。
- 成功时,生成登录凭证,发放给客户端。
- 失败时,跳转回登录页。
- 退出
- 将登录凭证修改为失效状态。
- 跳转至网站首页。
## 4、登录优化(主要检验登录凭证时间、前端根据登录状态改变页面)
- 拦截器示例
- 定义拦截器,实现Handlerlnterceptor
- 配置拦截器,为它指定拦截、排除的路径。
- 拦截器应用
- 在请求开始时查询登录用户:根据存储的Cookie中的凭证号ticket查询对应的登录凭证信息->判断凭证状态、凭证时间是否还有效->有效则查询对应的用户信息,将用户信息存储到模板信息中,前端检验用户是否登录。
- 在本次请求中持有用户数据-在模板视图上显示用户数据
- 在请求结束时清理用户数据

## 5、账号设置
- 上传文件
- 请求:必须是POST请求
- 表单: enctype= “multipart/form-data"
- Spring MVC : 通过MultipartFile处理上传文件
- 开发步骤
- 访问账号设置页面
- 上传头像
- 获取头像
- 修改密码
## 6、检查登录状态
- 使用拦截器
- 在方法前标注自定义注解
- 拦截所有请求,只处理带有该注解的方法
- 自定义注解
- 常用的元注解:
- @Target、@Retention、@Document、@Inherited
- 如何读取注解:
- Method.getDeclaredAnnotations ()
- Method.getAnnotation (Class annotationClass)
```java
/**
* 标记自定义注解:加入这个注解的方法需要登录才能使用
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
}
```
# 五、开发社区核心功能
## 1、过滤敏感词
- 前缀树
- 名称:Trie、字典树、查找树
- 特点:查找效率高,消耗内存大
- 应用:字符串检索、词频统计、字符串排序等。
- 敏感词过滤器
- 定义前缀树
- 根据敏感词,初始化前缀树
- 编写过滤敏感词的方法
特殊符号跳过
```java
/**
* 敏感词过滤
*/
@Slf4j
@Component
public class SensitiveFilter {
/**
* 定义前缀树
*/
private class TrieNode {
// 关键词结束标识
private boolean isKeywordEnd = false;
// 子节点(key是下级字符,value是下级节点)
private Map subNodes = new HashMap<>();
public boolean isKeywordEnd() {
return isKeywordEnd;
}
public void setKeywordEnd(boolean keywordEnd) {
isKeywordEnd = keywordEnd;
}
// 添加子节点
public void addSubNode(Character c, TrieNode node) {
subNodes.put(c, node);
}
// 获取子节点
public TrieNode getSubNode(Character c) {
return subNodes.get(c);
}
}
// 替换符
private static final String REPLACEMENT = "***";
// 根节点
private TrieNode rootNode = new TrieNode();
/**
* 当前bean被容器初始化时调用:初始化前缀树
*/
@PostConstruct
public void init() {
try (
//获取敏感词文件字节流
InputStream is = this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt");
//字节流转字符流再转成缓冲流
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
) {
//读取文件敏感词
String keyword;
while ((keyword = reader.readLine()) != null) {
// 添加到前缀树
this.addKeyword(keyword);
}
} catch (IOException e) {
log.error("加载敏感词文件失败: " + e.getMessage());
}
}
/**
* 将一个敏感词添加到前缀树中
* @param keyword
*/
private void addKeyword(String keyword) {
TrieNode tempNode = rootNode;//根节点
//遍历字符串字符
for (int i = 0; i < keyword.length(); i++) {
char c = keyword.charAt(i);
//获取子节点
TrieNode subNode = tempNode.getSubNode(c);
//子节点为空:即当前没有子节点是存储着c
if (subNode == null) {
// 初始化子节点
subNode = new TrieNode();
tempNode.addSubNode(c, subNode);
}
// 指向子节点,进入下一轮循环
tempNode = subNode;
// 设置结束标识
if (i == keyword.length() - 1) {
tempNode.setKeywordEnd(true);
}
}
}
/**
* 过滤敏感词
* @param text 待过滤的文本
* @return 过滤后的文本
*/
public String filter(String text) {
if (StringUtils.isBlank(text)) {
return null;
}
// 指针1:指向前缀树
TrieNode tempNode = rootNode;
// 指针2:慢指针最终指向过滤词汇的首部
int begin = 0;
// 指针3:快指针最终指向敏感自会尾部
int position = 0;
// 结果
StringBuilder sb = new StringBuilder();
while (position < text.length()) {
char c = text.charAt(position);
// 跳过符号
if (isSymbol(c)) {
// 若指针1处于根节点,将此符号计入结果,让指针2向下走一步
if (tempNode == rootNode) {
sb.append(c);
begin++;
}
// 无论符号在开头或中间,指针3都向下走一步
position++;
continue;
}
// 检查下级节点
tempNode = tempNode.getSubNode(c);
if (tempNode == null) {
// 以begin开头的字符串不是敏感词
sb.append(text.charAt(begin));
// 进入下一个位置
position = ++begin;
// 重新指向根节点
tempNode = rootNode;
} else if (tempNode.isKeywordEnd()) {
// 发现敏感词,将begin~position字符串替换掉
sb.append(REPLACEMENT);
// 进入下一个位置
begin = ++position;
// 重新指向根节点
tempNode = rootNode;
} else {
// 检查下一个字符
if(position 0x9FFF);
}
}
```
## 2、发布帖子
- AJAX
Asynchronous JavaScript and XML
- 异步的JavaScript与XML,不是一门新技术,只是一个新的术语。
- 使用AJAX,网页能够将增量更新呈现在页面上,而不需要刷新整个页面。
- 虽然X代表XML,但目前JSON的使用比XML更加普遍。
- https://developer.mozilla.org/zh-CN/docs/Web/Guide/AJAX。
- 示例
- 使用jQuery发送AJAX请求。
- 实践
- 采用AJAX请求,实现发布帖子的功能。
```java
public int addDiscussPost(DiscussPost post) {
//传入的帖子为空
if (post == null) {
throw new IllegalArgumentException("参数不能为空!");
}
// 转义HTML标记:题目和内容出现一些标签将其转义
post.setTitle(HtmlUtils.htmlEscape(post.getTitle()));
post.setContent(HtmlUtils.htmlEscape(post.getContent()));
// 过滤敏感词
post.setTitle(sensitiveFilter.filter(post.getTitle()));
post.setContent(sensitiveFilter.filter(post.getContent()));
//执行新增帖子
return discussPostMapper.insertDiscussPost(post);
}
```
```java
public String addDiscussPost(String title, String content) {
//获取当前用户数据
User user = hostHolder.getUser();
if (user == null) {
//未登录
return CommunityUtil.getJSONString(403, "你还没有登录哦!");
}
//封装帖子对象
DiscussPost post = new DiscussPost();
post.setUserId(user.getId());
post.setTitle(title);
post.setContent(content);
post.setCreateTime(new Date());
//存入数据库
discussPostService.addDiscussPost(post);
// 报错的情况,将来统一处理.
return CommunityUtil.getJSONString(Code.OK, "发布成功!");
}
```
## 3、帖子详情
- DiscussPostMapper
- DiscussPostService
- DiscussPostControlle
- index.html
- 在帖子标题上增加访问详情页面的链接。
- discuss-detail.html
- 处理静态资源的访问路径
- 复用index.html的header区域
- 显示标题、作者、发布时间、帖子正文等内容
## 4、显示评论
- 数据层
- 根据实体查询一页评论数据。
- 根据实体查询评论的数量。
- 业务层
- 处理查询评论的业务。
- 处理查询评论数量的业务。
- 表现层
- 显示帖子详情数据时,
- 同时显示该帖子所有的评论数据。
```java
public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model, Page page) {
// 查询出帖子
DiscussPost post = discussPostService.findDiscussPostById(discussPostId);
//将数据给到模板
model.addAttribute("post", post);
// 查询出帖子的作者
User user = userService.findUserById(post.getUserId());
model.addAttribute("user", user);
// 评论分页信息
page.setLimit(5);
page.setPath("/discuss/detail/" + discussPostId);
page.setRows(post.getCommentCount());//这个帖子下的评论总数
// 评论: 给帖子的评论
// 回复: 给评论的评论
// 评论列表:给帖子的评论
List commentList = commentService.findCommentsByEntity(
Code.ENTITY_TYPE_POST, post.getId(), page.getOffset(), page.getLimit());
// 评论VO列表
List