项目实战-仿牛客网社区开发

仿牛客网社区论坛

项目名称:仿牛客网社区论坛

项目开发语言:JAVA

开发环境:Windows7+、Centos7

集成开发工具:IntelliJ IDEA 2019

文件传输:Xshell 5

版本控制工具:Git

数据库可视化工具:Navicat

数据库系统:MySQL 5.5、Redis 6.2

应用服务器:Apache Tomcat 8.x

构建工具:Apache Maven 3.x

项目核心技术:Spring、SpringMVC、MyBatis、SpringBoot、Redis、Kafka、Elasticsearch、Spring security、Spring Actuator

项目核心功能:SSM—-社区首页开发

​ SpringBoot—-社区登录模块、过滤敏感词、事务管理等.

​ Redis—–整合springboot,高性能存储.

​ Kafka——构建TB级别异步消息系统.

​ Elasticsearch—–开发社区搜索功能.

​ 项目进阶—–整合spring security,构建高效安全的企业业务.

配置文件application.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# ServerProperties
server.port=8080
server.servlet.context-path=/community

# ThymeleafProperties
spring.thymeleaf.cache=false

# DataSourceProperties
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/community?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=30000

# MyBatisProperties
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.tzd.community.entity
mybatis.configuration.use-generated-keys=true
mybatis.configuration.map-underscore-to-camel-case=true

# logger
#logging.level.com.tzd.community=debug
#logging.file.path=h:/community/community.log

# MailProperties
spring.mail.host=smtp.sina.com
spring.mail.port=465
spring.mail.username=tianzedeng@sina.cn
spring.mail.password=a39cbaa2835b8880
spring.mail.protocol=smtps
spring.mail.properties.mail.smtp.ssl.enable=true

# community
community.path.domain=http://localhost:8080
community.path.upload=h:/project/data/upload

# RedisProperties
spring.redis.database=11
spring.redis.host=localhost
spring.redis.port=6379

# 注意:一定要关闭虚拟机的防火墙!!!!
# KafkaProperties
spring.kafka.bootstrap-servers=192.168.66.3:9092
spring.kafka.consumer.group-id=test-consumer-group
spring.kafka.consumer.enable-auto-commit=true
spring.kafka.consumer.auto-commit-interval=3000

# ElasticsearchProperties(注意springboot和es的版本问题)
spring.data.elasticsearch.cluster-name=tzd
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300

spring.main.allow-bean-definition-overriding=true

# TaskExecutionProperties
spring.task.execution.pool.core-size=5
spring.task.execution.pool.max-size=15
spring.task.execution.pool.queue-capacity=100

# TaskSchedulingProperties
spring.task.scheduling.pool.size=5

# QuartzProperties
spring.quartz.job-store-type=jdbc
spring.quartz.scheduler-name=communityScheduler
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
spring.quartz.properties.org.quartz.jobStore.isClustered=true
spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
spring.quartz.properties.org.quartz.threadPool.threadCount=5

# wk
wk.image.command=e:/wkhtmltopdf/bin/wkhtmltoimage
wk.image.storage=h:/project/data/wk-images

# qiniu
qiniu.key.access=Y7cn8X71N5ujSHSb71ZZrQTrSzmCGbajvuAWqh5s
qiniu.key.secret=4GzMiXe0L9rkgzUzaF4661PlmgWMheuLxEsBUvNX
qiniu.bucket.header.name=tianzedeng
qiniu.bucket.header.url=http://raiibt9ye.hb-bkt.clouddn.com
qiniu.bucket.share.name=tianzhedeng
qiniu.bucket.share.url=http://raijyoyfg.hb-bkt.clouddn.com

# caffine
caffeine.posts.max-size=15
caffeine.posts.expire-seconds=180

pom.xml依赖文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.tzd.community</groupId>
<artifactId>community</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>community</name>
<description>community</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>7.2.23</version>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.8.0</version>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
<configuration>
<skipTests>
true
</skipTests>
</configuration>
</plugin>
</plugins>
</build>

</project>

社区首页开发

首页index.html代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<!--访问该页面时,在此处生成CSRF令牌-->
<!--<meta name="_csrf" th:content="${_csrf.token}">
<meta name="_csrf_header" th:content="${_csrf.headerName}" >-->

<link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" th:href="@{/css/global.css}" />
<title>牛客网-首页</title>
</head>
<body>
<div class="nk-container">
<!-- 头部 -->
<header class="bg-dark sticky-top" th:fragment="header">
<div class="container">
<!-- 导航 -->
<nav class="navbar navbar-expand-lg navbar-dark">
<!-- logo -->
<a class="navbar-brand" href="#"></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<!-- 功能 -->
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" th:href="@{/index}">首页</a>
</li>
<li class="nav-item ml-3 btn-group-vertical" th:if="${loginUser!=null}">
<a class="nav-link position-relative" th:href="@{/letter/list}">消息<span class="badge badge-danger" th:text="${allUnreadCount!=0?allUnreadCount:''}">12</span></a>
</li>
<li class="nav-item ml-3 btn-group-vertical" th:if="${loginUser==null}">
<a class="nav-link" th:href="@{/register}">注册</a>
</li>
<li class="nav-item ml-3 btn-group-vertical" th:if="${loginUser==null}">
<a class="nav-link" th:href="@{/login}">登录</a>
</li>
<li class="nav-item ml-3 btn-group-vertical dropdown" th:if="${loginUser!=null}">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img th:src="${loginUser.headerUrl}" class="rounded-circle" style="width:30px;"/>
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item text-center" th:href="@{|/user/profile/${loginUser.id}|}">个人主页</a>
<a class="dropdown-item text-center" th:href="@{/user/setting}">账号设置</a>
<a class="dropdown-item text-center" th:href="@{/logout}">退出登录</a>
<div class="dropdown-divider"></div>
<span class="dropdown-item text-center text-secondary" th:utext="${loginUser.username}">nowcoder</span>
</div>
</li>
</ul>
<!-- 搜索 -->
<form class="form-inline my-2 my-lg-0" method="get" th:action="@{/search}">
<input class="form-control mr-sm-2" type="search" aria-label="Search" name="keyword" th:value="${keyword}"/>
<button class="btn btn-outline-light my-2 my-sm-0" type="submit">搜索</button>
</form>
</div>
</nav>
</div>
</header>

<!-- 内容 -->
<div class="main">
<div class="container">
<div class="position-relative">
<!-- 筛选条件 -->
<ul class="nav nav-tabs mb-3">
<li class="nav-item">
<a th:class="|nav-link ${orderMode==0?'active':''}|" th:href="@{/index(orderMode=0)}">最新</a>
</li>
<li class="nav-item">
<a th:class="|nav-link ${orderMode==1?'active':''}|" th:href="@{/index(orderMode=1)}">最热</a>
</li>
</ul>
<button type="button" class="btn btn-primary btn-sm position-absolute rt-0" data-toggle="modal" data-target="#publishModal" th:if="${loginUser!=null}">我要发布</button>
</div>
<!-- 弹出框 -->
<div class="modal fade" id="publishModal" tabindex="-1" role="dialog" aria-labelledby="publishModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="publishModalLabel">新帖发布</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<label for="recipient-name" class="col-form-label">标题:</label>
<input type="text" class="form-control" id="recipient-name">
</div>
<div class="form-group">
<label for="message-text" class="col-form-label">正文:</label>
<textarea class="form-control" id="message-text" rows="15"></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="publishBtn">发布</button>
</div>
</div>
</div>
</div>
<!-- 提示框 -->
<div class="modal fade" id="hintModal" tabindex="-1" role="dialog" aria-labelledby="hintModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="hintModalLabel">提示</h5>
</div>
<div class="modal-body" id="hintBody">
发布完毕!
</div>
</div>
</div>
</div>

<!-- 帖子列表 -->
<ul class="list-unstyled">
<li class="media pb-3 pt-3 mb-3 border-bottom" th:each="map:${discussPosts}">
<a th:href="@{|/user/profile/${map.user.id}|}">
<img th:src="${map.user.headerUrl}" class="mr-4 rounded-circle" alt="用户头像" style="width:50px;height:50px;">
</a>
<div class="media-body">
<h6 class="mt-0 mb-3">
<a th:href="@{|/discuss/detail/${map.post.id}|}" th:utext="${map.post.title}">备战春招,面试刷题跟他复习,一个月全搞定!</a>
<span class="badge badge-secondary bg-primary" th:if="${map.post.type==1}">置顶</span>
<span class="badge badge-secondary bg-danger" th:if="${map.post.status==1}">精华</span>
</h6>
<div class="text-muted font-size-12">
<u class="mr-3" th:utext="${map.user.username}">寒江雪</u> 发布于 <b th:text="${#dates.format(map.post.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18</b>
<ul class="d-inline float-right">
<li class="d-inline ml-2"><span th:text="${map.likeCount}">11</span></li>
<li class="d-inline ml-2">|</li>
<li class="d-inline ml-2">回帖 <span th:text="${map.post.commentCount}">7</span></li>
</ul>
</div>
</div>
</li>
</ul>
<!-- 分页 -->
<nav class="mt-5" th:if="${page.rows>0}" th:fragment="pagination">
<ul class="pagination justify-content-center">
<li class="page-item">
<a class="page-link" th:href="@{${page.path}(current=1)}">首页</a>
</li>
<li th:class="|page-item ${page.current==1?'disabled':''}|">
<a class="page-link" th:href="@{${page.path}(current=${page.current-1})}">上一页</a></li>
<li th:class="|page-item ${i==page.current?'active':''}|" th:each="i:${#numbers.sequence(page.from,page.to)}">
<a class="page-link" th:href="@{${page.path}(current=${i})}" th:text="${i}">1</a>
</li>
<li th:class="|page-item ${page.current==page.total?'disabled':''}|">
<a class="page-link" th:href="@{${page.path}(current=${page.current+1})}">下一页</a>
</li>
<li class="page-item">
<a class="page-link" th:href="@{${page.path}(current=${page.total})}">末页</a>
</li>
</ul>
</nav>
</div>
</div>

<script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" crossorigin="anonymous"></script>
<script th:src="@{/js/global.js}"></script>
<script th:src="@{js/index.js}"></script>
</body>
</html>

效果图如下:

82

开发社区登录模块

效果图如下:

83

84

前缀树算法过滤敏感词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package com.tzd.community.util;

import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

/**
* @author tianzedeng
* @date 2022/4/8 - 14:59
*/
@Component
public class SensitiveFilter {

private static final Logger logger = LoggerFactory.getLogger(SensitiveFilter.class);

//替换符
private static final String REPLACEMENT = "***";

//2.初始化前缀树
//根节点
private TrieNode rootNode = new TrieNode();

@PostConstruct
public void init(){
try(
InputStream inputStream = this.getClass().getClassLoader()
.getResourceAsStream("sensitive-words.txt");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
){
String keyword;
while ((keyword = bufferedReader.readLine()) != null){
//添加到前缀树
this.addKeyword(keyword);
}
}catch (IOException e){
logger.error("加载敏感词文件失败!" + e.getMessage());
}
}

//将一个敏感词添加到前缀树
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);
if (subNode==null){
//初始化子节点
subNode = new TrieNode();
tempNode.addSubNode(c,subNode);
}
//指向子节点,进入下一轮循环
tempNode = subNode;

//设置结束标识
if (i == keyword.length()-1){
tempNode.setKeywordEnd(true);
}
}
}

/**
* 3.过滤敏感词
* @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 stringBuilder = new StringBuilder();

while (position < text.length()){
char c = text.charAt(position);
//跳过符号
if (isSymbol(c)){
//如果指针1处于根节点,将此符号计入结果,让指针2向下走一步
if (tempNode == rootNode){
stringBuilder.append(c);
begin++;
}
//无论符号在开头或中间,指针3都向下走一步
position++;
continue;
}
//检查下级节点
tempNode = tempNode.getSubNode(c);
if (tempNode == null){
//以begin为开头的字符串不是敏感词
stringBuilder.append(text.charAt(begin));
//进入下一个位置
position = ++begin;
//重新指向根节点
tempNode = rootNode;
}else if (tempNode.isKeywordEnd()){
//发现敏感词,将begin到position的字符串替换掉
stringBuilder.append(REPLACEMENT);
//进入下一个位置
begin = ++position;
//重新指向根节点
tempNode = rootNode;
}else {
//检查下一个字符
position++;
}
}
//将最后一批字符计入结果
stringBuilder.append(text.substring(begin));
return stringBuilder.toString();
}
//判断是否为符号
private boolean isSymbol(Character c){
return !CharUtils.isAsciiAlphanumeric(c) && (c<0x2E80 || c>0x9FFF);
}



//1.前缀树
private class TrieNode{
//关键词结束的标识
private boolean isKeywordEnd = false;

//子节点(key是下级节点字符,value是下级节点)
private Map<Character,TrieNode> subNodes = new HashMap<>();


public boolean isKeywordEnd() {
return isKeywordEnd;
}

public void setKeywordEnd(boolean keywordEnd) {
isKeywordEnd = keywordEnd;
}

//添加子节点
public void addSubNode(Character character,TrieNode node){
subNodes.put(character,node);
}
//获取子节点
public TrieNode getSubNode(Character character){
return subNodes.get(character);
}
}

}

记录日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<contextName>community</contextName>
<property name="LOG_PATH" value="H:/project/data"/>
<property name="APPDIR" value="community"/>

<!-- error file -->
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APPDIR}/log_error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APPDIR}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>5MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory>
</rollingPolicy>
<append>true</append>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>error</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>

<!-- warn file -->
<appender name="FILE_WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APPDIR}/log_warn.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APPDIR}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>5MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory>
</rollingPolicy>
<append>true</append>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>warn</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>

<!-- info file -->
<appender name="FILE_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APPDIR}/log_info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APPDIR}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>5MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory>
</rollingPolicy>
<append>true</append>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>

<!-- console -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>debug</level>
</filter>
</appender>

<logger name="com.tzd.community" level="debug"/>

<root level="info">
<appender-ref ref="FILE_ERROR"/>
<appender-ref ref="FILE_WARN"/>
<appender-ref ref="FILE_INFO"/>
<appender-ref ref="STDOUT"/>
</root>

</configuration>

Redis处理点赞、关注、热帖排行

效果图:

85

92

91

kafka处理系统通知

效果图:

89

90

Elasticsearch实现分布式搜索功能

效果图:

86

Spring Security实现权限控制

效果图:

87

88

-------------本文结束感谢您的阅读-------------