1. 程式人生 > 資訊 >長城汽車魏牌 3 月銷量達 4755 輛,環比增長 7.3%

長城汽車魏牌 3 月銷量達 4755 輛,環比增長 7.3%

一步一步copy一個自己的網站

前言:之前學完ssm框架就想自己搭建一個網站了,但苦於ssm的配置地獄,最近發現boot搭建簡易網站的難度要小得多,就著手準備起來了

1. 基礎搭建

1.1 搭建一個簡易的springboot專案

包括初始化、資源路徑資料夾、架構層資料夾、匯入thymeleaf依賴並關閉thymeleaf快取

1.2 加入靜態資源

由於自身的前端框架確實太拉,做不出好看的介面,就直接下載的現成的模板,大概是這樣子的,也推薦下這個網站,搜了好多後臺模板網站都是收費的,只有這個是公開的站長素材

我是將所有的頁面都放進了templates資料夾,將css,js,圖片等都放進了static資料夾。

1.3 用thymeleaf和controller渲染所有互動介面

  • 普通靜態資源,像js,css,圖片這些不需要服務,只需要連結就行,原本是這樣的,用thymeleaf接管的好處是它能動態適應路徑

修改後是這樣的,th爆紅的可能是因為沒有加thymeleaf名稱空間

  • 跳轉靜態資源,比如說html網頁,需要整合controller,編寫一個controller類,然後跳轉的URl寫成控制mapping就行了

    @Controller
    public class IndexController {
        @RequestMapping("/toIndex")
        public String toIndex(){
            return "index";
        }
        .....
    }      
    

1.4 抽取公共部分

我選的這個網頁的公共部分有頂部工具欄、側邊欄和底部,抽取的方式是用thymeleaf的fregment標註id,再用insert注入

1.5 遇到的問題

  • 靜態資源丟失問題,最最噁心的問題之一

    我目前遇到的三種情況

    第一種,資源路徑問題,我開始寫的資源路徑是對的,因為我清楚的直到boot專案有預設的資源路徑,但結果頁面演示,就這個功能坑了我,它怎麼也載入不出來,我昧著我的良心修改了好多次路徑,都不行,在我偶然運行了一次專案後發現成功載入了,頓時人都不好了,這問題也是最經典的boot關於靜態資源的問題,我在boot筆記也總結過相關

第二種,thymeleaf快取問題,專案自身的快取,當你修改了靜態資源,開開心心的去執行專案時,他提交給網頁的卻是上次載入快取在jar包裡的,所以靜態資源修改了但沒有完全修改

解決辦法是在配置裡關掉thymeleaf快取

spring.thymeleaf.cache=false

第三種,瀏覽器快取,和第二種比較像,不過快取是瀏覽器幫你做的,真是謝謝了,平時看來很方便的一個功能卻成了雞肋,而且特別噁心人,瀏覽器提供的清除會清除掉cookie和一些自動幫你填入賬號密碼的記錄,我又不想刪除這些,只能選擇每次關閉瀏覽器清除圖片和靜態資源,每次測試都得關一次瀏覽器

2022.3.29 時隔多月,我回來了,自從簽了工作之後一直在擺爛,最近也是畢設題目剛好是一個網頁專案,就想著順便拿這個做個記錄;

淺談一下新專案吧,新專案是一個會議室預約管理的專案,我也是打算採用springboot去做成一個前後端Web專案,再接著前面的準備工作後,這次我選擇了整合springsecurity去認證和授權。

1.6 整合springsecurity測試

比較令我頭疼的是,整合在最開始的測試就失敗了(用security的原生登入頁面登入不進去,顯示賬號密碼錯誤),原因我至今還沒摸清楚,但是我可以淺談一下我排除的一些錯誤
①密碼編碼問題
security選擇將前端輸入的密碼進行編碼後再傳輸到後端進行邏輯比較(這情況只在高版本出現),那麼如果後端的密碼是直接從資料庫取出來的或者後端直接指定的,那麼顯然密碼比對不會正確,所以要在後端進行密碼編碼

protected void configure(AuthenticationManagerBuilder auth) throws Exception {
          //定義一個使用者以及他的許可權等級
  auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())//定義前端編碼方式
    .withUser("amlia").
        password(new BCryptPasswordEncoder().encode("123456")).roles("許可權1","許可權2");
}

②未開啟security註解

@EnableWebSecurity
//springsecurity的配置類必須加這個註解告訴springboot開啟安全模式
//如果security配置類沒有被boot識別,還需要再加上@Configuration表明這是一個配置類

③依賴衝突+版本問題

這是我在排除了上面兩種可能性之後比較懷疑的,因為我為了後期方便,在專案初始化的時候勾選了很多依賴,但是我後來重新導了一遍衝突,甚至只加了Jdbc和Web的依賴,還是不行,最後我使用之前的MyWeb專案重置了才可以。

1.7 自定義security登入頁

在使用Web專案“舊換新”的方法解決了登入問題後,當然是自定義登入頁,自定義登入頁有一些注意事項,也就是要開始授權,這是初步授權的配置:

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.formLogin()
                .loginPage("/toLogin")//登入頁
                .loginProcessingUrl("/toLogin")//登陸訪問路徑
        .defaultSuccessUrl("/toHome").permitAll()//登入成功跳轉頁面
        .and().authorizeRequests()
        //放行登入介面和靜態資源
        .antMatchers("/","/toHome","login.html","/css/**","/js/**","/images/**").permitAll()
        .anyRequest().authenticated();
        //所有請求都必須被認證,必須登入後才能訪問
    }

1.8 登入問題

2022.4.10 這個問題在最開始測試登入的時候並沒發現,但是最近在測試前端渲染的時候需要重複重新整理頁面測試,而且我設定了頁面攔截。也就是說每次重新整理都要重新登入一遍,看起來沒問題,但是每次重新整理後的第一次登入總是會出問題,報404錯誤。

最開始我懷疑是不是預設的錯誤路徑/login/error?前面的/login路徑和登入路徑/login衝突了,我自定義了錯誤頁面並自定義了失敗路徑.failureUrl("/toError"),但是問題沒有得到解決

第一次登入跳轉到了一個空白頁面,並且路徑是/fonts/fontawesome-webfont.ttf?v=4.7.0

我在專案裡並沒有找到這個路徑,我又懷疑是不是缺少了這個檔案,我還去fontawesome官網下載了這個檔案(真是大冤種)新建了一個對應路徑把他放了進去,結果還是不行,一樣的錯誤但是空表頁面顯示了404,結果我就是給404下載了個樣式:-(

在搜尋了相關問題後,最後找到了解決辦法,原來是重定向的問題,每次重新整理之前,比如我之前停留在一個toRoom路徑的介面,重新整理了之後由於受到了springsecurity的攔截,本應該走向錯誤介面,但是此時springsecurity將其強制跳轉到登入介面,在使用者登陸完成之後,預設跳轉到了剛才的錯誤介面,恍然大悟了

要改變這個很簡單,只需要在.defaultSuccessUrl("/toHome")後加個true就可以

.defaultSuccessUrl("/toHome",true)

百度是這麼說的

.defaultSuccessUrl ("/", true) 如果您未將其設定為 true ,它將自動將使用者重定向到上一個上一個請求,在我的情況下,上一個請求將傳送到url /error ,這解釋了我的問題。 通過將其設定為 true ,您可以強制將使用者重定向到您 defaultSuccessUrl 所需要的任何位置。

2.資料庫整合

由於我感覺這個小專案的sql語句可能並不複雜,我使用的是JdbcTemplate

資料庫結構初步是這樣的

編寫對應的對映實體類就行,對於關係表的對映,我使用的是Map集合

private Map<Meetroom,Equipment> belongList

新增資料之後,在test裡進行測試,測試出來是成功的,證明資料庫連線和Template都是沒問題的

@SpringBootTest
class MyWebApplicationTests {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Test
    void contextLoads() {
        List<Map<String,Object>> list = jdbcTemplate.queryForList("select * from user");
        for (Map<String, Object> map : list) {
            System.out.println(map.toString());
        }
    }
}

之後我編寫了對應的dao層介面,最開始,我是這樣用的,關係類直接走dao層,然後就用queryForList查找出來一個一個去賦值,meetroomService和userService是一樣的

@Data
@Component
public class Occupy {
    private Map<Meetroom,User> occupyList;
    @Autowired
    MeetroomService meetroomService;
    @Autowired
    UserService userService;
    @Autowired
    JdbcTemplate jdbcTemplate;
    public Occupy() {
        occupyList = new HashMap<>();
        List<Map<String,Object>> list = jdbcTemplate.queryForList("select * from occupy");
        for (Map<String, Object> map : list) {
            occupyList.put(meetroomService.findRoomById((int)map.get("rid")), userService.findUserById((int)map.get("eid")));
        }
    }
}

但是是行不通的,一直報空指標異常,最開始我以為是 JdbcTemplate物件沒有注入成功,但是boot官方文件確實是這麼教的,所以我一度很懷疑自己,也懷疑是不是和@Component註解衝突的問題 ,網上確實有隻言片語這麼說,但是我如果放到測試類裡就是好的。證明這個不是問題的關鍵

這個bug困擾了我許久,在輾轉反側幾天後,發現了問題的根源,空指標是語句查詢返回空報的,這是springdata的防禦機制,為了讓程式設計師的程式碼更健壯,防止對null值得錯誤比較,所以是語句的問題,語句需要在加一些約束,不能丟簡單的sql,而且為了使程式碼更具有專業性,我還丟了一個對映類進去,對arm進行解釋,這樣其實已經有點像Mybatis模式了

@Repository
public class EquipmentService {
    @Autowired
    JdbcTemplate jdbcTemplate;

    public List<Equipment> findAllEquip() {
        return jdbcTemplate.query("select * from equipment", new EquipRowMapper());
    }

    public Equipment findEquipById(int id) {//根據裝置id查詢到裝置
        return jdbcTemplate.queryForObject("select * from equipment where eid=?", new Object[]{id}, new EquipRowMapper());
    }
}
class EquipRowMapper implements RowMapper<Equipment> {//對映
    @Override
    public Equipment mapRow(ResultSet rs, int rowNum) throws SQLException {
        Equipment equipment = new Equipment();
        equipment.setEid(rs.getInt("eid"));
        equipment.setEname(rs.getString("ename"));
        equipment.setEstate(rs.getBoolean("estate"));
        return equipment;
    }
}

2022.4.2 在jdbcTemplate方式下的開發遇到了瓶頸,苦於我對jdbcTemplate的對映方式不是很清楚,導致關聯表的對映沒法進行,是這樣的,我有一個使用者表和一個會議室表,還有一個額外的表格專門用來記錄他們之間的關係,列值就用使用者id和會議室號,我想的是返回一個Map<Integer,Integer>來儲存比較利於後續的使用,但是我不懂如何用jdbcTemplate去對映,搜尋了一天也無果,好像對映只能用自定義類,像Integer這種沒有set方法去設定值得就很麻煩,但是為其專門自定義一個類又很多此一舉,倒不如直接把關係表對映到一個實體類上,比如這樣

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Occpy {
    private Map<Integer,Integer> occpyMap;
}

但實際上,除此之外,我還有一個關係表用來表示會議室和裝置之間的關係,如果要為關係表設定實體類,會顯得dao層和service層很臃腫,最終還是決定放棄掉用jdbcTemplate去操作資料庫

2022.4.6 經過幾天的溫習Mybatis和重寫架構,新資料庫架構已經初步完成,總體思路是修改資料庫架構,因為多表子查詢對Mybatis也異常複雜,我把關係表改成了多對一繫結,比如一個人可能預約多個會議室,但是會議室同一時間只能被一個人預約,那麼在會議室裡加一列使用者id用來繫結使用者,為空時證明會議室無人預約,這樣一來只用一個簡單的子查詢就能解決,而且整個架構也很清晰

簡單舉例一個UserMapper.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="www.amlia.com.demo.mapper.UserMapper">
    <select id="findUserById" parameterType="int" resultMap="MyUser">
        SELECT * FROM user WHERE uid=#{id}
    </select>
    <select id="findGradeById" parameterType="int" resultType="Integer">
        select ugrade from user where uid=#{id}
    </select>
    <select id="findAllUser" resultMap="MyUser">
        select * from user meetroom
    </select>
    <insert id="addUser" parameterType="User">
        insert into user (uid,ugrade,uname,unumber,upassword,phone) values
        (#{uid},#{ugrade},#{uname},#{unumber},#{upassword},#{phone})
    </insert>
    <update id="updateUser" parameterType="User">
        update user set uname=#{uname},unumber=#{unumber},ugrade=#{ugrade}
        ,upassword=#{upassword},phone=#{phone}
    </update>
    <delete id="deleteUser" parameterType="int">
        delete from user where uid=#{id}
    </delete>
    <select id="findRoomByUser" parameterType="int" resultType="Meetroom">
        select * from meetroom where uid=#{id}
    </select>
    <!--由於我的資料庫列名和類屬性名基本一樣,這裡沒進行對映-->
    <resultMap id="MyUser" type="User">
        <collection property="roomList" column="uid" ofType="Meetroom" javaType="java.util.List" select="findRoomByUser"/>
    </resultMap>
</mapper>

2.3 遇到的問題

2022.4.10 這裡解決一下在3.3遇到的問題,我對所有資料庫進行了測試,發現id相關的全都輸出為0

我檢查資料庫列名和類屬性的確能對應上,因為我當時為了方便,把兩個名字定義的一模一樣,所以不存在對映不上的問題,我又檢查了屬性型別發現也確實都是int,隨後我發現了,在聯合查詢下,子查詢的結果id是可以顯示的,我立馬感覺到問題所在了。由於我把屬性名和列名定義的一樣,所以在聯合查詢的mapper結果集對映裡沒有進行對映,按理說可以不用,但是問題出在,我的meetroom表裡也存了一個uid,兩個衝突了,所以id的對映是必須的

   <resultMap id="MyUser" type="User">
        <result column="uid" property="uid"></result>
        <collection property="roomList" column="uid" ofType="Meetroom" javaType="java.util.List" select="findRoomByUser"/>
    </resultMap>

加了對映之後,輸出正確

3.前端渲染

3.1 首頁標題欄高亮

一般我們都希望將當前所顯示的大標題高亮起來,而且會隨著我點選不同的標題去動態高亮,這個該如何實現呢,正好這個模板也沒有用到高亮,我也去學習了一下相關知識,標題這快的程式碼是這樣的

<div class="collapse navbar-collapse" id="navbarsExample04">
                        <ul class="navbar-nav mr-auto nav-pills" id="myul">
                           <!--這個active其實就是高亮,他是vue中的一個元件-->
                           <li class="nav-item active">
                              <a class="nav-link " th:href="@{/toHome}">首頁</a>
                           </li>
                           <li class="nav-item">
                              <a class="nav-link" th:href="@{/toLogin}">登入</a>
                           </li>
                           <li class="nav-item">
                              <a class="nav-link " th:href="@{/toRoom}">會議室</a>
                           </li>
                           <li class="nav-item">
                              <a class="nav-link" th:href="@{gallery.html}">通告</a>
                           </li>
                           <li class="nav-item">
                              <a class="nav-link" th:href="@{blog.html}">歷史評價</a>
                           </li>
                           <li class="nav-item">
                              <a class="nav-link" th:href="@{login.html}">我的</a>
                           </li>
                        </ul>
                     </div>

知道了active就是高亮,該怎麼讓它動態顯示呢,這就要用到js去實現,這串程式碼充分顯示了js和java的相似性

$(document).ready(function () {
    //each是為每一個選中的元素該方法
    $("#myul").find("li").each(function () {
        var a = $(this).find("a:first")[0];//定義一個變數儲存當前的第一個a標籤,事實上每個li也只有一個標籤所以這樣寫
        // location.pathname獲取當前瀏覽器上的url地址,檢視與之相同的href即是當前點選的標題
        if ($(a).attr("href") == location.pathname) {
            $(this).addClass("active");
        } else {
            $(this).removeClass("active");//remove為了取消預設首頁高亮
        }
    });
});

用js標籤直接寫到html裡或者存在檔案裡然後引用都可以

3.2 插入模板

2022.4.11因為原先的模板資源有限,並沒有我想要的一些頁面,比如個人資訊頁,直接的方式就是再拿一個模板,把想要的html程式碼插入到我們的頁面中。但是這樣會帶來一個問題,就是css-class命名衝突問題,因為模板的很多命名都是偏系統的,比較相似,衝突問題會導致模板不能呈現出想要的結果,像這樣:

就導致了不能單純引入插入模組的樣式,需要有一些手段,最開始我想的比較簡單,就是說在div塊裡有沒有一種引用方式可以引用css樣式,也就是說,使css樣式只針對部分div生效,但是搜尋無果,有一個可能的解決方式是css-moudle,但是礙於我的前端知識過於薄弱,看了半天就好像看天書一般。最後耽擱了幾天,選擇了一種暴力的方式,就是我把插入模板要引用的css全部整合到一個css裡面,然後對其所有引用到的class命進行統一修改,ctrl+f搜尋class名,點選這個選中所有就可以統一修改

3.3 渲染資料

對於個人資訊頁面的資料渲染,總體就是用thymeleaf提供的api去實現,非常簡單,拿使用者姓名的渲染舉例,我們需要後端首先獲取到當前登入使用者的資訊,由於我們使用了springsecurity框架,這點變得很容易,只需要定義一個引數Principal principal,這個類物件其實就是springsecurity為我們封裝的使用者資訊

@Autowired
    UserService userService; 
@RequestMapping("/toMine")
    public String toMine(Model model,Principal principal){
        List<User> list = userService.findAllUser();//獲取一個使用者列表
        for (User user : list) {
            if(user.getUnumber().equals(principal.getName()))
                model.addAttribute("user",user);//找到當前使用者,返回給前端
        }
        return "mine";
    }

前端方面,直接用th:text="${}"獲取文字

 <h4 class="card-title m-t-10" th:text="${user.getUname()}"></h4>

如果是迴圈的話,用th:each,比如我的使用者裡存的會議室list就可以這麼渲染到前端,有點類似於foreach迴圈

 <div th:each="room:${user.getRoomList()}">
                           <h6 th:text="${room.getRname()}"/><a href="#" class="btn btn-success" th:text="${room.getRgrade()}"></a>
                           <a href="#" class="btn btn-success">取消預訂</a><br>
                           <br>
                        </div>

但是在進行渲染頭像的時候出了問題,因為頭像每個人是不同的,而且要根據uid去動態渲染,thymeleaf也有這種用法,比如在路徑裡我需要提取uid,就可以這樣

<img th:src="@{images/profile/{jpgName}.jpg(jpgName=${user.getUid()%10000})}" class="img-circle" width="150">

我的頭像路徑是images/profile/*.jpg, 星號代表的是使用者uid對10000取餘,寫法是沒問題的,但是無論如何也載入不出來問題,排除了路徑、快取等等問題後,我在前端檢視原始碼發現了,不管使用者登入為什麼,取餘都是0,我起初以為是寫法的錯誤導致傳過來為null值然後被初始化了,但是我試了其他的使用者等級發現是可以的,我在後臺測試了資料庫的輸出,這才恍然發現,uid從查過來就一直是空的,這個問題我放到資料庫那個模組了 ↑,解決之後果然渲染了正確頭像

4.增刪改查

4.1 我的頁面

4.1.1 評論系統

為了先簡單的測試一下增刪改查的可用性,我新增了一個數據庫表blog,blog裡儲存的是使用者的評論,以及通告等,這是幾個例子

@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Blog {
    private int replyid;//回覆的使用者id
    private int uid;//發表評論的使用者id
    private int bid;//唯一標識一個評論的id
    private String blog;//發表的評論
    private String picture;//發表的圖片
    private int rid;//評論針對的會議室
    private Date date;//發表的時間
}

評論我打算展示在我的頁面中,渲染完之後是這樣

回覆他按鈕會新增一個評論,並且指向這個人,具體的前端程式碼是這樣

<div class="tab-pane active" id="home" role="tabpanel">
 <div class="card-body">
  <div class="profiletimeline">
   <div class="sl-item" th:each="replyBolg:${replyBlogs}">
    <div class="sl-left"> <img th:src="@{images/profile/{jpgName}.jpg(jpgName=${replyBolg.getUid()%10000})}" alt="user" class="img-circle"> </div>
    <div class="sl-right" th:with="user1=${userService.findUserById(replyBolg.getUid())}">
    <div><a href="#" class="link" th:text="${user1.getUname()}"></a> <span class="sl-date" th:text="${replyBolg.getDate().toString()}"></span>
       <p th:text="${replyBolg.getBlog()}">
       <div class="row">
        <div class="col-lg-3 col-md-6 m-b-20"><img th:src="@{images/img1.jpg}" class="img-responsive radius"></div>
       </div>
       <form th:action="@{/ReplyToOther}" th:method="post" th:object="${blog}">
          <input  type="hidden" th:name="uid" th:value="${user.getUid()}">
          <input  type="hidden" th:name="replyid" th:value="${replyBolg.getUid()}" >
          <input  type="hidden" th:name="date" th:value="${#dates.format(#dates.createNow(),'yyyy-MM-dd')}">
          <input placeholder="輸入評論" th:name="blog"><button class="btn-primary">回覆他</button>
       </form>
      </div>
     </div>
    </div>
    <hr>
  </div>
 </div>
</div>

這裡涉及一個知識點,就是themeleaf表單向後端返回bean,th:object去宣告這個物件,前提是這個物件要先從後端傳到前端來,所以實質上themeleaf只是在修改這個物件的屬性,在返回給前端,th:name是這個物件的屬性名,th:value是要賦給它的值,要記住,這個物件所對應的類必須要有對應的set方法

Controller層是這樣的

@RequestMapping("/ReplyToOther")
    public String ReplyToOther(Blog blog){
        blogService.addBlog(blog);
        return "redirect:/toMine";
    }

這裡我遇到了一個問題,就是在資料庫裡,沒有賦值的情況下,int型的初值會是null,但是java裡沒有賦值的int型是0,也就是說,當我新建了一個回覆評論時,他是沒有roomid的,回覆評論只儲存被回覆使用者的id,也就造成了傳到資料庫的roomid值為0,但是這個回覆評論也需要在我的評論裡展示

由於我在我得評論裡展示了會議室的名字,所以這個回覆評論的roomid查找不出來報錯了,也促發了我的另一種想法,用th:if去判斷存在性,然後決定是否展出

<label class="col-md-12" th:if="${myBlog.getRid()!=0}" th:text="${room.getRname()}+會議室"></label>
<label class="col-md-12" th:if="${myBlog.getReplyid()!=0}"         th:with="replyuser=${userService.findUserById(myBlog.getReplyid())}" th:text="回覆 +${replyuser.getUname()}"></label>

圖片目前還沒有解決,涉及更多的問題

4.1.2 取消預約會議室

我的預約資訊是在meetroom表裡繫結的,就是每個會議室綁定了一個使用者id,所以取消預約實質上是對會議室資訊的修改,但是我為了展示方便,在實體類裡,用了一個User物件去儲存,這也直接導致了修改的困難,總不能是uid=#{user.geiUid()},實不相瞞,我確實試過這個,最終是新加了一個sql解決的,兩引數,一個是rid用來精準查詢,一個是uid用來修改。

int deleteUser(@Param("uid")int uid,@Param("rid")int rid);
<update id="deleteUser">
    update meetroom set uid=#{uid} where rid=#{rid}
    </update>