文章目录
1. 技术架构
- Spring Boot
- Spring、Spring MVC、MyBatis
- Redis、Kafka、Elasticsearch
- Spring Security、Spring Actuator
说明:
- SpringBoot 是 Spring 的脚手架,可以简化许多Spring配置文件
- SpringMVC 用于处理浏览器的请求
- MyBatis 用于访问数据库(此项目用MySQL)
- Redis 非关系型数据库,用于缓存
- Kafka 用于消息队列
- Elasticsearch 用于帖子内容搜索
- SpringSecurity 用于项目的权限管理
- SpringActuator 用于项目上线后的状态监控
2. 开发环境
- 构建工具:Apache Maven,maven官网
- 可以帮助我们构建项目、管理项目中的jar包
- Maven仓库:存放构件的位置
- 本地仓库:默认是 ~/.m2/resposity
- 远程仓库:中央仓库、镜像仓库、私服仓库
- 示例:
-
安装且配置环境变量后使用
mvc -version
检测Maven的版本: -
修改配置文件settings.xml,当加载资源时,从阿里镜像下载
<mirror> <id>nexus-aliyun</id> <mirrorOf>central</mirrorOf> <name>Nexus aliyun</name> <url>http://maven.aliyun.com/nexus/content/groups/public</url> </mirror>
-
- Maven仓库:存放构件的位置
- 集成开发环境:Intellij IDEA
- 数据库:MySQL、Redis
- 应用服务器:Apache Tomcat
- 版本控制工具:Git
- 可以帮助我们构建项目、管理项目中的jar包
3. Spring入门
3.1 Spring全家桶(网址)
- Spring Framework
- Spring Boot
- Spring Clound
- Spring Cloud Data Flow
3.2 Spring Framework
- Spring Core:IoC、AOP
- Spring Data Access:Transactions、Spring MyBatis
- Web Servlet:Spring MVC
- Integration:Email、Scheduling、AMQP、Security
3.3 Spring IOC
- Inversion of Control:控制反转,是一种面向对象编程的设计思想
- Dependency Injection:依赖注入,是IoC思想的实现方式
- IoC Container:IoC容器,是实现依赖注入的关键,本质上是一个工厂
3.3 Spring容器小案例
定义一个接口AlphaDao
public interface AlphaDao {
String select();
}
AlphaDao
有两个实现类AlphaDaoHibernateImpl
和AlphaDaoMybatisImpl
,我们使用ApplicationContext可一获取Bean时,希望可以获取对应的实现类。
@Repository("alphaHibernate")
public class AlphaDaoHibernateImpl implements AlphaDao {
@Override
public String select() {
return "this is Hibernate";
}
}
@Repository
@Primary
public class AlphaDaoMybatisImpl implements AlphaDao {
@Override
public String select() {
return "this is Mybatis";
}
}
说明:
@Primary:使用该注解,默认调用AlphaDao接口实现类时,是AlphaDaoMyBatis
@Repository(“alphaHibernate”):自定义Bean的别名
在配置类中配置要使用的Bean,然后调用
@Configuration//表明该类是一个配置类
public class AlphaConfig {
@Bean
public SimpleDateFormat simpleDateFormat() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
}
说明:
@Bean:默认配置的bean的别名是simpleDateFormat(即类名首字母小写)
@Autowired:依赖注入,默认是类型
@Qualifie(“xxx”):把名字为xxx的bean注入,一般和Autowired配合使用
测试:
package com.ateam.community;
import com.ateam.community.dao.AlphaDao;
import com.ateam.community.service.AlphaService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import java.text.SimpleDateFormat;
import java.util.Date;
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)//使用配置类
class CommunityApplicationTests implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Test
public void testApplicationContext(){
System.out.println(applicationContext);//org.springframework.web.context.support.GenericWebApplicationContext@7b205dbd
AlphaDao alphaDao = applicationContext.getBean(AlphaDao.class);
String select = alphaDao.select();
System.out.println(select);
AlphaDao alphaHibernate = applicationContext.getBean("alphaHibernate", AlphaDao.class);
System.out.println(alphaHibernate.select());
}
@Test
public void testBeanManagement(){
AlphaService service = applicationContext.getBean(AlphaService.class);
System.out.println(service);
AlphaService alphaService = applicationContext.getBean(AlphaService.class);
System.out.println(alphaService);
}
@Test
public void testBeanConfig(){
SimpleDateFormat dateFormat = applicationContext.getBean(SimpleDateFormat.class);
System.out.println(dateFormat.format(new Date()));
}
//自动注入
@Autowired
private AlphaDao alphaDao;
@Test
public void testDI(){
System.out.println(alphaDao);
}
}
4 Spring MVC入门
Spring MVC
- 三层架构
- 表现层
- 业务层
- 数据访问层
- MVC
- Model:模型层
- View:视图层
- Controller:控制层
- 核心组件
- 前端控制器:DispatcherServlet
- 前端控制器:DispatcherServlet
MVC处理过程:
HTTP
- HyperText Transfer Protocol
- 用于传输HTML等内容的应用层协议
- 规定了浏览器和服务器之间如何通信,以及通信时的数据格式。
参考资料:火狐浏览器开发者手册
Thymeleaf - 模板引擎:生成动态的HTML
- Thymeleaf:倡导自然模板,即以HTML文件为模板
- 常用语法:标准表达式、判断与循环、模板的布局
参考资料:Thymeleaf官网
5 MyBatis入门
核心组件
- SqlSessionFactory:用于创建SqlSession的工厂类
- SqlSession:MyBatis的核心组件,用于向数据库执行SQL
- 主配置文件:XML配置文件,可以对MyBatis的底层行为做出详细的配置
- Mapper接口:就是DAO接口,在MyBatis中习惯性的称之为Mapper
- Mapper映射器:用于编写SQL,并将SQL和实体类映射的组件,采用XML、注解均可实现
参考资料:MyBatis官网,Spring整合MyBatis
小Demo:
- 引入依赖
<!-- mysql-->
<!-- MySQL测试本地版-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>-->
</dependency>
<!-- mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
- 添加配置
# mysql和mybatis配置
#DataSourceProperties &serverTimezone=Hongkong
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
#映射文件的位置 classpath:mapper/*.xml
mybatis.mapper-locations=classpath*:mapper/*.xml
#mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.ateam.community.entity
#设置自动增加主键
mybatis.configuration.useGeneratedKeys=true
#下划线命名方式和驼峰命名方式匹配,如:create_time = crateTime
mybatis.configuration.mapUnderscoreToCamelCase=true
# logger
logging.level.com.ateam.community=debug
logging.file.name=E:/log/blog/community.log
- 创建entity包并创建User实体类
package com.ateam.community.entity;
import java.util.Date;
public class User {
private int id;
private String username;
private String password;
private String salt;
private String email;
private int userType;
private int status;
private String activationCode;
private String headerUrl;
private Date createTime;
}
- 在dao包下创建UserMapper接口
@Mapper
public interface UserMapper {
User selectById(int id);
User selectByName(String name);
User selectByEmail(String email);
int insertUser(User user);
int updateStatus(int id, int status);
int updateHeader(int id, String headerUrl);
int updatePassword(int id, String password);
}
- 在resources包下,创建mapper文件夹,并建立user-mapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ateam.community.dao.UserMapper">
<sql id="insertFields">
username, password, salt, email, user_type, status, activation_code, header_url, create_time
</sql>
<sql id="selectFields">
id, username, password, salt, email, user_type, status, activation_code, header_url, create_time
</sql>
<select id="selectById" resultType="com.ateam.community.entity.User">
select <include refid="selectFields"></include>
from user
where id = #{id}
</select>
<select id="selectByName" resultType="User">
select <include refid="selectFields"></include>
from user
where username = #{username}
</select>
<select id="selectByEmail" resultType="User">
select <include refid="selectFields"></include>
from user
where email = #{email}
</select>
<insert id="insertUser" parameterType="User" keyProperty="id">
insert into user (<include refid="insertFields"></include>)
values(#{username}, #{password}, #{salt}, #{email}, #{userType}, #{status}, #{activationCode}, #{headerUrl}, #{createTime})
</insert>
<update id="updateStatus">
update user set status = #{status} where id = #{id}
</update>
<update id="updateHeader">
update user set header_url = #{headerUrl} where id = #{id}
</update>
<update id="updatePassword">
update user set password = #{password} where id = #{id}
</update>
</mapper>
- 测试,在test包下,建立Mappertests类
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)//配置类
public class MapperTests implements CommunityConstant {
@Resource
private UserMapper userMapper;
@Test
public void selectById() {
User user = userMapper.selectById(150);
System.out.println(user);
}
@Test
public void selectByName() {
User user = userMapper.selectByName("liubei");
System.out.println(user);
}
}
6. 开发社区首页
开发流程
- 1此请求的执行过程
- 分布实现
- 开发社区首页,显示前10个帖子
- 开发分页组件,分页和显示所有帖子
开发步骤:
- 创建帖子实体类
public class DiscussPost {
private int id;
private int userId;
private String title;
private String content;
//0-普通; 1-置顶;
private int discussType;
//0-正常; 1-精华; 2-拉黑;
private int status;
private Date createTime;
private int commentCount;
private double score;
}
- 创建帖子对应的dao
@Mapper
public interface DiscussPostMapper {
List<DiscussPost> selectDiscussPosts(int userId, int offset, int limit);
//@Param 注解用于给参数取别名
//如果只有一个参数,并且在<if>里使用(动态sql),则必须添加别名
int selectDiscussPostRows(@Param("userId") int userId);
}
- 写对应的discussPost-mapper.xml,也在mapper目录下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ateam.community.dao.DiscussPostMapper">
<sql id="selectFields">
id, user_id, title, content, discuss_type, status, create_time, comment_count, score
</sql>
<select id="selectDiscussPosts" resultType="com.ateam.community.entity.DiscussPost">
select <include refid="selectFields"></include>
from discuss_post
where status != 2
<if test="userId != 0">
and user_id = #{userId}
</if>
order by discuss_type desc, create_time desc
limit #{offset}, #{limit}
</select>
<select id="selectDiscussPostRows" resultType="java.lang.Integer">
select count(id)
from discuss_post
where status != 2
<if test="userId != 0">
and user_id = #{userId}
</if>
</select>
</mpper>
- 创建用于分页的实体类Page
package com.ateam.community.entity;
public class Page {
// 当前页码
private int current = 1;
// 显示上限
private int limit = 10;
// 数据总数(用于计算总页数)
private int rows;
// 查询路径(用于复用分页链接)
private String path;
public int getCurrent() {
return current;
}
public void setCurrent(int current) {
if (current >= 1){
this.current = current;
}
}
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
if (limit >= 1 && limit <= 100){
this.limit = limit;
}
}
public int getRows() {
return rows;
}
public void setRows(int rows) {
if (rows >= 0){
this.rows = rows;
}
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
/**
* 获取当前页的起始行
* @return int
*/
public int getOffset(){
return (current - 1) * limit;
}
/**
* 获取总页数
* @return int
*/
public int getTotal(){
if (rows % limit == 0){
return rows / limit;
} else {
return rows / limit + 1;
}
}
public int getFrom(){
int from = current - 2;
return from < 1 ? 1 : from;
}
public int getTo(){
int to = current + 2;
int total = getTotal();
return to > total ? total : to;
}
@Override
public String toString() {
return "Page{" +
"current=" + current +
", limit=" + limit +
", rows=" + rows +
", path='" + path + '\'' +
'}';
}
}
- 在service包下,创建DiscussPostService类
package com.ateam.community.service;
@Service
public class DiscussPostService {
@Resource
private DiscussPostMapper discussPostMapper;
public List<DiscussPost> findDiscussPosts(int userId, int offset, int limit) {
return discussPostMapper.selectDiscussPosts(userId, offset, limit);
}
public int findDiscussPostRows(int userId) {
return discussPostMapper.selectDiscussPostRows(userId);
}
}
- 静态资源和前端页面模板(Html)
- 在controller包下,编写HomeController,处理首页请求,然后将数据传给index.html,进行展示
package com.ateam.community.controller;
import com.ateam.community.entity.DiscussPost;
import com.ateam.community.entity.Page;
import com.ateam.community.entity.User;
import com.ateam.community.service.DiscussPostService;
import com.ateam.community.service.LikeService;
import com.ateam.community.service.UserService;
import com.ateam.community.util.CommunityConstant;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author wsh
* @date 2021-11-13 16:19
* @description
*/
@Controller
public class HomeController implements CommunityConstant {
@Autowired
private DiscussPostService discussPostService;
@Autowired
private UserService userService;
@RequestMapping(value = "/index", method = RequestMethod.GET)
public String getIndexPage(Model model, Page page){
// 方法调用前,SpringMVC会自动实例化Model和Page,并将Page注入Model中
// 所以,在thymeleaf中可以直接访问Page对象中的数据
// 若是路径中带有参数如index?current=2 current的值可以在page中获得
page.setRows(discussPostService.findDiscussPostRows(0));
page.setPath("/index");
List<DiscussPost> list = discussPostService.findDiscussPosts(0, page.getOffset(), page.getLimit());
ArrayList<Map<String, Object>> discussPosts = new ArrayList<>();
if (list != null){
for (DiscussPost post : list){
HashMap<String, Object> map = new HashMap<>();
map.put("post",post);
User user = userService.findUserById(post.getUserId());
map.put("user",user);
discussPosts.add(map);
}
}
model.addAttribute("discussPosts",discussPosts);
return "/index";
}
}
- 修改index.html
<!-- 帖子列表 -->
<ul class="list-unstyled">
<li class="media pb-3 pt-3 mb-3 border-bottom" th:each="map:${discussPosts}">
<a th:href="#">
<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.discussType==1}">置顶</span>
<span class="badge badge-secondary bg-danger" th:if="${map.post.status==1}">精华</span>
</h6>
<div class="text-muted font-size-12">
<!-- utext可以显示转义字符,比如 < 显示为 < --->
<!-- #dates #代表引用内置工具 --->
<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">赞 11</li>
<li class="d-inline ml-2">|</li>
<li class="d-inline ml-2">回帖 7</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">
<!-- @{${page.path}(current=1,limit=10)} -> /index?current=1&limit=10 -->
<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>
7. 项目调试技巧
- 响应状态码的含义
- 服务端断点调试技巧
- 客户端断点调试技巧
- 设置日志级别,并讲日志输出到不同的终端
7.1 响应状态码的含义
- 1XX:指临时性的响应,需要执行者继续操作即可解决的状态码
- 2XX:指已经成功地处理了请求,用户可以正常的打开了这个页面
- 3XX:继续重定向相关操作
- 4XX:客户端的错误
- 5XX:服务器端的错误
火狐浏览器HTTP响应码介绍,挑选一些常用的介绍一下:
- 200 OK
请求成功。成功的含义取决于HTTP方法:
GET:资源已被提取并在消息正文中传输。
HEAD:实体标头位于消息正文中。
POST:描述动作结果的资源在消息体中传输。
TRACE:消息正文包含服务器收到的请求消息 - 301 Moved Permanently
被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个 URI 之一。如果可能,拥有链接编辑功能的客户端应当自动把请求的地址修改为从服务器反馈回来的地址。除非额外指定,否则这个响应也是可缓存的。 - 302 Found
请求的资源现在临时从不同的 URI 响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的。 - 400 Bad Request
1、语义有误,当前请求无法被服务器理解。除非进行修改,否则客户端不应该重复提交这个请求。
2、请求参数有误 - 404 Not Found
请求失败,请求所希望得到的资源未被在服务器上发现。没有信息能够告诉用户这个状况到底是暂时的还是永久的。假如服务器知道情况的话,应当使用410状态码来告知旧资源因为某些内部的配置机制问题,已经永久的不可用,而且没有任何可以跳转的地址。404这个状态码被广泛应用于当服务器不想揭示到底为何请求被拒绝或者没有其他适合的响应可用的情况下。 - 500 Internal Server Error
服务器遇到了不知道如何处理的情况。
7.2 服务端断点调试技巧
- 打断点(在需要重点查看的语句前)
- 以debug模式启动程序
- F8逐行执行;F7进入所调用方法的内部;F9程序继续执行直到遇到下一个断点,如果程序没有断点,则程序执行完毕
- 管理断点
7.3 客户端断点调试技巧
- 打断点(F12打开页面,在Sources下,找到要打断点的程序)
- F10 -> 执行下一行;F11 -> 进入方法内F8 -> 执行到底
7.4 设置日志级别,并将日志输出到不同的终端
SpringBoot默认日志logback(网址)
在test包下编写Loggertests类
package com.ateam.community;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author wsh
* @date 2021-11-13 21:51
* @description
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)//配置类
public class LoggerTests {
private static final Logger logger = LoggerFactory.getLogger(LoggerTests.class);
@Test
public void testLogger(){
System.out.println(logger.getName());
logger.trace("trace log");
// 调试日志
logger.debug("debug log");
// 普通级别日志
logger.info("info log");
logger.warn("warn log");
// 错误日志
logger.error("error log");
}
}
日志配置文件
# logger
logging.level.com.ateam.community=debug
测试结果
# logger
com.ateam.community.LoggerTests
2021-12-16 15:43:05,659 DEBUG [main] c.a.c.LoggerTests [LoggerTests.java:28] debug log
2021-12-16 15:43:05,660 INFO [main] c.a.c.LoggerTests [LoggerTests.java:29] info log
2021-12-16 15:43:05,660 WARN [main] c.a.c.LoggerTests [LoggerTests.java:30] warn log
2021-12-16 15:43:05,660 ERROR [main] c.a.c.LoggerTests [LoggerTests.java:31] error log
日志输出到具体的文件的相关配置
logging.file.name=E:/log/blog/community.log
但是,这样配置的话,所有的日志信息都会保存到该文件中,会导致不同级别的日志混杂且文件不断增大,不易查看。
重新写一个日志的配置文件,命名为logback-spring.xml,放在resources目录下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--项目名-->
<contextName>community</contextName>
<!--日志润发目录-->
<property name="LOG_PATH" value="E:/log"/>
<!--子目录,一般通过项目名加以区分-->
<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">
<!--格式-->
<!--
%d : 日期
%level : 日志级别
[%thread] : 线程
%logger{10} :日志所属的类,在哪个类中输出的日志
[%file:%line] :哪个文件,哪一行
%msg :具体的信息
%n :换行
-->
<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.ateam.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>
日志目录:
8. 版本控制
- 认识Git
- Git简介
- Git的安装与配置
- Git常用命令
- 将代码提交至本地仓库
- 将代码上传至远程仓库
- Idea基础Git
- 在Idea中配置并使用Git