1. 程式人生 > 其它 >基於Spring Boot的Java教育論壇系統

基於Spring Boot的Java教育論壇系統

基於Spring Boot的教學論壇系統

我個人負責的模組

  • 登入登出以及註冊的實現
  • 攔截器的實現
  • 論壇活躍度系統以及在排行榜上展示
  • 個人主頁的編寫
  • 檢視我的帖子以及我的回覆功能的實現
  • 好帖點贊功能的實現
  • 帖子置頂邏輯的編寫
  • 首頁帖子展示的編寫以及分頁邏輯的實現

具體實現方式

  • 登入登出以及註冊

    註冊功能的實現

    • 前端

      通過表單向後端傳送post請求

      在前端展示兩個輸入框,第一行是使用者名稱,第二行是密碼

      兩行分別向後端傳遞引數

      核心程式碼如下:

      <form action="/register" method="post">
            <div class="form-group">
              <label for="exampleInputEmail1">使用者名稱</label>
              <input type="text" class="form-control" id="exampleInputEmail1" name="username" placeholder="在此鍵入使用者名稱">
            </div>
            <div class="form-group">
              <label for="exampleInputPassword1">密碼</label>
              <input type="password" class="form-control" id="exampleInputPassword1" name="password" placeholder="在此鍵入密碼">
            </div>
            <button type="submit" class="btn btn-default">註冊</button>
            <div class="alert alert-danger col-lg-12 col-md-12 col-sm-12 col-xs-12"
                 th:text="${error}"
                 th:if="${error != null}">
            </div>
      </form>
      
    • 後端

      在接收到前端的傳值之後進行判斷使用者名稱和密碼是否為空

      且判斷使用者名稱是否已經存在

      若兩者均不為空且使用者名稱未被使用則存入資料庫並且寫入Cookie

      而Session的寫入是在攔截器中進行的,相關程式碼在攔截器中

      返回首頁時就已經是登入狀態

      否則註冊失敗返回異常

      核心程式碼如下:

      @PostMapping("/register")
          private String doRegister(@RequestParam("username") String username,
                                    @RequestParam("password") String password,
                                    HttpServletResponse response,
                                    Model model){
              model.addAttribute(username);
              model.addAttribute(password);
              if(username==null || username=="")
              {
                  model.addAttribute("error","使用者名稱不能為空");
                  return "register";
              }
              if(password==null || password == "")
              {
                  model.addAttribute("error","密碼不能為空");
                  return "register";
              }
              UserExample example = new UserExample();
              example.createCriteria()
                      .andAccountIdEqualTo(username);
              List<User> users = userMapper.selectByExample(example);
              if(users.size() != 0)
              {
                  model.addAttribute("error","使用者名稱已經被註冊了");
                  return "register";
              }
              User user =new User();
              user.setAccountId(username);
              user.setPassword(password);
              user.setGmtCreate(System.currentTimeMillis());
              user.setGmtModified(user.getGmtCreate());
              user.setName("小明");
              user.setIsAdmin(false);
              user.setQuestionCount(0);
              user.setCommentCount(0);
              user.setActive(0);
              user.setAvatarUrl("https://pic.imgdb.cn/item/60a9dd9135c5199ba7deab59.jpg");
              String token = UUID.randomUUID().toString();
              user.setToken(token);
              userMapper.insert(user);
              response.addCookie(new Cookie("token",token));
              return "redirect:/";
          }
      

    登入功能的實現

    1. 通過資料庫的登入

      • 前端與註冊一樣,後端也大同小異,因此此處僅展示查詢程式碼

        核心程式碼如下

            UserExample example = new UserExample();
            example.createCriteria()
                .andAccountIdEqualTo(username)
                .andPasswordEqualTo(password);
            List<User> users = userMapper.selectByExample(example);
            if(users.size() == 0)
            {
                model.addAttribute("error","使用者名稱或密碼錯誤");
                return "login";
            }
            User dbUser = users.get(0);
            User upadteUser = new User();
            upadteUser.setGmtModified(System.currentTimeMillis());
            String token = UUID.randomUUID().toString();
            upadteUser.setToken(token);
            UserExample userExample = new UserExample();
            userExample.createCriteria()
                .andIdEqualTo(dbUser.getId());
            userMapper.updateByExampleSelective(upadteUser, userExample);
            response.addCookie(new Cookie("token",token));
            return "redirect:/";
        
    2. 連線Gitee通過OAuth2認證登入

      • OAuth2認證的基本流程

        對於此認證Gitee也有官方文件,如果看不懂我接下來的解釋可以去看官方文件[Gitee對於OAuth2的官方文件](Gitee OAuth 文件)

        上圖中的應用伺服器可以理解為執行程式的電腦

        瀏覽器就是開啟本論壇所使用的瀏覽器

        當你在Gitee中申請了可以接入Gitee的應用之後你會獲得一個應用ID以及你自己填寫的回撥地址

        你用你手上的這兩樣東西可以拼成以下這個形式的連結

        https://gitee.com/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code
        

        其中client_id就是應用id,redirect_uri就是回撥地址,千萬記得大括號也要一併刪除(不要和我一樣可愛)

        當用戶在這個連結中登入自己的Gitee賬號後,我們就走完了圖中的AB路線

        此時Gitee會通過客戶端也就是瀏覽器給我們傳回一個使用者授權碼(這個授權碼會返回在url中我們在控制層中用@RequestParam方法進行接收就可以了!)

        當我們得到授權碼之後我們又可以和另外兩樣東西拼成下面這個形式的連結

        https://gitee.com/oauth/token?grant_type=authorization_code&code={code}&client_id={client_id}&redirect_uri={redirect_uri}&client_secret={client_secret}
        

        其中code就是我們剛剛得到的使用者授權碼

        之後Gitee就會返回一串String

        輸出String並觀察access_token的位置就可以把它截取出來並通過其獲取使用者資訊了!

        程式碼實現邏輯:

        /*
        從這裡進行獲取code並初始化accessTkoenDTO
        並且用一個隨機的UUID作為Cookie
        */
        public String callback(@RequestParam(name = "code") String code,
                                   HttpServletResponse response){
                AccessTokenDTO accessTokenDTO = new AccessTokenDTO();
                accessTokenDTO.setClient_id(clientId);
                accessTokenDTO.setClient_secret(clientSecert);
                accessTokenDTO.setCode(code);
                accessTokenDTO.setRedirect_uri(redircrUri);
                String accessToken = giteeProvider.getAccessToken(accessTokenDTO);
                GiteeUser giteeUser = giteeProvider.getUser(accessToken);
                if(giteeUser!=null && giteeUser.getId()!=0)
                {
                    //登入成功,寫cookie和session
                    User user = new User();
                    //User user1 ;
                    String token = UUID.randomUUID().toString();
                    user.setToken(token);
                    user.setName(giteeUser.getName());
                    user.setAccountId(String.valueOf(giteeUser.getId()));
                    user.setAvatarUrl(giteeUser.getAvatarUrl());
        
                    //user1 = userMapper.findByAccountId(user.getAccountId());
                    /*if(user1==null)*/
                    userService.createOrUpdate(user);
                    response.addCookie(new Cookie("token",token));
                    return "redirect:/";
                }
         }
        
        /*獲取accesstoken的具體邏輯以及通過其獲取使用者資訊的方法*/
        @Component
        public class GiteeProvider {
            public String getAccessToken(AccessTokenDTO accessTokenDTO){
                MediaType mediaType = MediaType.get("application/json; charset=utf-8");
        
                OkHttpClient client = new OkHttpClient();
        
                RequestBody body = RequestBody.create(mediaType, JSON.toJSONString(accessTokenDTO));
                Request request = new Request.Builder()
                        .url("https://gitee.com/oauth/token?grant_type=authorization_code&code="+accessTokenDTO.getCode()+"&client_id="+accessTokenDTO.getClient_id()+"&redirect_uri="+accessTokenDTO.getRedirect_uri()+"&client_secret="+accessTokenDTO.getClient_secret())
                        .post(body)
                        .build();
                try (Response response = client.newCall(request).execute()) {
                    String string = response.body().string();
                    String token=null;
                    int cnt=0,l=0;
                    boolean flag=false;
                    for(int i=0;i<string.length();i++)
                    {
                        if(string.charAt(i)=='\"') {
                            cnt++;
                        }
                        if(cnt==3&&!flag)
                        {
                            l=i+1;
                            flag=true;
                        }
                        if(cnt==4)
                        {
                            token=string.substring(l,i);
                            break;
                        }
                    }
                    //System.out.println(string);
                    //System.out.println(token);
                    return token;
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return null;
            }
            public GiteeUser getUser(String accessToken){
                OkHttpClient client = new OkHttpClient();
                Request request = new Request.Builder()
                        .url("https://gitee.com/api/v5/user?access_token=" +accessToken)
                        .build();
                try {
                    Response response = client.newCall(request).execute();
                    String string=response.body().string();
                    GiteeUser giteeUser=JSON.parseObject(string,GiteeUser.class);
                    return giteeUser;
                } catch (IOException e) {
                }
                return null;
            }
        }
        
      • 前端

        前端比較簡單,只需要一個可以點進去的連結作為入口就可以了,這裡不做展示

    登出功能的實現

    • 基本思路

      實現使用者的登出要把存在服務端的Session和存在客戶端的Cookie都一併清除

      清除Session好說,有現成的語法可以用,如下:

      request.getSession().removeAttribute("這裡是你的session名");
      

      而清除Cookie會稍微麻煩一點,首先用一個同名的空cookie把原來的cookie覆蓋掉,之後把它的存在時間改為0,他就會消失,具體程式碼如下:

          Cookie cookie = new Cookie("token",null);
          cookie.setMaxAge(0);
          response.addCookie(cookie);
      
    • 前端

      一樣只需要一個入口,在此不多展示

  • 攔截器

    • 定義一個攔截器

      首先我們需要知道我們這個攔截器的作用是什麼,我要寫的攔截器是在使用者進入這個頁面之後通過檢視客戶端瀏覽器的Cookie來之間確定是否為登入態

      很顯然我們的攔截器是要在執行Controller之前就要執行的

      而HandlerInterceptor為我們提供了三種重寫方法,分別是:preHandle,postHandle,afterCompletion

      其中preHandle方法是符合我們要求的,所有我們只需要重寫它就可以了

      我們通過列舉cookie來找到名為token的cookie,在利用資料庫的查詢語句查詢是否有對應的使用者

      如果有,則寫入session

      核心程式碼如下:

      //其中usermapper是通過mybatis逆向生成的檔案
      @Override
          public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
              Cookie[] cookies = request.getCookies();
              if(cookies!=null&&cookies.length!=0) {
                  for(Cookie cookie:cookies)
                  {
                      if(cookie.getName().equals("token"))
                      {
                          String token=cookie.getValue();
                          UserExample example = new UserExample();
                          example.createCriteria()
                                  .andTokenEqualTo(token);
                          List<User> users = userMapper.selectByExample(example);
                          if(users.size()!=0)
                          {
                              request.getSession().setAttribute("user",users.get(0));
                          }
                          break;
                      }
                  }
              }
              return true;
          }
      
    • 新增攔截器

      我們要在每個頁面都判斷一下是否為登入態,所有配置如下:

      @Configuration
      public class WebConfig implements WebMvcConfigurer {
          @Autowired
          private SessionInterceptor sessionInterceptor;
      
          @Override
          public void addInterceptors(InterceptorRegistry registry) {
              registry.addInterceptor(sessionInterceptor).addPathPatterns("/**");
          }
      }
      
  • 論壇活躍度系統以及在排行榜上的展示

    • 活躍度設定

      由於不是很會怎麼設定比較高階的活躍度設定,就設定為發一次貼增加5點活躍度,回覆一次增加3點活躍度(經驗+3!)

    • 需要考慮的細節

      注:由於博主能力有限以及時間過緊,本功能沒有處理高併發的能力

      由於釋出帖子介面同時兼顧修改帖子的功能,所有要首先判斷帖子id是否為空再進行活躍度的計算,如下:

          if(question.getId()==null)
          {
              user.setQuestionCount(user.getQuestionCount()+1);
              user.setActive(user.getActive()+5);
              userMapper.updateByPrimaryKey(user);
          }
      

      在刪除帖子時相應的活躍度也需要減少,通過列舉評論來減少活躍度,要注意在delete語句之前執行,如下:

       @GetMapping("/delete/{id}")
          public String delete(@PathVariable(name = "id") String id,
                               Model model){
      
              QuestionExample questionExample = new QuestionExample();
              questionExample.createCriteria()
                      .andIdEqualTo(Integer.parseInt(id));
              Question question = questionMapper.selectByPrimaryKey(Integer.parseInt(id));
              User user = userMapper.selectByPrimaryKey(question.getCreator());
              user.setQuestionCount(user.getQuestionCount()-1);
              user.setActive(user.getActive()-5);
              userMapper.updateByPrimaryKey(user);
              questionMapper.deleteByExample(questionExample);
      
              CommentExample commentExample = new CommentExample();
              commentExample.createCriteria()
                      .andParentIdEqualTo(Integer.parseInt(id));
              int count = (int) commentMapper.countByExample(commentExample);
              List<Comment> comments = commentMapper.selectByExample(commentExample);
              for(Comment comment:comments)
              {
                  user = userMapper.selectByPrimaryKey(comment.getCommentator());
                  user.setActive(user.getActive()-3);
                  user.setCommentCount(user.getCommentCount()-1);
                  userMapper.updateByPrimaryKey(user);
              }
              commentMapper.deleteByExample(commentExample);
              return "delete";
          }
      
    • 排行榜的展示

      通過資料庫排序語句進行排序,前端通過th:each語句進行列舉

      前端核心程式碼:

      <table class="table table-striped">
          <thead>
              <tr>
                <th>排名</th>
                <th>使用者名稱</th>
                <th>發帖數</th>
                <th>回帖數</th>
                <th>活躍度</th>
              </tr>
          </thead>
          <tbody>
              <tr th:each="user : ${users}">
                <th th:text="${user.rank}"></th>
                <th th:text="${user.name}"></th>
                <th th:text="${user.questionCount}"></th>
                <th th:text="${user.commentCount}"></th>
                <th th:text="${user.active}"></th>
              </tr>
          </tbody>
      </table>
      

      後端核心程式碼:

      @RequestMapping("/rank/{type}")
          private String rank(@PathVariable(name = "type") Integer type,
                              Model model){
              List<User> users = new ArrayList<>();
              List<UserDTO> userDTOS = new ArrayList<>();
              if(type==1){
                  UserExample example = new UserExample();
                  example.createCriteria();
                  example.setOrderByClause("active DESC");
                  users = userMapper.selectByExample(example);
              }else if(type==2){
                  UserExample example = new UserExample();
                  example.setOrderByClause("question_count DESC");
                  users = userMapper.selectByExample(example);
              }else {
                  UserExample example = new UserExample();
                  example.setOrderByClause("comment_count DESC");
                  users = userMapper.selectByExample(example);
              }
      
              for(int i=0;i<users.size();i++)
              {
                  UserDTO u=new UserDTO();
                  BeanUtils.copyProperties(users.get(i),u);
                  userDTOS.add(u);
                  userDTOS.get(i).setRank(i+1);
              }
              model.addAttribute("users",userDTOS);
              return "rank";
          }
      
  • 個人頁面的編寫

    在個人頁面中展示個人頭像在右側表單跳轉到我的帖子以及我的回覆

    在表單上的徽章代表帖子和回覆的個數

    • 前端核心程式碼

      <div class="list-group">
          <a th:href="@{'/profile/'+${session.user.id}+'/questions'}" class="list-group-item">
              <span class="badge" th:text="${questionNumber}"></span>
              我的帖子
          </a>
          <a th:href="@{'/profile/'+${session.user.id}+'/replies'}" class="list-group-item">
              <span class="badge" th:text="${commentNumber}"></span>
              我的回帖
          </a>
      </div>
      
    • 後端核心程式碼

      @RequestMapping("/profile/{id}")
          private String profile(Model model,
                                 @RequestParam(name="page",defaultValue = "1") Integer page,
                                 @RequestParam(name = "size",defaultValue = "5") Integer size,
                                 @PathVariable(name = "id") Integer id){
              PagingDTO pagination= questionService.list(page,size);
              model.addAttribute("pagination",pagination);
              User user = userMapper.selectByPrimaryKey(id);
              QuestionExample example = new QuestionExample();
              example.createCriteria()
                      .andCreatorEqualTo(id);
              Integer questionNumber = (int) questionMapper.countByExample(example);
              CommentExample example1 = new CommentExample();
              example1.createCriteria()
                      .andCommentatorEqualTo(id);
              Integer commentNumber = (int) commentMapper.countByExample(example1);
              model.addAttribute("questionNumber",questionNumber);
              model.addAttribute("commentNumber",commentNumber);
              model.addAttribute("user",user);
              model.addAttribute("id",id.toString());
              return "profile";
          }
      
  • 我的問題以及我的回覆

    通過路徑上的id來判斷使用者,使用資料庫語句進行查詢,使用分頁邏輯進行展示

    • 前端核心程式碼:

       <div class="media" th:if="${section}=='questions'" th:each="question : ${pagination.questions}">
      	<div class="media-left">
          	<a href="#">
              	<img class="media-object img-rounded"th:src="${question.user.avatarUrl}">
              </a>
          </div>
          <div class="media-body">
          	<h4 class="media-heading" th:text="${question.title}"></h4>
              <span th:text="${question.description}"></span>
              <br>
              <span class="text-desc">
                  <span th:text="${question.commentCount}"></span> 個回覆 • 
                  <span th:text="${question.viewCount}"></span> 次瀏覽 • 
                  <span th:text="${#dates.format(question.gmtCreate,'yyyy-MM-dd HH:mm')}"></span>
              </span>
          </div>
      </div>
      <div class="media" th:if="${section}=='replies'" th:each="comment : ${pagination.comments}">
      	<div class="media-left">
      		<a href="#">
      			<img class="media-object img-rounded"th:src="${comment.user.avatarUrl}">
              </a>
          </div>
          <div class="media-body">
          <h4 class="media-heading" th:text="${comment.content}"></h4>
          <span th:text="${comment.question.title}"></span> <br>
          <span class="text-desc"><span th:text="${comment.likeCount}"></span> 個點贊</span></span>
          </div>
      </div>
      
    • 後端核心程式碼

      @GetMapping("/profile/{id}/{action}")
          public String profile(@PathVariable(name = "action") String action,
                                @PathVariable(name = "id") Integer id,
                                HttpServletRequest request,
                                Model model,
                                @RequestParam(name="page",defaultValue = "1") Integer page,
                                @RequestParam(name = "size",defaultValue = "5") Integer size){
              User user = (User) request.getSession().getAttribute("user");
              if(user==null){
                  return "redirect:/";
              }
              if("questions".equals(action)){
                  model.addAttribute("section","questions");
                  model.addAttribute("sectionName","我的帖子");
                  PagingDTO paginationDTO = questionService.list(id, page, size);
                  model.addAttribute("pagination",paginationDTO);
              }else if("replies".equals(action)){
                  model.addAttribute("section","replies");
                  model.addAttribute("sectionName","我的回覆");
                  PagingDTO pagingDTO = commentService.list(id,page,size);
                  model.addAttribute("pagination",pagingDTO);
              }
              model.addAttribute("id",id.toString());
              return "profileMy";
          }
      
  • 好帖點贊功能的實現

    • 實現方式

      我能想到的只有最原始最暴力的方法,新建一張表

      parent_id表示被點讚的物件,可以是帖子的id也可以是回覆的id

      type表示被點贊物件,若值為1則表示為對帖子的點贊,若值為2則表示對回覆的點贊

      liker_id表示點贊者的id

      當用戶點選點贊按鈕時,在資料庫中通過上面三項進行查詢匹配

      若為空則點贊成功,否則點贊失敗

    • 核心程式碼:

      @RequestMapping("/like/{id}/{type}")
          private String like(@PathVariable(name = "id") Integer id,
                              @PathVariable(name = "type") Integer type,
                              HttpServletRequest request,
                              Model model){
      
              User user = (User) request.getSession().getAttribute("user");
      
      
              LoveExample example = new LoveExample();
      
              example.createCriteria()
                      .andParentIdEqualTo(id)
                      .andTypeEqualTo(type)
                      .andLikerIdEqualTo(user.getId());
              List<Love> loves = loveMapper.selectByExample(example);
              if(loves.size()==0){
                  Love love = new Love();
                  love.setIsLike(true);
                  love.setLikerId(user.getId());
                  love.setParentId(id);
                  love.setType(type);
                  Question question = new Question();
                  if(type.equals(1)){
                      question = questionMapper.selectByPrimaryKey(id);
                      user = userMapper.selectByPrimaryKey(question.getCreator());
                      question.setLikeCount(question.getLikeCount()+1);
                      question.setGmtModified(System.currentTimeMillis());
                      questionMapper.updateByPrimaryKey(question);
                      List<CommentDTO> commentDTOList = commentService.getByParentIdList(id);
                      model.addAttribute("comments",commentDTOList);
                  }else{
                      Comment comment = commentMapper.selectByPrimaryKey(id);
                      question = questionMapper.selectByPrimaryKey(comment.getParentId());
                      user = userMapper.selectByPrimaryKey(question.getCreator());
                      comment.setLikeCount(comment.getLikeCount()+1);
                      comment.setGmtModified(System.currentTimeMillis());
                      commentMapper.updateByPrimaryKey(comment);
                      List<CommentDTO> commentDTOList = commentService.getByParentIdList(comment.getParentId());
                      model.addAttribute("comments",commentDTOList);
                  }
                  QuestionDTO questionDTO =new QuestionDTO();
                  BeanUtils.copyProperties(question,questionDTO);
                  questionDTO.setUser(user);
                  model.addAttribute("question",questionDTO);
                  loveMapper.insert(love);
                  return "question";
              }else {
                  Question question = new Question();
                  if(type.equals(1)){
                      question = questionMapper.selectByPrimaryKey(id);
                      user = userMapper.selectByPrimaryKey(question.getCreator());
                      List<CommentDTO> commentDTOList = commentService.getByParentIdList(id);
                      model.addAttribute("comments",commentDTOList);
                  }else{
                      Comment comment = commentMapper.selectByPrimaryKey(id);
                      question = questionMapper.selectByPrimaryKey(comment.getParentId());
                      user = userMapper.selectByPrimaryKey(question.getCreator());
                      List<CommentDTO> commentDTOList = commentService.getByParentIdList(comment.getParentId());
                      model.addAttribute("comments",commentDTOList);
                  }
                  QuestionDTO questionDTO =new QuestionDTO();
                  BeanUtils.copyProperties(question,questionDTO);
                  questionDTO.setUser(user);
                  model.addAttribute("question",questionDTO);
                  model.addAttribute("error", "已經贊過了");
                  return "question";
              }
          }
      
  • 帖子置頂邏輯的編寫

    • 實現方式

      直接給帖子一個bool標籤,之後通過資料庫語句暴搜(藍橋杯騙分技巧)

      在前端先列舉每一個被置頂的帖子,再在後面用分頁功能實現對剩下帖子的列舉

    • 前端核心程式碼

      <blockquote th:each="top : ${tops}">
      	<div class="media" >
              <div class="media-left">
                  <a th:href="@{'/profile/'+${top.user.id}}">
                      <img class="media-object img-rounded"
                           style="width: 60px;height: 60px;"
                           th:src="${top.user.avatarUrl}">
                  </a>
              </div>
              <div class="media-body">
                  <h4 class="media-heading" >
                      <svg  class="icon" aria-hidden="true">
                          <use xlink:href="#icon-zhiding"></use>
                      </svg>
                      <a th:href="@{'/question/'+${top.id}}" th:text="${top.title}"></a>
                  </h4>
                  <span th:text="${top.description}"></span> <br>
                  <span class="text-desc">
                      <span th:text="${top.commentCount}"></span> 個回覆 • 
                      <span th:text="${top.viewCount}"></span> 次瀏覽 • 
                      <span th:text="${#dates.format(top.gmtCreate,'yyyy-MM-dd HH:mm')}"></span>
                  </span>
              </div>
          </div>
      </blockquote>
      
    • 後端核心程式碼

      @RequestMapping("/")
          public String index(Model model,
                              @RequestParam(name="page",defaultValue = "1") Integer page,
                              @RequestParam(name = "size",defaultValue = "7") Integer size,
                              @RequestParam(name ="search",required = false) String search){
      
              List<QuestionDTO> top = new ArrayList<>();
              List<Question> tops =new ArrayList<>();
      
              if(!StringUtils.isNullOrEmpty(search)){
                  PagingDTO pagination= questionService.list(page,size,search);
                  model.addAttribute("pagination",pagination);
                  return "index";
              }else {
                  QuestionExample example = new QuestionExample();
                  example.createCriteria()
                          .andIsTopEqualTo(true);
                  tops = questionMapper.selectByExampleWithBLOBs(example);
                  PagingDTO pagination= questionService.list(page,size);
                  model.addAttribute("pagination",pagination);
              }
              for (Question question : tops) {
                  QuestionDTO questionDTO = new QuestionDTO();
                  User user = userMapper.selectByPrimaryKey(question.getCreator());
                  BeanUtils.copyProperties(question, questionDTO);
                  questionDTO.setUser(user);
                  questionDTO.setCommentCount(commentService.countByParentId(question.getId()));
                  top.add(questionDTO);
              }
              model.addAttribute("tops",top);
              return "index";
          }
      
  • 首頁的編寫以及分頁邏輯的實現

    • 實現方式

      對於首頁的編寫由於導航欄每個頁面都需要所有我把它封裝起來了

      而首頁只需要展示置頂的帖子和由新到舊的帖子展示就可以了

      都比較好實現,只不過要注意mybatis中排序語法的使用,如下

      example.setOrderByClause("gmt_create DESC");//DESC為降序排列,預設為升序,前邊為表中的元素
      

      分頁邏輯就是一個簡單的模擬,首先讀入頁碼,以此為中間值來插入左右的頁碼

      通過此是否為第一頁來判斷是否有上一頁按鈕

      通過頁碼欄中是否含有第一頁來判斷是否有首頁按鈕

      判斷下一頁和尾頁邏輯一致

      而對於每一頁展示哪個頁面mybatis逆向生成的語句中有有個可以設定偏移量和查詢個數的語句

      使用方法如下:

      List<Question> questions = questionMapper
          				.selectByExampleWithBLOBsWithRowbounds(example, new RowBounds(offset, size));
      

      其中offset為偏移量,很簡單可以推出為offset=size*(page-1)

      size為一頁展示幾條帖子,我設定預設為5

      理解了這些之後就很好實現了

    • 部分核心程式碼

      Controller中傳值程式碼如下:

      PagingDTO pagination= questionService.list(page,size);
      model.addAttribute("pagination",pagination);
      

      QuestionService中對應的list方法如下:

       public PagingDTO list(Integer page, Integer size) {
              PagingDTO paginationDTO = new PagingDTO();
              Integer totalPage;
              QuestionExample example = new QuestionExample();
              example.createCriteria()
                      .andIsTopEqualTo(false);
              Integer totalCount=(int)questionMapper.countByExample(example);
              if(totalCount%size==0)
              {
                  totalPage=totalCount/size;
              }
              else
              {
                  totalPage=totalCount/size+1;
              }
      
              if(page<1) {
                  page=1;
              }
              if(page>totalPage) {
                  page=totalPage;
              }
      
              paginationDTO.setPagination(totalPage,page);
      
              Integer offset = size * (page - 1);
              example.setOrderByClause("gmt_create DESC");
              List<Question> questions = questionMapper.selectByExampleWithBLOBsWithRowbounds(example, new RowBounds(offset, size));
              List<QuestionDTO> questionDTOList=new ArrayList<>();
      
              for(Question question:questions)
              {
                  User user = userMapper.selectByPrimaryKey(question.getCreator());
                  QuestionDTO questionDTO = new QuestionDTO();
                  questionDTO.setId(question.getId());
                  BeanUtils.copyProperties(question,questionDTO);
                  questionDTO.setCommentCount(commentService.countByParentId(question.getId()));
                  questionDTO.setUser(user);
                  questionDTOList.add(questionDTO);
              }
              paginationDTO.setQuestions(questionDTOList);
              return paginationDTO;
          }
      

      PagingDTO 中的引數設定如下:

      @Data
      public class PagingDTO {
          private List<QuestionDTO> questions;
          private List<CommentDTO> comments;
          private boolean showPrevious;
          private boolean showFirstPage;
          private boolean showNext;
          private boolean showEndPage;
          private Integer page;
          private List<Integer> pages = new ArrayList<>();
          Integer totalPage;
      
          public void setPagination(Integer totalPage, Integer page) {
              this.totalPage=totalPage;
              this.page=page;
              pages.add(page);
              for(int i=1;i<=3;i++)
              {
                  if(page-i>0){
                      pages.add(0,page-i);
                  }
                  if(page+i<=totalPage){
                      pages.add(page+i);
                  }
              }
      
              if(page==1){
                  showPrevious=false;
              }
              else{
                  showPrevious=true;
              }
              if(page.equals(totalPage)){
                  showNext=false;
              }
              else{
                  showNext=true;
              }
              if(pages.contains(1)){
                  showFirstPage=false;
              }else {
                  showFirstPage=true;
              }
              if(pages.contains(totalPage)){
                  showEndPage=false;
              }else {
                  showEndPage=true;
              }
          }
      }
      

      注:在此DTO中沒有get,set方法是因為使用了Lombok中的@Data方法


不足與展望改進

還有很多地方需要改進,還有許多功能不夠完善,基本沒有處理高併發的能力,希望後續可以完善其功能。