• 第1章 初识Spring Boot,开发社区首页(下)


    6. 开发社区首页

    image-20220704160449355

    浏览器在给服务器发送请求时,首先发送给 Controller 然后 Controller 调 Service,然后 Service 调 DAO,所以我们开发的时候建议先开发 DAO,然后开发 Service,最后开发 controller。

    1. 开发DAO

    社区首页需要用到的数据库表

    CREATE TABLE `discuss_post` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `user_id` varchar(45) DEFAULT NULL,
      `title` varchar(100) DEFAULT NULL,
      `content` text,
      `type` int(11) DEFAULT NULL COMMENT '0-普通; 1-置顶;',
      `status` int(11) DEFAULT NULL COMMENT '0-正常; 1-精华; 2-拉黑;',
      `create_time` timestamp NULL DEFAULT NULL,
      `comment_count` int(11) DEFAULT NULL,
      `score` double DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `index_user_id` (`user_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=281 DEFAULT CHARSET=utf8;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    image-20220704161016924

    本次开发用到的类在下面图中显示

    image-20220705130814494

    首先需要建立一个与该表对应的实体类DiscussPost

    public class DiscussPost {
    
        private int id;
        private int userId;
        private String title;           // 标题
        private String content;         // 内容
        private int type;               // 帖子类型
        private int status;             // 帖子状态
        private Date createTime;       // 创建时间
        private int commentCount;      // 评论数量
        private double score;              // 分数/热度
    	
      	// get、set、toString方法为了以免影响阅读就没粘,实际开发时是有的
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    接下来开发DAO接口:DiscussPostMapper

    @Mapper
    public interface DiscussPostMapper {
    
        List<DiscussPost> selectDiscussPosts(int userId, int offset, int limit);      // 分页查询
        /*
        实现功能:分页查询
        参数1:userId   参数2:从第几条数据开始展示   参数3:每页展示几条数据
        这里要说一下,这个功能之一是展示首页的帖子,其实我们只需要查询所有数据然后分页就好了不需要传 userId,
        但是以后我们开发的时候有一个我的主页的功能,里面显示的都是我们自己的帖子,我们可以通过
        传 userId 展示自己发布过的所有帖子,所以为了以后开发方便,还是建议加上 userId
        具体实现展示首页帖子时 userId 传0, mapper配置文件处会进行修改,userId为0时查询所有帖子
         */
    
        int selectDiscussPostRows(@Param("userId") int userId);
        /*
        实现功能:查询数据库中有多少条数据
        参数1:userId
        在展示社区首页的时候,有一个页码,总共有多少页是由总数据条数和每页显示多少条数据决定的,我们可以把每页显示的
        数据条数固化下来,然后我们只需要一个方法查询一下数据库中总共有多少条数据。
        同样在查询社区首页数据时userId传入0,在mapper配置文件中处理时直接查询所有
        补充:@Param 注解用于给参数起别名,在mapper配置文件的动态sql中可以使用这个别名
             如果某个方法只有一个参数,并且在里(动态sql)使用,则必须用@Param注解给这个参数起别名
    
         */
    
    }
    
    • 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
    # 补充:@Param注解
    这个注解是给这个参数起一个别名,在mapper配置文件中写sql时就可以用这个别名,
    另外:如果mapper配置文件中需要用到动态sql并且需要用到这个参数,并且这个方法
    只有一个参数,这个时候,这个参数一定要加上@Param注解起别名,否则会报错。
    
    • 1
    • 2
    • 3
    • 4

    接下来我们要创建这个DAO接口对应的mapper配置文件:discusspost-mapper.xml

    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.nowcoder.community.dao.DiscussPostMapper">
    
        <sql id="selectFields" >
            id, user_id, title, content, type, status, create_time, comment_count, score
        sql>
        <select id="selectDiscussPosts" resultType="com.nowcoder.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 type desc, create_time desc
            limit #{offset}, #{limit}
        select>
    
        <select id="selectDiscussPostRows" resultType="Integer">
            select count(id)
            from discuss_post
            where status != 2
            <if test="userId!=0">
                and user_id = #{userId}
            if>
        select>
    
    mapper>
    
    • 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
    • 拉黑帖子不能展示(status = 2 表示拉黑帖子)

    image-20220704211725052

    因为mapper配置文件中的sql很容易写错,所以开发完dao之后建议在测试类中测试一下dao接口是否开发成功。

    2. 业务层

    接下来开发业务层service

    DiscussPostService:

    @Service
    public class DiscussPostService {
    
        @Autowired
        private DiscussPostMapper discussPostMapper;
    
        public List<DiscussPost> findDiscussPosts(int userId, int offest, int limit){
            return discussPostMapper.selectDiscussPosts(userId, offest, limit);
        }
    
        public int findDiscussPostRows(int userId){
            return discussPostMapper.selectDiscussPostRows(userId);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    我们查到的是userId,但是社区首页显示的肯定不是userId,而是user的用户名,我们可以
    直接在mapper配置文件中的sql进行关联查询,也可以在得到DiscussPost后根据userId单独
    地查一下user的用户名,把查到的user和DiscussPost组合到一起返回给页面。这里我们采用
    第二种,虽然第二种看起来比较麻烦,但是未来使用redis缓存一些数据的时候会比较方便,性能
    比较高。所以我们就需要一种方法根据userId到user,那这个方法写到DiscussPostService就
    不合适了,因为是跟user有关的操作,所以我们需要新建一个service即UserService,那
    UserService肯定需要用到UserDAO接口,但是这个接口已经写过了,所以我们只需要写UserService
    就可以了。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    UserService:

    @Service
    public class UserService {
    
        @Autowired
        private UserMapper userMapper;
    
        public User findUserById(int id){
            return userMapper.selectById(id);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    接下来将需要用到的一些静态资源、模板文件粘贴到项目中去

    image-20220704225333751

    3. 表现层

    接下来我们来开发视图层,首先来开发视图层:

    注:如果controller类上不加访问路径,那访问的时候就不需要写类的路径了,直接写方法的路径就可以了

    @Controller
    public class HomeController {
    
        @Autowired
        private DiscussPostService discussPostService;
    
        @Autowired
        private UserService userService;
    
        @RequestMapping(path = "/index", method = RequestMethod.GET)
        public String getIndexPage(Model model){
            List<DiscussPost> list = discussPostService.findDiscussPosts(0, 0, 10);
            List<Map<String, Object>> discussPosts = new ArrayList<>();
            if(list != null){
                for(DiscussPost post : list){
                    Map<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";            // 返回templates目录下的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

    image-20220705093836320

    接下来就是修改与这个controller相关的thymeleaf模板了 index.html

    # 注:将html改成themeleaf模板一定要修改第二行为
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    
    • 1
    • 2

    与帖子相关的themeleaf模板的部分内容

    
    <ul class="list-unstyled">
       <li class="media pb-3 pt-3 mb-3 border-bottom" th:each="map:${discussPosts}">
          <a href="site/profile.html">
             <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 href="#" 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:text="${map.user.username}">寒江雪u> 发布于 <b th:text="${#dates.format(map.post.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18b>
                <ul class="d-inline float-right">
                   <li class="d-inline ml-2">赞 11li>
                   <li class="d-inline ml-2">|li>
                   <li class="d-inline ml-2">回帖 7li>
                ul>
             div>
          div>                
       li>
    ul>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    # 小知识补充:
    th:text     里面的内容会原样输出
    th:utext    里面如果有转义字符会转义后再输出
    
    • 1
    • 2
    • 3

    image-20220705082952846

    image-20220705083113349

    image-20220705095238742

    实际效果:

    image-20220705095732141

    接下来我们来开发一下分页组件:

    分页对应的实体类:Page

    public class Page {
    
        // 当前页码
        private int current = 1;        // 默认为1
        // 每页显示的条数
        private int limit = 10;         // 默认为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
         */
        public int getOffset(){
            // current * limit - limit
            return (current - 1) * limit;
        }
    
        /**
         * 获取总页数
         * 这个方法是为了页面显示页码时左边界判断需要的条件
         * @return
         */
        public int getTotal(){
            // rows / limit [+1]
            if(rows % limit == 0){
                return rows / limit;
            } else {
                return rows / limit + 1;
            }
        }
    
        /**
         * 获取起始页码
         * 这个起始页码是当前页码附近的页码,左边的页码
         * 起始页码和中止页码会显示,中间的都用.省略
         * @return
         */
        public int getFrom(){
            int from = current - 2;
            return from < 1 ? 1 : from;
        }
    
        /**
         * 获取结束页码
         * @return
         */
        public int getTo(){
            int to = current + 2;
            int total = getTotal();
            return to > total ? total : to;
        }
    
    
    }
    
    • 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

    另外,我们还需要将前面的HomeController中的查询方法修改一下(参数另加了分页类):

    @Controller
    public class HomeController {
    
        @Autowired
        private DiscussPostService discussPostService;
    
        @Autowired
        private UserService userService;
    
        @RequestMapping(path = "/index", method = RequestMethod.GET)
        public String getIndexPage(Model model, Page page){
            // 方法调用前,SpringMVC会自动实例化Model和Page,并将Page注入Model
            // 所以,在thymeleaf中可以直接访问Page中的数据,不需要再将Page添加到Model里面了
            page.setRows(discussPostService.findDiscussPostRows(0));
            page.setPath("/index");
    
            List<DiscussPost> list = discussPostService.findDiscussPosts(0, page.getOffset(), page.getLimit());
            List<Map<String, Object>> discussPosts = new ArrayList<>();
            if(list != null){
                for(DiscussPost post : list){
                    Map<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";            // 返回templates目录下的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

    最后我们要修改index.html模板中的分页的内容

    如果标签里面的内容是动态的话,一定要在标签的前面加上 th:

    
    <nav class="mt-5" th:if="${page.rows>0}">
       <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}">1a>
          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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    image-20220705132207650

    将测试,功能成功实现

    下面是相关截图

    image-20220705132250530

    image-20220705132301999

    7.项目调试技巧

    image-20220705162214824

    状态码

    常见状态码:

    image-20220705175813332

    成功响应

    200 OK

    请求成功。成功的含义取决于 HTTP 方法:

    • GET: 资源已被提取并在消息正文中传输。
    • HEAD: 实体标头位于消息正文中。
    • PUT or POST: 描述动作结果的资源在消息体中传输。
    • TRACE: 消息正文包含服务器收到的请求消息。

    另外,这里介绍一下重定向:

    image-20220705180630598

    比如说浏览器请求删除,然后服务器执行删除操作之后应该返回给浏览器什么界面呢,应该返回查询结果以便查看请求删除数据是否被删除,这个时候的话让服务器的 “删除” 操作去调用 “查询” 操作的话是不合适的,因为这是两个业务,之间不应该产生耦合,这个时候服务器会传给浏览器一个状态码并附加一个路径,然后浏览器通过这个路径调 “查询” 方法,服务器再去执行 “查询方法“ 然后返回结果给浏览器。这就是重定向,这种方式比起 controller之间 互相调用降低了耦合。


    重定向消息

    303 See Other

    服务器发送此响应,以指示客户端通过一个 GET 请求在另一个 URI 中获取所请求的资源。

    客户端响应

    404 Not Found

    服务器找不到请求的资源。在浏览器中,这意味着无法识别 URL。在 API 中,这也可能意味着端点有效,但资源本身不存在。服务器也可以发送此响应,而不是 403 Forbidden,以向未经授权的客户端隐藏资源的存在。这个响应代码可能是最广为人知的,因为它经常出现在网络上。

    服务端响应

    500 Internal Server Error

    服务器遇到了不知道如何处理的情况。

    501 Not Implemented

    服务器不支持请求方法,因此无法处理。服务器需要支持的唯二方法(因此不能返回此代码)是 GET and HEAD.

    502 Bad Gateway

    此错误响应表明服务器作为网关需要得到一个处理这个请求的响应,但是得到一个错误的响应。

    服务端调试

    向下执行一行 按:F8

    进入当前行所调的方法内部 按:F7

    如果想要直接跳转到下一个断点 按:Alt + F9 (注:如果没有下一个断点直接执行到底)

    image-20220705200954403

    image-20220705201207436

    客户端调试

    客户端调试就是 js 调试,就是打断点调试

    客户端的代码运行在浏览器上,我们想给客户端打断点应该在哪打呢

    向下执行一行 F10

    进入到某一个方法内部 F11

    执行到下一个断点 F8

    image-20220705202959111

    设置日志级别

    https://logback.qos.ch

    image-20220705204303467

    trace 、debug、info、warn、error

    严重性越来越高,级别越来预高

    比如启用了 info 级别,只有 info 、warn、error级别会被打印出来,trace、debug会被忽视

    使用测试类

    接下来用测试类使用一下日志

    在配置文件里声明一下启用什么级别的日志

    在 application.properties 配置文件中加上下面这句话(意思是启用 debug 级别以上的日志都打印出来)

    # logger
    logging.level.com.nowcoder.community=debug
    
    • 1
    • 2

    image-20220706094619177

    要记录日志,首先要记得实例化记录日志的接口Logger

    @SpringBootTest
    @RunWith(SpringRunner.class)
    @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的名字,并不是日志
    
            logger.debug("debug log");
            logger.info("info log");
            logger.warn("warn log");
            logger.error("error log");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    image-20220706092724007

    # 注意
    按理说 debug 级别的话 trace 级别的日志是不会输出的,但是我所执行的程序里面 debug 日志级别trace 级别
    的日志却输出了,其他日志级别的倒是遵循高日志输出。
    
    • 1
    • 2
    • 3

    测试结果:

    image-20220706094738524

    将日志存到指定的文件

    上面这种方式是将日志打印在控制台上,这种方式只要程序关闭日志就不会留存下来。所以为了能够长久的保存这些日志,便于对日志的分析,我们经常需要另外把日志存到指定的文件里,下面演示一下

    我们只需要在 application.properties 配置文件中配置指定路径即可

    logging.file.name=d:/work/data/nowcoder/community.log
    
    • 1

    image-20220706100346536

    执行测试代码之后

    image-20220706100442572

    上面这种日志打印到文件中的方式比较简单,实际上在应用当中还会做的比这个更复杂一点。

    简单的打到一个 log 文件里,这个文件可能会非常的大,而且这个文件里面会混杂着各种级别的日志,不方便我们去做分析。我们在实际开发的过程中往往是把日志按照不同的级别存到不同的文件里,这样的话如果只想分析 “错误”,就看 error.log,最好是一个文件达到一定的空间后,再拆分出另外一个文件,接下来演示一下

    想达到上面的效果,我们需要写一个 log 所对应的xml文件 logback-spring.xml ,且xml文件必须是这个名字,这个文件比较复杂,用的时候直接粘到项目里,会改就行了,把 logback-spring.xml 配置文件放到 resources 根路径下(注:这个日志配置文件必须叫这个名字,且放在resources 目录下)

    使用下面的配置文件配置日志就不需要在 application.properties 配置文件中配置日志了

    
    <configuration>
        <contextName>communitycontextName>
        <property name="LOG_PATH" value="D:/work/data"/>
        <property name="APPDIR" value="community"/>
    
        
        <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_PATH}/${APPDIR}/log_error.logfile>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_PATH}/${APPDIR}/error/log-error-%d{yyyy-MM-dd}.%i.logfileNamePattern>
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>5MBmaxFileSize>
                timeBasedFileNamingAndTriggeringPolicy>
                <maxHistory>30maxHistory>
            rollingPolicy>
            <append>trueappend>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>%d %level [%thread] %logger{10} [%file:%line] %msg%npattern>
                <charset>utf-8charset>
            encoder>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>errorlevel>
                <onMatch>ACCEPTonMatch>
                <onMismatch>DENYonMismatch>
            filter>
        appender>
    
        
        <appender name="FILE_WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_PATH}/${APPDIR}/log_warn.logfile>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_PATH}/${APPDIR}/warn/log-warn-%d{yyyy-MM-dd}.%i.logfileNamePattern>
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>5MBmaxFileSize>
                timeBasedFileNamingAndTriggeringPolicy>
                <maxHistory>30maxHistory>
            rollingPolicy>
            <append>trueappend>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>%d %level [%thread] %logger{10} [%file:%line] %msg%npattern>
                <charset>utf-8charset>
            encoder>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>warnlevel>
                <onMatch>ACCEPTonMatch>
                <onMismatch>DENYonMismatch>
            filter>
        appender>
    
        
        <appender name="FILE_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_PATH}/${APPDIR}/log_info.logfile>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_PATH}/${APPDIR}/info/log-info-%d{yyyy-MM-dd}.%i.logfileNamePattern>
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>5MBmaxFileSize>
                timeBasedFileNamingAndTriggeringPolicy>
                <maxHistory>30maxHistory>
            rollingPolicy>
            <append>trueappend>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>%d %level [%thread] %logger{10} [%file:%line] %msg%npattern>
                <charset>utf-8charset>
            encoder>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>infolevel>
                <onMatch>ACCEPTonMatch>
                <onMismatch>DENYonMismatch>
            filter>
        appender>
    
        
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d %level [%thread] %logger{10} [%file:%line] %msg%npattern>
                <charset>utf-8charset>
            encoder>
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>debuglevel>
            filter>
        appender>
    
        <logger name="com.nowcoder.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>
    
    • 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

    image-20220706105552317

    image-20220706110245779

    image-20220706110958678

    上面的文件在使用到自己项目的时候需要改动的地方就是上面的路径包名

    接下来运行一个测试类测试一下

    image-20220706111728427

    总结

    在开发程序的时候,如果发现有问题,首先应该 1. 看状态码,大概定位一下是服务器端还是客户端有问题,
    然后通过断点去跟踪或者去看一 2. 看这个日志,当然能看日志就先看日志,日志还是找不到问题就 3.跟踪一下。

    8.版本控制

    image-20220706161928761

    http://git-scm.com

    认识Git并安装

    认识Git

    image-20220706162909417

    # 版本控制作用
    通过版本控制可以对代码做一个备份
    版本控制会将代码的每一次变更记录下来,假如这一代版本改坏了,可以通过历史记录恢复到上一代版本
    另外还可以通过版本控制让团队之间互相协作(一个人将代码上传到版本控制服务器,其他人可以下载代码去看去改)
    
    • 1
    • 2
    • 3
    • 4

    下载及安装Git

    image-20220706163148528

    image-20220706163200679

    image-20220706163221266

    下载完成之后是一个 exe 文件,安装即可

    安装的时候可以选择自己想要安装的位置,其他的一路默认就可以。

    image-20220706165613226

    安装完成之后

    image-20220706165848357

    注意, Git Bash虽然输入的是Linux命令行,但是在windows环境下也可以使用(我估计后台会进行一个转化)

    Git常用命令

    # 账号配置
    git config --list
    git config --global user.name "xxx"              设置用户名
    git config --global user.email "xxx@xxx.com"     设置邮箱地址
    
    # 本地仓库
    git init                           将当前目录配置成git仓库,信息记录在隐藏的.git文件夹中
    git status -s                      查看仓库状态
    git add XX 												 将XX文件添加到暂存区
    git add *                          将所有文件添加到暂存区
    git commit -m ‘...’                给自己看的“备注信息":将暂存区的内容提交到当前分支
    
    注:git reset .                    清空暂存区的文件
    
    # 生成秘钥
    ssh-keygen -t rsa -C "lihonghe@nowcoder.com"    后面加的是本地的的邮箱地址
    
    # 推送已有项目								
    git remote add origin https://git.nowcoder.com/334190970/Test.git  
    将本地仓库关联到远程仓库,origin表示后面那个网址的远程仓库的别名
    
    git push -u origin master  		
    将本地代码推送到名为 origin 的远程仓库的 master 分支
    
    # 克隆已有仓库
    git clone https://git.nowcoder.com/334190970/Test.git 
    将后面的远程仓库地址下的文件下载到当前目录下
    
    • 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

    接下来我们来介绍一下windows环境下 Git 的常用命令

    # windows环境下 Git 的常用命令
    git version        查看 Git 的版本
    
    • 1
    • 2

    打开上面windows环境下的Git软件

    image-20220706170320090

    先看一下 git version 命令查看Git版本

    image-20220706170558910

    在本地仓库中使用Git

    首先演示一下在本地仓库中怎么使用git去管理项目中的代码

    因为要把代码传到仓库里去,它得能识别你是谁,所以首先需要对 Git 进行一个配置,你要告诉这个 Git 我的用户名是谁,我的邮箱是谁,这两个都需要配,首先使用下面命令查看当前已有配置:

    git config --list             查看当前已有配置
    
    • 1

    image-20220706173139120

    可以看到上面没有用户名的配置也没有邮箱的配置,接下来我们来配置用户名和邮箱

    git config --global user.name "lubing"    配置用户名   
    git config --global user.email "958691367@qq.com"  设置全局邮箱地址
    
    • 1
    • 2

    可以看到执行上述命令后配置中多了用户名和密码

    image-20220706173911639

    现在就可以利用Git去管理我们的代码了

    比如现在有一套代码需要把它存到本地仓库里去

    要管理哪套代码,需要先cd到项目目录下

    image-20220706182817978

    然后我们需要去做一个初始化表示这个项目需要交给 Git 去管理

    git init
    
    • 1

    这个命令会导致 Git 在项目目录下创建一个隐藏的目录(了解)

    image-20220706183104283

    然后可以使用下面命令去查看管理的这套代码的状态

    git status 
    
    • 1

    image-20220706183400434

    然后我们需要把它们添加到我们的本地仓库中去

    git add *.xml		将所有的以.xml为后缀的文件添加到本地仓库中去
    git add *.java  将所有的以.java为后缀的文件添加到本地仓库中去
    git add *       将所有文件添加到本地仓库中去
    
    • 1
    • 2
    • 3

    image-20220706183924770

    上面只是临时的加进去了,还没有正式的提交,所以我们还需要提交以后才能在仓库里进行永久的存储

    git commit -m 'Test1'
    
    • 1

    -m 后面单引号的内容表示备注

    image-20220706184358241

    然后接下来我们修改一下 mavendemo1 项目的内容,然后再使用 git status 命令

    image-20220706184956795

    远程仓库演示Git

    接下来演示在本地仓库中的代码(已经提交过的代码)传到远程仓库上去

    远程仓库有很多,比如 GitHub、Gitee ·····,其中牛客也有远程仓库,接下来演示如何把代码传到牛客的远程仓库上去。

    Git 为了传代码安全,避免这个代码被偷,它在传输的时候采用 SSL 安全连接,安全的方式去传输,所以
    我们需要首先配置一下秘钥,这样才可以使用这种安全传输方式。
    
    创建秘钥
    ssh-keygen -t rsa -C "958691367@qq.com"     
    
    后面加的是Git账户的邮箱,敲完这个命令以后会有一些询问要把生成的秘钥存到哪里去,不用管,一路
    回车即可(这个秘钥只需要生成一次在远程仓库中配置即可,之后就不需要配置了)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    image-20220706211127391

    接下来需要去远程仓库的界面做一个配置,把秘钥添加到远程仓库中去,这样远程仓库才能接收我们传的代码。

    image-20220706211657842

    image-20220706211814282

    首先我们要在远程仓库里自己建一个项目(仓库),我们的代码要传到这个项目里

    image-20220706212006846

    image-20220706212254223

    image-20220706213156154

    之后我们需要在里面获取远程仓库的地址并将其复制下来

    image-20220706213506177

    首先我们需要在本地关联一下远程仓库(给上面的仓库起个别名,以后再去访问远程仓库用别名而不是每次都用上面的网址,不好记)

    git remote add origin https://git.acwing.com/Lb/mavendemo2.git
    将本地仓库关联到远程仓库,origin 表示后面那个网址的远程仓库的别名
    
    git push -u origin master  		
    将本地代码推送到名为 origin 的远程仓库的 master 分支
    
    • 1
    • 2
    • 3
    • 4
    • 5

    image-20220706214028916

    之后需要输入邮箱和密码(在哪个平台就输入哪个远程仓库托管平台的邮箱和密码)

    image-20220706215246014

    注:因为牛客平台服务器问题,我上传到了其他平台,上传之后截图

    image-20220706221506548

    然后我们还有一个需求,比如我们远程仓库本来就有一个项目,我们想把它下载到本地好去学习。

    image-20220706222104800

    我们首先要 cd 到我们想要把这个项目存在哪里(不需要使用 git init 设置为仓库),然后执行下面命令(后面接的链接就是上面我们复制的 克隆的地址,建议使用 HTTPS 克隆,SSH 克隆不好使)

    git clone https://git.acwing.com/Lb/mavendemo1.git
    
    • 1

    image-20220706223646028

    成功下载(注意在下载的时候只会下载 main 分支下的内容)

    IDEA演示Git

    接下来我们演示在 IDEA 怎么去配怎么去管理git管理代码,这样就不用写命令了,传上去很方便

    首先要在 IDEA 中配置一下Git,因为 IDEA 不知道你的远程仓库放在哪,你要告诉它。

    image-20220706224007133

    之后点击 Apply 应用就好。

    之后我们需要把这个项目初始化一下,然后把这个项目添加到本地仓库中去,然后再推送到远程仓库中去,和刚才的命令行操作是一样的,只不过这里是通过点按钮的方式。

    image-20220706224437404

    image-20220706224523457

    image-20220706224756930

    然后会出来一个框框,让选择提交的文件

    image-20220706225145656

    要想把本地仓库中的文件传到远程仓库,我们要在远程仓库建一个项目。

    image-20220706225654559

    image-20220706230126901

    image-20220706230333823

    image-20220706230448300

    image-20220706230526482

    image-20220706230715303

    最后补充一点:

    GitHub 上传文件的最大大小是 100 M,如果大小超过这个限制,会出现未知的错误。

  • 相关阅读:
    lock4j--分布式锁中间件--使用/实例
    @JSONField注解
    RobotFramework+Eclispe环境安装篇
    [附源码]计算机毕业设计基于springboot校园帮平台管理系统
    DSP篇--C6678功能调试系列之网络调试
    汇编实现点灯实验
    docker启动mysql服务
    python利用pandas.DataFram批量写入clickhouse
    错字修改 | 布署1个中文文文本拼蟹纠错模型
    FastDFS数据迁移
  • 原文地址:https://blog.csdn.net/qq_50313418/article/details/125917809