注冊一個(gè)網(wǎng)站營銷團(tuán)隊(duì)找產(chǎn)品合作
從 0 開始實(shí)現(xiàn)一個(gè) SpringBoot + Vue 項(xiàng)目
- 從 0 開始實(shí)現(xiàn)一個(gè) SpringBoot + Vue 項(xiàng)目
-
- 軟件和工具
- 創(chuàng)建 SpringBoot 后端項(xiàng)目
- 創(chuàng)建 MySQL 數(shù)據(jù)庫
- 配置文件
- 實(shí)現(xiàn)增刪改查接口
-
- Model 層
- mapper 層
- service 層
- controller 層
- 測試
- 實(shí)現(xiàn)項(xiàng)目功能接口
-
- 代碼
- 測試
- 創(chuàng)建 Vue 前端
-
- 安裝 Node.js
- 配置 npm 鏡像
- 安裝腳手架
- 創(chuàng)建并配置項(xiàng)目
- 項(xiàng)目結(jié)構(gòu)
- Vue 組件結(jié)構(gòu)
- Vue 組件調(diào)用與傳值
- Vue 組件的生命周期
- 測試 Vue 程序
- 需求分析
- 實(shí)現(xiàn)項(xiàng)目頁面
-
- 框架搭建
- 項(xiàng)目配置
- 主界面設(shè)計(jì)
- 設(shè)置路由
- 內(nèi)容組件設(shè)計(jì)
- 發(fā)送組件設(shè)計(jì)
- 分頁面設(shè)計(jì)
-
- 排行頁面 LikesSortedView.vue
- 最新頁面 NewestView.vue
- 隨機(jī)頁面 RandomView.vue
- 項(xiàng)目啟動(dòng)
- 源碼下載
從 0 開始實(shí)現(xiàn)一個(gè) SpringBoot + Vue 項(xiàng)目
參考:夢想屋A
軟件和工具
- 后端開發(fā)軟件:IntelliJ IDEA
- 前端開發(fā)軟件:Visual Studio Code
- 后端框架:SpringBoot
- 后端語言:Java
- 前端框架:Vue
這是后面要用的妙妙小工具:
- 服務(wù)器連接工具:Termius
- 數(shù)據(jù)庫:MySQL
- 數(shù)據(jù)庫管理工具:Navicat Premium
- 數(shù)據(jù)庫連接工具:MyBatis
- API 文檔生成工具:Swagger
- API 文檔美化工具:Knife4j
- UI 組件庫:Element
- 網(wǎng)絡(luò)請求庫:Axios
- 字體處理庫:Sfntly
- JSON 處理工具:Fastjson
- Java 工具庫:Lombok
可以不必全部用這些來做,有很多類似的產(chǎn)品可以替代。
創(chuàng)建 SpringBoot 后端項(xiàng)目
首先我們打開 IDEA,點(diǎn)擊新建項(xiàng)目,選擇 Spring Initializr,然后在右側(cè)填寫項(xiàng)目名稱,類型選擇 Maven,JDK 版本選擇1.8,如下圖所示,然后點(diǎn)擊下一步。
在新的頁面中選擇 SpringBoot 版本 3.0.2,引入一些依賴,點(diǎn)擊創(chuàng)建。
后面在 pom.xml 中把 Java 版本改回 1.8,SpringBoot 版本改回 2.7.6。
項(xiàng)目結(jié)構(gòu):
可以看到 SpringBoot 的基礎(chǔ)結(jié)構(gòu)有三個(gè)文件:
- src/main/java 下的程序入口:DreamHouseApplication
- src/main/resources 下的配置文件:application.properties
- src/test 下的測試入口:DreamHouseApplicationTests
在運(yùn)行類 DreamHouseApplication 同級目錄下創(chuàng)建 controller、service、mapper、model 四個(gè)目錄:
controller 層:控制層
- 作用是請求和響應(yīng)控制。
- 負(fù)責(zé)前后端交互,接受前端請求,調(diào)用 service 層,接收 service 層返回的數(shù)據(jù),最后返回具體的頁面和數(shù)據(jù)到客戶端。
service 層:業(yè)務(wù)邏輯層
- 作用是完成功能設(shè)計(jì)。
- 調(diào)用 mapper 層接口,接收 mapper 層返回的數(shù)據(jù),完成項(xiàng)目的基本功能設(shè)計(jì)。
mapper 層:數(shù)據(jù)持久層,也被稱為 dao 層
- 作用是訪問數(shù)據(jù)庫,向數(shù)據(jù)庫發(fā)送 sql 語句,完成數(shù)據(jù)的增刪改查任務(wù)。
model 層:數(shù)據(jù)庫實(shí)體層,也被稱為 entity 層、pojo 層
- 用于存儲數(shù)據(jù)庫中的數(shù)據(jù),類屬性與數(shù)據(jù)庫表字段對應(yīng)。
- 通常情況下,Model 層使用 ORM(對象關(guān)系映射)技術(shù),如 JPA,MyBatis 等與數(shù)據(jù)庫進(jìn)行交互。
在項(xiàng)目目錄中的 pom.xml 配置文件中引入本次項(xiàng)目中需要用到的相關(guān)依賴:
<!--MyBatis--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.0</version></dependency><!--Swagger3--><dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version></dependency><!--knife4j--><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-ui</artifactId><version>3.0.3</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><!--JSON--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.15</version></dependency><!--MySQL--><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>8.0.31</version><scope>runtime</scope></dependency><!--Lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
將這些依賴放到標(biāo)簽內(nèi),就接著 SpringBoot 自帶的一些依賴下面就行了。
稍微解釋一下這些依賴:
-
Lombok:可以通過注解的方式為 Java 類生成許多常用的方法,例如 getter、setter、toString、equals 等,省去了手寫這些方法的代碼。
-
Swagger:開源的 API 文檔生成工具,可以根據(jù)代碼中的注釋自動(dòng)生成 API 文檔。
-
Knife4j:用來美化 Swagger 生成的 API 文檔。
-
MyBatis:持久層框架,主要用于簡化數(shù)據(jù)庫操作,提高代碼可讀性,降低數(shù)據(jù)訪問代碼的維護(hù)難度,提高效率。
-
MySQL:MySQL 官方提供的 Java 用的 JDBC 驅(qū)動(dòng),它允許 Java 程序通過 JDBC API 連接到 MySQL 數(shù)據(jù)庫。
-
JSON:用來將 json 數(shù)據(jù)序列化和反序列化的,主要是覺得 SpringBoot 自帶的 json 工具不好用。
創(chuàng)建 MySQL 數(shù)據(jù)庫
安裝 MySQL 這里就不講了,這里用 Navicat Premium 連接數(shù)據(jù)庫。
填寫連接名、主機(jī)IP、端口、用戶名和密碼等,然后點(diǎn)擊連接。
可以先點(diǎn)測試連接,顯示連接成功,在點(diǎn)確定。
連接上數(shù)據(jù)庫之后,新建數(shù)據(jù)庫–>新建表–>添加 id、ip、province、time、str、likes 六個(gè)字段。
表名為 dream,具體內(nèi)容如下所示:
這里有兩個(gè)細(xì)節(jié):
- id 要設(shè)置自動(dòng)遞增
- likes 設(shè)置為無符號
友情鏈接:mysql自增navicat_navicat怎么設(shè)置主鍵自增
配置文件
讓我們回到 SpringBoot,打開剛剛介紹到的配置文件 application.properties 進(jìn)行項(xiàng)目配置:
server.port = 8087spring.mvc.pathmatch.matching-strategy = ant_path_matcher# Swagger
swagger.enabled = true# MySQL
spring.datasource.url = jdbc:mysql:(服務(wù)器IP地址):3306/(數(shù)據(jù)庫名)
spring.datasource.username = (用戶名)
spring.datasource.password = (密碼)
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
稍微解釋一下:
-
server.port:指定項(xiàng)目使用 8087 端口,默認(rèn) 8080 端口。
-
spring.mvc.pathmatch.matching-strategy:指定 Spring MVC 框架中 URL 路徑匹配策略的實(shí)現(xiàn)類的類名,ant_path_matcher 是一種路徑匹配策略,使用 Ant 風(fēng)格的通配符來匹配 URL 路徑。
-
swagger.enabled:啟用 Swagger 工具。
-
spring.datasource.url:指定 MySQL 數(shù)據(jù)庫的 URL。這里的服務(wù)器 IP 地址就是主機(jī)名,我的是 localhost(前面要帶//),數(shù)據(jù)庫名是 dreamhouse,完整格式:jdbc:mysql://localhost:3306/dreamhouse。
-
spring.datasource.username:指定連接 MySQL 數(shù)據(jù)庫使用的用戶名。我的是 root。
-
spring.datasource.password:指定連接 MySQL 數(shù)據(jù)庫使用的密碼。
-
spring.datasource.driver-class-name:指定連接 MySQL 數(shù)據(jù)庫使用的 JDBC 驅(qū)動(dòng)程序的類名。
這里可能會有注釋中文亂碼問題,在設(shè)置中可以解決,全部換成 UTF-8:
友情鏈接:【SpringBoot2】讀取配置application.properties文件亂碼問題解決
第一次用要安裝點(diǎn)驅(qū)動(dòng)啥的,點(diǎn)一下就安裝好了,沒截到圖。
配置好以后,可以測試連接:
也可以在 IDEA 看到數(shù)據(jù)庫:
實(shí)現(xiàn)增刪改查接口
Model 層
回顧一下 model 層:數(shù)據(jù)庫實(shí)體層,也被稱為 entity 層、pojo 層
- 用于存儲數(shù)據(jù)庫中的數(shù)據(jù),類屬性與數(shù)據(jù)庫表字段對應(yīng)。
- 通常情況下,Model 層使用 ORM(對象關(guān)系映射)技術(shù),如 JPA,MyBatis 等與數(shù)據(jù)庫進(jìn)行交互。
在 model 目錄下新建 Data 類:
package com.example.dream_house.model;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;/*** @BelongsProject:dream_house* @BelongsPackage:com.example.dream_house.model* @Author:Uestc_Xiye* @CreateTime:2023-12-17 16:29:49*/@lombok.Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("數(shù)據(jù)庫字段")
public class Data {@ApiModelProperty(value = "信息所屬ID", required = true, example = "1")private int id;@ApiModelProperty(value = "信息來源IP地址", required = true, example = "127.0.0.1")private String ip;@ApiModelProperty(value = "信息來源所屬省份", required = true, example = "廣東")private String province;@ApiModelProperty(value = "內(nèi)容發(fā)布時(shí)間", required = true, example = "2023-12-17 16:58:00")private String time;@ApiModelProperty(value = "夢想內(nèi)容", required = true, example = "環(huán)游世界!")private String str;@ApiModelProperty(value = "點(diǎn)贊數(shù)", required = true, example = "52")private int likes;
}
接下來說一下這段代碼中的各個(gè)注解的作用:
-
@lombok.Data:這是 Lombok 框架提供的注解,它會自動(dòng)生成 getter、setter、toString、equals、hashCode 等方法。使用該注解可以簡化代碼,并提高開發(fā)效率。
-
@NoArgsConstructor:這也是 Lombok 提供的注解,它會生成一個(gè)無參構(gòu)造器,可以避免手動(dòng)編寫無參構(gòu)造器。這個(gè)注解常用于一些框架或工具的實(shí)例化。
-
@AllArgsConstructor:同樣是 Lombok 提供的注解,它會生成一個(gè)全參構(gòu)造器,可以避免手動(dòng)編寫全參構(gòu)造器。這個(gè)注解也常用于一些框架或工具的實(shí)例化。
-
@ApiModel:這是 Swagger 框架提供的注解,用于描述一個(gè)模型類。這個(gè)注解的作用是將模型類描述為一個(gè) API 文檔的模型,可以通過該注解指定模型類的名稱和描述信息。
-
@ApiModelProperty:也是 Swagger 框架提供的注解,用于描述模型類中的屬性信息。該注解可以設(shè)置屬性的名稱、描述、是否必需等信息,以便在 Swagger 生成的 API 文檔中顯示。
-
value:屬性的描述信息,用于在 API 文檔中顯示該屬性的作用。
-
required:屬性是否必需。當(dāng)該值為 true 時(shí),表示該屬性必須包含在請求中;當(dāng)該值為 false 時(shí),表示該屬性可以為空或者不包含在請求中。
-
example:屬性的示例值。用于在 API 文檔中顯示該屬性的樣例值,方便開發(fā)者理解該屬性的類型和取值范圍。
-
mapper 層
回顧一下 mapper 層:數(shù)據(jù)持久層,也被稱為 dao 層
- 作用是訪問數(shù)據(jù)庫,向數(shù)據(jù)庫發(fā)送 sql 語句,完成數(shù)據(jù)的增刪改查任務(wù)。
在 mapper 目錄下新建 DataMapper 接口:
package com.example.dream_house.mapper;import com.example.dream_house.model.Data;
import org.apache.ibatis.annotations.*;/*** @BelongsProject:dream_house* @BelongsPackage:com.example.dream_house.mapper* @Author:Uestc_Xiye* @CreateTime:2023-12-17 16:36:57*/@Mapper
public interface DataMapper {/*** 信息來源IP地址* @param ip* 信息來源省份* @param province* 信息發(fā)出時(shí)間* @param time* 信息內(nèi)容* @param str* 點(diǎn)贊數(shù)* @param likes** @return*/@Insert("insert into dream (ip, province, time, str, likes) values(#{ip}, #{province}, #{time}, #{str}, #{likes})")int insert(@Param("ip") String ip,@Param("province") String province,@Param("time") String time,@Param("str") String str,@Param("likes") int likes);/*** 信息id* @param id** @return** property屬性對應(yīng)Data對象中的成員名,column對應(yīng)select出的字段名。*/@Results({@Result(property = "id", column = "id"),@Result(property = "ip", column = "ip"),@Result(property = "province", column = "province"),@Result(property = "time", column = "time"),@Result(property = "str", column = "str"),@Result(property = "likes", column = "likes")})@Select("select * from dream where id = #{id}")Data findById(@Param("id") int id);/*** 用Data對象來作為傳參,這樣語句中的#{id}、#{ip}等數(shù)據(jù)就分別對應(yīng)Data對象中的id和ip等屬性。** @param data*/@Update("update dream set ip=#{ip}, province=#{province}, time=#{time}, str=#{str}, likes=#{likes} where id=#{id}")void update(Data data);/*** 刪除該id對應(yīng)的信息** @param id*/@Delete("delete from dream where id =#{id}")void delete(int id);}
相關(guān)注解的作用:
-
@Mapper:是 MyBatis 框架提供的注解,用于標(biāo)記一個(gè) Java 接口,該接口用于定義數(shù)據(jù)訪問方法。在使用 @Mapper 注解后,MyBatis 會自動(dòng)掃描該接口,為其創(chuàng)建一個(gè)代理對象。該代理對象可以將接口方法與 MyBatis 的 SQL 映射文件中的 SQL 語句進(jìn)行綁定,并完成數(shù)據(jù)訪問的操作。
-
@Insert:也是 MyBatis 框架提供的注解,該注解的值為 SQL 語句,用于指定插入操作的具體邏輯。該 SQL 語句使用了預(yù)處理語句,從而避免了 SQL 注入的問題。
-
@Param:
-
/** */中的內(nèi)容是 JavaDoc(Java文檔注釋),它用于對方法進(jìn)行說明、描述和文檔化。
-
在方法中的 @Param 注解用于指定參數(shù)的名稱,以便在 SQL 語句中使用相應(yīng)的占位符。
-
-
@Results:用于定義從查詢結(jié)果集中將查詢結(jié)果映射為 Java 對象的過程。
-
@Select:同樣是 MyBatis 框架提供的注解,該注解的值為 SQL 語句,用于指定查詢操作的具體邏輯。
-
@Update:MyBatis 框架提供的注解,用于指定更新操作的 SQL 語句。
-
@Delete:MyBatis 框架提供的注解,用于指定刪除操作的 SQL 語句。
service 層
簡單回顧一下 service 層:業(yè)務(wù)邏輯層
- 作用是完成功能設(shè)計(jì)。
- 調(diào)用 mapper 層接口,接收 mapper 層返回的數(shù)據(jù),完成項(xiàng)目的基本功能設(shè)計(jì)。
在 service 目錄下新建 DataService 類:
package com.example.dream_house.service;import com.example.dream_house.mapper.DataMapper;
import com.example.dream_house.model.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** @BelongsProject:dream_house* @BelongsPackage:com.example.dream_house.service* @Author:Uestc_Xiye* @CreateTime:2023-12-17 17:04:45*/@Service
public class DataService {@Autowiredprivate DataMapper dataMapper;/*** 新增信息** @param ip* @param province* @param time* @param str* @param likes* @return*/public String insert(String ip, String province, String time, String str, int likes) {dataMapper.insert(ip, province, time, str, likes);return "succeed";}/*** 查詢id對應(yīng)的信息** @param id* @return*/public Data findById(int id) {return dataMapper.findById(id);}/*** 更新信息** @param data*/public void update(Data data) {dataMapper.update(data);}/*** 刪除id對應(yīng)的信息** @param id*/public void delete(int id) {dataMapper.delete(id);}
}
相關(guān)注解的作用:
-
@Service:用于標(biāo)注一個(gè)類為 Spring 框架中的一個(gè)服務(wù)類,該類中通常包含了業(yè)務(wù)邏輯的實(shí)現(xiàn)。使用該注解可以使 Spring 框架自動(dòng)掃描并將該類實(shí)例化,并將其作為服務(wù)類注冊到容器中,以供其他組件使用。當(dāng)我們需要在其他類中使用該服務(wù)類時(shí),只需要通過依賴注入的方式獲取該類的實(shí)例即可。
-
@Autowired:用于實(shí)現(xiàn) Spring 框架中的自動(dòng)裝配功能,將需要使用的 Bean 對象注入到指定的屬性中。通過使用該注解,可以避免手動(dòng)創(chuàng)建 Bean 實(shí)例和手動(dòng)注入對象的麻煩。
controller 層
簡單回顧一下 controller 層:控制層
- 作用是請求和響應(yīng)控制。
- 負(fù)責(zé)前后端交互,接受前端請求,調(diào)用 service 層,接收 service 層返回的數(shù)據(jù),最后返回具體的頁面和數(shù)據(jù)到客戶端。
在 controller 目錄下新建 DataController 類:
package com.example.dream_house.controller;import com.example.dream_house.model.Data;
import com.example.dream_house.service.DataService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;/*** @BelongsProject:dream_house* @BelongsPackage:com.example.dream_house.controller* @Author:Uestc_Xiye* @CreateTime:2023-12-17 17:17:36*/@Api(tags = "API接口")
@RestController
@CrossOrigin(origins = "*", maxAge = 3600)
public class DataController {@Autowiredprivate DataService dataService;@ApiOperation("添加完整信息")@PostMapping("/insert")public String insert(@RequestBody Data data) {// @RequestBody注解用來綁定通過http請求中application/json類型上傳的數(shù)據(jù)return dataService.insert(data.getIp(), data.getProvince(), data.getTime(), data.getStr(), data.getLikes());}@ApiOperation("查詢id對應(yīng)的信息")@GetMapping("/findById/{id}")public Data findById(@PathVariable int id) {return dataService.findById(id);}@ApiOperation("更新信息")@PutMapping("/update")public void update(@RequestBody Data data) {dataService.update(data);}@ApiOperation("刪除指定id的信息")@DeleteMapping("/delete/{id}")public void deleteUser(@PathVariable int id) {dataService.delete(id);}}
相關(guān)注解的作用:
-
@Api:Swagger 的注解之一,用于對 API 接口進(jìn)行注釋和說明。tags 屬性是 Swagger 文檔中的一個(gè)重要屬性,可以用來將 API 接口進(jìn)行分類,方便管理和查找。
-
@RestController:Spring MVC 中的注解之一,用于標(biāo)識該類是一個(gè)基于 RESTful 風(fēng)格的 Web 服務(wù)類。
-
@CrossOrigin:Spring 中的一個(gè)注解,用于支持跨域請求。跨域請求通常指在一個(gè)域名下的頁面中使用 AJAX 技術(shù)向不同的域名或端口號的 Web 服務(wù)發(fā)送請求。
-
@ApiOperation:Swagger 的注解之一,用于對 API 接口中的具體操作進(jìn)行注釋和說明。
-
@PostMapping:Spring MVC 中的注解之一,表示該方法接收 POST 請求。
-
@RequestBody:Spring MVC 中的注解之一,表示該方法接收的請求參數(shù)為請求體中的數(shù)據(jù)。
-
@GetMapping:Spring MVC 中的注解之一,表示該方法接收 GET 請求。
-
@PathVariable:Spring MVC 中的注解之一,表示該方法接收的請求參數(shù)為路徑參數(shù)。
-
@PutMapping:Spring MVC 中的注解之一,表示該方法接收 PUT 請求。
-
@DeleteMapping:Spring MVC 中的注解之一,表示該方法接收 DELETE 請求。
這里要引入 Spring Web,不然在使用這些注解的時(shí)候報(bào)錯(cuò)無法解析。
測試
預(yù)先設(shè)置一些數(shù)據(jù)庫內(nèi)容:
在把上面的四層架構(gòu)都處理完之后,我們直接啟動(dòng)項(xiàng)目。
運(yùn)行 DreamHouseApplication,或者運(yùn)行它的 main 方法。
踩坑 1:Please refer to dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstre
切換“跳過測試”模式:
友情鏈接:解決:Please refer to dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstre
踩坑點(diǎn)2:Consider defining a bean of type ‘com.example.dream_house.mapper.DataMapper’ in your configuration.
18:10:27.233 [Thread-1] DEBUG org.springframework.boot.devtools.restart.classloader.RestartClassLoader - Created RestartClassLoader org.springframework.boot.devtools.restart.classloader.RestartClassLoader@6d1a5149. ____ _ __ _ _/\ / ___'_ __ _ _(_)_ __ __ _
( ( )___ | '_ | '| | ’ / ` |
/ )| |)| | | | | || (| | ) ) ) )
’ || .__|| ||| |, | / / / /
=========||==============|/=///_/
:: Spring Boot :: (v2.7.6)
2023-12-17 18:10:27.843 INFO 11824 --- [ restartedMain] c.e.dream_house.DreamHouseApplication : Starting DreamHouseApplication using Java 1.8.0_192 on LAPTOP-P25TKBR2 with PID 11824 (C:Users81228DocumentsProgramJava ProjectDreamHousedream_house argetclasses started by 81228 in C:Users81228DocumentsProgramJava ProjectDreamHousedream_house)
2023-12-17 18:10:27.844 INFO 11824 --- [ restartedMain] c.e.dream_house.DreamHouseApplication : No active profile set, falling back to 1 default profile: "default"
2023-12-17 18:10:27.975 INFO 11824 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2023-12-17 18:10:27.975 INFO 11824 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
2023-12-17 18:10:29.551 WARN 11824 --- [ restartedMain] o.m.s.mapper.ClassPathMapperScanner : No MyBatis mapper was found in '[com.example.dream_house]' package. Please check your configuration.
2023-12-17 18:10:30.264 INFO 11824 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8087 (http)
2023-12-17 18:10:30.265 INFO 11824 --- [ restartedMain] o.a.catalina.core.AprLifecycleListener : An older version [1.2.24] of the Apache Tomcat Native library is installed, while Tomcat recommends a minimum version of [1.2.30]
2023-12-17 18:10:30.265 INFO 11824 --- [ restartedMain] o.a.catalina.core.AprLifecycleListener : Loaded Apache Tomcat Native library [1.2.24] using APR version [1.7.0].
2023-12-17 18:10:30.265 INFO 11824 --- [ restartedMain] o.a.catalina.core.AprLifecycleListener : APR capabilities: IPv6 [true], sendfile [true], accept filters [false], random [true], UDS [false].
2023-12-17 18:10:30.265 INFO 11824 --- [ restartedMain] o.a.catalina.core.AprLifecycleListener : APR/OpenSSL configuration: useAprConnector [false], useOpenSSL [true]
2023-12-17 18:10:30.283 INFO 11824 --- [ restartedMain] o.a.catalina.core.AprLifecycleListener : OpenSSL successfully initialized [OpenSSL 1.1.1g 21 Apr 2020]
2023-12-17 18:10:30.295 INFO 11824 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-12-17 18:10:30.295 INFO 11824 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.69]
2023-12-17 18:10:30.507 INFO 11824 --- [ restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-12-17 18:10:30.507 INFO 11824 --- [ restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2531 ms
2023-12-17 18:10:30.645 WARN 11824 --- [ restartedMain] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dataController': Unsatisfied dependency expressed through field 'dataService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dataService': Unsatisfied dependency expressed through field 'dataMapper'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.dream_house.mapper.DataMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
2023-12-17 18:10:30.651 INFO 11824 --- [ restartedMain] o.apache.catalina.core.StandardService : Stopping service [Tomcat]
2023-12-17 18:10:30.679 INFO 11824 --- [ restartedMain] ConditionEvaluationReportLoggingListener : Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2023-12-17 18:10:30.751 ERROR 11824 --- [ restartedMain] o.s.b.d.LoggingFailureAnalysisReporter : ***************************
APPLICATION FAILED TO START
***************************Description:Field dataMapper in com.example.dream_house.service.DataService required a bean of type 'com.example.dream_house.mapper.DataMapper' that could not be found.The injection point has the following annotations:- @org.springframework.beans.factory.annotation.Autowired(required=true)Action:Consider defining a bean of type 'com.example.dream_house.mapper.DataMapper' in your configuration.
解決方法:在 dataService.java 的 private DataMapper dataMapper; 的上面的@Autowired 改成 @Autowired(required = false)。
友情鏈接:Consider defining a bean of type問題解決
運(yùn)行成功后,在瀏覽器中訪問 http://127.0.0.1:8087/doc.html 頁面,該頁面是 Swagger 生成的 API 文檔經(jīng)過 knife4j 美化過后的 API 文檔頁面。
點(diǎn)擊左側(cè)的 “API接口” 可以看到出現(xiàn)了四個(gè)熟悉的接口,就是我們剛剛寫的 “增刪改查” 對應(yīng)的接口,該 API 文檔的好處就是可以在線對接口進(jìn)行測試。
首先測試添加接口,依次點(diǎn)擊并填寫數(shù)據(jù)信息:
然后點(diǎn)擊發(fā)送,看到響應(yīng)內(nèi)容 succeed 說明添加成功了:
我們前往 Navicat Premium 查看數(shù)據(jù)庫內(nèi)容有沒有變化,刷新一下頁面,可以看到在最下面的數(shù)據(jù)出現(xiàn)了我們剛剛添加進(jìn)去的內(nèi)容:
其次測試查詢接口,依次點(diǎn)擊并填寫 id 信息,然后點(diǎn)擊發(fā)送。
可以看到響應(yīng)內(nèi)容成功拿到數(shù)據(jù):
然后測試更新接口,依次點(diǎn)擊并填寫信息,然后點(diǎn)擊發(fā)送:
由于沒有設(shè)置返回值,所以響應(yīng)內(nèi)容為空。
我們直接去看數(shù)據(jù)庫的內(nèi)容變化,刷新一下數(shù)據(jù)庫,可以看到該條數(shù)據(jù)已經(jīng)發(fā)生了變化:
最后測試刪除接口,點(diǎn)擊并填寫 id 信息,然后點(diǎn)擊發(fā)送:
由于沒有設(shè)置返回值,所以還是直接前往數(shù)據(jù)庫查看,刷新數(shù)據(jù)庫,發(fā)現(xiàn) id 為 4 的這條數(shù)據(jù)不見了,說明接口沒問題:
經(jīng)過測試,“增刪改查”四個(gè)接口全部都能夠正常使用。
實(shí)現(xiàn)項(xiàng)目功能接口
上面詳細(xì)介紹了編寫 “增刪改查” 四個(gè)接口,下面給出其他項(xiàng)目功能接口。
代碼
model 層之前寫的一個(gè) Data 的實(shí)體類就繼續(xù)保留,我們再增加一個(gè) getUser 類,用來作為前端從接口獲取到的信息的類。
原理與 Data 類相同,這里就不再進(jìn)行講解了,直接上代碼:
package com.example.dream_house.model;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;/*** @BelongsProject:dream_house* @BelongsPackage:com.example.dream_house.model* @Author:Uestc_Xiye* @CreateTime:2023-12-17 22:25:14*/@lombok.Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("獲取信息內(nèi)容")
public class getUser {@ApiModelProperty(value = "信息所屬ID", required = true, example = "1")private int id;@ApiModelProperty(value = "信息來源所屬省份", required = true, example = "湖北")private String province;@ApiModelProperty(value = "夢想內(nèi)容", required = true, example = "環(huán)游世界")private String str;@ApiModelProperty(value = "點(diǎn)贊數(shù)", required = true, example = "52")private int likes;}
其實(shí)就是 Data 類去掉了 ip 和 time。
mapper 層我們需要在 DataMapper 接口中添加新的內(nèi)容,新增的方法有:
- 查詢點(diǎn)贊數(shù)前 50 名的信息。
- 查詢最新的 50 條信息。
- 查詢隨機(jī)的 50 條信息。
- 更新指定 id 的點(diǎn)贊數(shù) + 1。
- 更新指定 id 的點(diǎn)贊數(shù) - 1。
方法的原理還是與之前相同,只是改變了 SQL 語句。
/*** 查詢點(diǎn)贊數(shù)前50名的信息*/@Results({@Result(property = "id", column = "id"),@Result(property = "province", column = "province"),@Result(property = "str", column = "str"),@Result(property = "likes", column = "likes")})@Select("SELECT * FROM dream ORDER BY likes DESC LIMIT 50")List<getUser> findByLikes();/*** 查詢最新的50條信息*/@Results({@Result(property = "id", column = "id"),@Result(property = "province", column = "province"),@Result(property = "str", column = "str"),@Result(property = "likes", column = "likes")})@Select("SELECT * FROM dream ORDER BY time DESC LIMIT 50")List<getUser> findByTime();/*** 查詢隨機(jī)的50條信息*/@Results({@Result(property = "id", column = "id"),@Result(property = "province", column = "province"),@Result(property = "str", column = "str"),@Result(property = "likes", column = "likes")})@Select("SELECT * FROM dream ORDER BY rand() DESC LIMIT 50")List<getUser> findByRand();/*** 更新指定id的點(diǎn)贊數(shù)+1*/@Update("UPDATE dream SET likes = likes + 1 WHERE id = #{id}")void increaseLikesById(int id);/*** 更新指定id的點(diǎn)贊數(shù)-1*/@Update("UPDATE dream SET likes = likes - 1 WHERE id = #{id}")void decreaseLikesById(int id);
service 層在這里增加的內(nèi)容比較多,因?yàn)槭菢I(yè)務(wù)邏輯層,需要完成功能設(shè)計(jì)。
首先在 service 目錄下新建 getIP、getProvince、getTime 三個(gè)類,分別用來獲取用戶 IP 地址、用戶所在省份、內(nèi)容發(fā)送時(shí)間。
getIP 類代碼如下,可以從用戶的請求頭中篩選出用戶真實(shí)的IP地址:
package com.example.dream_house.service;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** @BelongsProject:dream_house* @BelongsPackage:com.example.dream_house.service* @Author:Uestc_Xiye* @CreateTime:2023-12-18 09:12:22*/
public class getIP {public String getIp(HttpServletRequest request, HttpServletResponse response) {response.setContentType("text/html;charset=utf-8");// 設(shè)置響應(yīng)頭允許ajax跨域訪問,星號表示所有的異域請求都可以接受response.setHeader("Access-Control-Allow-Origin", "*");response.setHeader("Access-Control-Allow-Methods", "GET,POST");return getIpAddr(request);}public String getIpAddr(HttpServletRequest request) {// 獲取請求頭"x-forwarded-for"對應(yīng)的valueString ip = request.getHeader("x-forwarded-for");// 如果獲取的ip值為空if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {// 則獲取請求頭"Proxy-Client-IP"對應(yīng)的valueip = request.getHeader("Proxy-Client-IP");}// 如果獲取的ip值仍為空if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {//則獲取請求頭"WL-Proxy-Client-IP"對應(yīng)的valueip = request.getHeader("WL-Proxy-Client-IP");}// 如果以上方式獲取的ip值都為空if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {//則直接獲取ip地址ip = request.getRemoteAddr();}// 返回ip地址return ip;}
}
getProvince 類代碼如下,可以調(diào)用外部 API 來檢測出用戶 ip 地址所屬的省份:
package com.example.dream_house.service;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;/*** @BelongsProject:dream_house* @BelongsPackage:com.example.dream_house.service* @Author:Uestc_Xiye* @CreateTime:2023-12-18 09:23:34*/
public class getProvince {/*** @param ip* @return*/public String get_Province(String ip) {// 設(shè)置api的urlString url = "https://ip.useragentinfo.com/json?ip=" + ip;RestTemplate template = new RestTemplate();// 發(fā)起一個(gè)HTTP GET請求,獲取指定URL的響應(yīng)實(shí)體,String.class表示要獲取的響應(yīng)實(shí)體的類型是字符串類型ResponseEntity<String> response = template.getForEntity(url, String.class);// 將Spring類型轉(zhuǎn)換為JSON類型JSONObject json = JSON.parseObject(response.getBody());// 取出json中的數(shù)據(jù)String province = json.getString("province");return province;}
}
getTime 類代碼如下,直接調(diào)用 Java 自帶的包就行:
package com.example.dream_house.service;import java.text.SimpleDateFormat;
import java.util.Date;/*** @BelongsProject:dream_house* @BelongsPackage:com.example.dream_house.service* @Author:Uestc_Xiye* @CreateTime:2023-12-18 09:29:50*/
public class getTime {public String get_Time() {// 設(shè)置日期格式SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 獲取當(dāng)前系統(tǒng)時(shí)間String date = simpleDateFormat.format(new Date());return date;}
}
接下來修改 DataService 類的內(nèi)容,需要新增以下方法:
/*** 獲取用戶數(shù)據(jù)并調(diào)用mapper層上傳數(shù)據(jù)庫** @param request* @param response* @param str* @return*/public String Add(HttpServletRequest request, HttpServletResponse response, String str) {getIP getIP = new getIP();getProvince getProvince = new getProvince();getTime getTime = new getTime();// 獲取信息的IP地址String ip = getIP.getIp(request, response);// 獲取信息所屬省份String province = getProvince.get_Province(ip);// 獲取當(dāng)前時(shí)間String time = getTime.get_Time();// 設(shè)置當(dāng)前點(diǎn)贊數(shù)為0int currentLikes = 0;// 上傳數(shù)據(jù)dataMapper.insert(ip, province, time, str, currentLikes);return "succeed";}/*** 查詢點(diǎn)贊數(shù)排名前50的信息** @return*/public List<getUser> findByLikes() {return dataMapper.findByLikes();}/*** 查詢最新的50條信息** @return*/public List<getUser> findByTime() {return dataMapper.findByTime();}/*** 查詢隨機(jī)的50條信息** @return*/public List<getUser> findByRand() {return dataMapper.findByRand();}/*** 更新指定id對應(yīng)的點(diǎn)贊數(shù)+1** @param id* @return*/public String increaseLikesById(int id) {dataMapper.increaseLikesById(id);return "succeed";}/*** 更新指定id對應(yīng)的點(diǎn)贊數(shù)-1** @param id* @return*/public String decreaseLikesById(int id) {dataMapper.decreaseLikesById(id);return "succeed";}
controller 層需要新增以下接口:
@ApiOperation("上傳信息接口")@PostMapping("/Add/{str}")public String Add(HttpServletRequest request, HttpServletResponse response, @PathVariable String str) {return dataService.Add(request, response, str);}@ApiOperation("查詢點(diǎn)贊數(shù)前50名的信息")@GetMapping("/findByLikes")public List<getUser> findByLikes() {return dataService.findByLikes();}@ApiOperation("查詢最新的50條信息")@GetMapping("/findByTime")public List<getUser> findByTime() {return dataService.findByTime();}@ApiOperation("查詢隨機(jī)的50條信息")@GetMapping("/findByRand")public List<getUser> findByRand() {return dataService.findByRand();}@ApiOperation("更新指定id對應(yīng)的點(diǎn)贊數(shù)+1")@PutMapping("/increaseLikesById/{id}")public String increaseLikesById(@PathVariable int id) {return dataService.increaseLikesById(id);}@ApiOperation("更新指定id對應(yīng)的點(diǎn)贊數(shù)-1")@PutMapping("/decreaseLikesById/{id}")public String decreaseLikesById(@PathVariable int id) {return dataService.decreaseLikesById(id);}
測試
啟動(dòng)項(xiàng)目,訪問 API 文檔頁面 http://127.0.0.1:8087/doc.html,可以看到我們編寫的接口都在這里:
發(fā)現(xiàn)問題:上傳信息接口無效。
原因是 service 層的 get_Province 函數(shù)中的:
String url = "https://ip.useragentinfo.com/json?ip=" + ip;
我們?nèi)?https://ip.useragentinfo.com 這個(gè)網(wǎng)址看看:
查詢一下,確實(shí)能用,但是和代碼里的不同,注意這里的網(wǎng)址:
我們把代碼改一下:
String url = "https://ip.useragentinfo.com/?ip=" + ip;
還是不行,原因是這個(gè)網(wǎng)站已經(jīng)不支持帶參數(shù)查詢了(悲)。
我們只能按下面的方法修改 get_IP、getIpAddr 和 getProvince 函數(shù)。
友情鏈接:java實(shí)現(xiàn)獲取IP及歸屬地
其他測試了都沒什么大問題。
有個(gè)小問題就是,測試上傳信息接口的時(shí)候,是能上傳,但是 IP 地址是 0:0:0:0:0:0:0:0:1。
友情鏈接:手把手教你用Java獲取IP歸屬地
這里面說,在本地環(huán)境調(diào)用獲取 IP,要么是 0:0:0:0:0:0:0:1,或者是局域網(wǎng)IP。
局域網(wǎng)IP是以192.168.x.x開頭,或者是127.0.0.1的IP。
所以需要部署到外網(wǎng)服務(wù)器才能獲取到公網(wǎng)地址,部署到外網(wǎng)服務(wù)器才能成功獲取 IP 地址。
再查了一下,在本地測試時(shí),這個(gè)是無解的。
要是在局域網(wǎng)中運(yùn)行,訪問時(shí)使用本身的 IP 訪問。這時(shí)候請求會通過路由器轉(zhuǎn)發(fā),因此服務(wù)器獲取的就是本機(jī)的局域網(wǎng)內(nèi) IP,在 Java 中獲取的 IP 就是局域網(wǎng) IP,不是 localhost 或者 127.0.0.1 這種東西。
友情鏈接:java獲取IP為0:0:0:0:0:0:0:1的情況
詳細(xì)解釋:
0:0:0:0:0:0:0:1是屬于 ipv6,后來我又進(jìn)行另一臺電腦做測試,發(fā)現(xiàn)這種情況只有在服務(wù)器和客戶端都在同一臺電腦上才會出現(xiàn)(例如用 localhost 訪問的時(shí)候才會出現(xiàn)),這是hosts配置文件的問題。
友情鏈接:
- request.getRemoteAddr()獲取ip地址時(shí)得到的值是[0:0:0:0:0:0:0:1]原因和解決方法
- 查詢本地ip地址為0:0:0:0:0:0:0:1
總結(jié):
因?yàn)殡娔X優(yōu)先把 localhost 解析成了 IPv6,用 localhost 測試,返回 IPv6 地址,get_Province 能解析到省份。
用 127.0.0.1 測試,返回本機(jī) IPv4 地址,get_Province 不能解析到省份,是“本機(jī)地址”。
創(chuàng)建 Vue 前端
安裝 Node.js
Node.js 簡介:Node.js 入門 | 青訓(xùn)營筆記
官網(wǎng)下載安裝:https://nodejs.org/zh-cn/
調(diào)出終端,輸入指令 node -v,顯示版本號說明 node 安裝好了。
輸入指令 npm -v,顯示版本號,說明 npm 可以正常使用。
配置 npm 鏡像
npm 默認(rèn)的倉庫地址在國外,訪問速度較慢,我們切換成國內(nèi)的淘寶鏡像。
輸入指令安裝:npm install -g cnpm --registry=https://registry.npm.taobao.org
輸入指令 cnpm -v,顯示版本號說明 cnpm 安裝好了。
前端代碼在 Visual Studio Code 上寫,在它上面再測一下:
安裝腳手架
終端輸入指令 cnpm i -g @vue/cli
或 npm i -g @vue/cli
全局安裝。
安裝細(xì)節(jié)很長,這樣就算成功了:
創(chuàng)建并配置項(xiàng)目
在任意位置新建一個(gè)文件夾用來放置項(xiàng)目。
終端中通過 cd 指令跳轉(zhuǎn)到這個(gè)文件夾(我這里已經(jīng)到項(xiàng)目文件夾了)。
輸入指令 vue create dream_house
創(chuàng)建項(xiàng)目。
- 上下鍵:表示選擇
- 回車鍵:表示確認(rèn)
選擇 Manually select features 手動(dòng)配置。
選擇需要安裝的插件,勾選如下插件,按空格鍵選擇:
- Babel:解析 es6 轉(zhuǎn) es5 的插件
- TypeScript:TypeScript 插件
- Progressive Web App (PWA) Support:漸進(jìn)式 Web 應(yīng)用程序(PWA)支持
- Router:vue 路由插件
- Vuex:Vuex 插件
- CSS Pre-processors:css 預(yù)處理插件
- Linter/Formatter:格式化程序
- Unit Testing:單元測試
- E2E Testing:端到端(end-to-end)
回車(Enter)確認(rèn)。
版本選擇:選 2.x。
路由模式:選擇是否為 history 模式,y 表示是,n 表示使用 hash 模式,這里選擇的是 n。
- history:利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法(需要特定瀏覽器支持)
*hash: 瀏覽器 url 址欄中的 # 符號(如這個(gè) URL:http://love.byzy.love/#/SuiJi ,hash 的值為“ #/SuiJi ”),hash 不被包括在 HTTP 請求中,所以對后端完全沒有影響。因此改變 hash 不會重新加載頁面,更容易進(jìn)行打包上傳服務(wù)器。
選擇 CSS 預(yù)處理器:選第一個(gè)
選擇編碼規(guī)則:
-
ESLint with error prevention only:只配置使用 ESLint 官網(wǎng)的推薦規(guī)則
-
ESLint + Airbnb config:官網(wǎng)推薦的規(guī)則 + Airbnb 第三方的配置
-
ESLint + Standard config:使用 ESLint 官網(wǎng)推薦的規(guī)則 + Standard 第三方的配置
-
ESLint + Prettier:使用 ESLint 官網(wǎng)推薦的規(guī)則 + Prettier 第三方的配置
建議初學(xué)者(就是我)選擇第一項(xiàng),表示只有報(bào)錯(cuò)時(shí)才會驗(yàn)證。
檢測條件:
-
Lint on save:保存就檢測
-
Lint and fix on commit:fix 和 commit 時(shí)候檢查
選 Lint on save。
存放配置:
- In dedicated config files:獨(dú)立文件放置
- In package.json:放 package.json 里
選 In package.json。
最后輸入 y:
保存配置并命名:
包管理器就選 npm:
等下載完成,顯示出如下界面說明配置完成:
輸入指令 cd dream_house 進(jìn)入項(xiàng)目。
項(xiàng)目結(jié)構(gòu)
Visual Studio Code 打開項(xiàng)目文件夾,觀察左側(cè)項(xiàng)目目錄。
項(xiàng)目結(jié)構(gòu)說明如下:
- node_modules: 包含所有安裝的 npm 包。
- public:包含所有不需要經(jīng)過 Webpack 處理的靜態(tài)文件,如:index.html、favicon.ico等。
- favicon.ico
- index.html
- src:包含所有源代碼
- assets:存放靜態(tài)資源文件,如圖片、樣式表等
- logo.png
- components:存放 Vue 組件
- HelloWorld.vue
- router:存放 Vue 路由
- index.js
- store:存放 Vuex 狀態(tài)管理相關(guān)的代碼
- index.js
- views:存放頁面級別的 Vue 組件
- AboutView.vue
- HomeView.vue
- App.vue:Vue應(yīng)用程序的根組件
- main.js:Vue應(yīng)用程序的入口文件
- assets:存放靜態(tài)資源文件,如圖片、樣式表等
- .gitignore:Git 忽略文件列表,告訴 Git 哪些文件和目錄應(yīng)該被忽略,不被納入版本控制中。
- babel.config.js:Babel 配置文件,用于轉(zhuǎn)換 ES6+ 代碼到 ES5,以便它們能夠在更舊的瀏覽器中運(yùn)行。
- jsconfig.json:用于配置 Vusial Studio Code 的 JavaScript 語言服務(wù)的 JSON 配置文件。它可以用來設(shè)置JS項(xiàng)目的編譯選項(xiàng)、路徑別名、自動(dòng)引入模塊等選項(xiàng)。
- package-lock.json:記錄當(dāng)前項(xiàng)目的依賴項(xiàng)及其版本信息,確保不同計(jì)算機(jī)或者環(huán)境下的依賴項(xiàng)保持一致。
- package.json:Vue.js 項(xiàng)目的元數(shù)據(jù)文件,包含項(xiàng)目的名稱、版本、作者、依賴項(xiàng)等信息。其中,dependencies 字段記錄的是項(xiàng)目的運(yùn)行時(shí)依賴項(xiàng),而 devDependencies 字段記錄的是項(xiàng)目的開發(fā)時(shí)依賴項(xiàng)。
- README.md:項(xiàng)目的說明文檔,描述項(xiàng)目的目的、功能、使用方法等等。
- vue.config.js:Vue.js 項(xiàng)目的配置文件,包含了一些 Vue CLI 的默認(rèn)配置,可以用于自定義 Webpack 配置、開發(fā)環(huán)境的代理設(shè)置等等。使用 vue-cli-service 命令時(shí),它將自動(dòng)被加載并應(yīng)用于項(xiàng)目配置。
Vue 組件結(jié)構(gòu)
一個(gè) Vue 組件分為三個(gè)部分,分別是:template 部分、script 部分、style 部分。
- template:組件的模板部分,用來定義組件的 html 結(jié)構(gòu)。
必須在里面放置一個(gè) html 標(biāo)簽來包裹所有的代碼,例如 標(biāo)簽。 - script:組件的 JavaScript 代碼部分,用來定義組件的邏輯和屬性。
- style:組件的樣式部分,它用來定義組件的樣式。
Vue 組件調(diào)用與傳值
我們就從官方的例子中來理解 Vue 組件間的傳值與調(diào)用。由于 HelloWorld.vue 中的內(nèi)容過多,所以我進(jìn)行了刪減。
觀察下面的兩段 Vue 代碼。
HomeView.vue:
<template><div class="home"><img alt="Vue logo" src="../assets/logo.png"><HelloWorld msg="Welcome to Your Vue.js App"/></div>
</template><script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'export default {name: 'HomeView',components: {HelloWorld}
}
</script>
HelloWorld.vue:
<template><div class="hello"><h1>{{ msg }}</h1></div>
</template><script>
export default {name: 'HelloWorld',props: {msg: String}
}
</script>
可以看到 HomeView.vue 組件中 template 部分有一個(gè) HelloWorld 標(biāo)簽,這個(gè)就是我們自定義的 HelloWorld 組件,在這個(gè)標(biāo)簽中,我們將一串字符串賦值給 msg 傳遞了過去。
在 script 部分中,使用 ES6 的 import 語法引入了 “@/components/HelloWorld.vue” 文件,并將其賦值給 HelloWorld 變量。
-
export default:使用 ES6 的 export 語法導(dǎo)出一個(gè)默認(rèn)的對象,該對象包含了組件的各種屬性和方法。
-
name:定義了組件的名稱,可以在代碼中用來引用這個(gè)組件。
-
components:定義了組件所包含的子組件。在這里,我們將 HelloWorld 子組件注冊為了 HomeView 組件的一個(gè)子組件,以便在 HomeView 組件的模板中使用 HelloWorld 組件。
接下來看 HelloWorld.vue 組件中的 script 部分,我們可以看到 props 屬性,這個(gè)屬性定義了該組件的數(shù)據(jù)屬性,也就是它的輸入。在這里,我們定義了一個(gè)名為 msg 的屬性,它是一個(gè)字符串類型。該屬性可以從組件外部傳遞進(jìn)來,在組件內(nèi)部使用。
然后在 template 部分,
{{ msg }}
調(diào)用了該值。Vue 組件的生命周期
Vue組件的生命周期是指在組件實(shí)例化時(shí),從開始到結(jié)束,不同階段會自動(dòng)執(zhí)行的一些函數(shù)。Vue提供了一些鉤子函數(shù),讓我們在這些生命周期階段執(zhí)行我們的自定義邏輯。Vue組件的生命周期可以分為以下三個(gè)階段:
-
創(chuàng)建階段:包括組件實(shí)例化、數(shù)據(jù)觀測、模板編譯和掛載等過程。
具體的生命周期函數(shù)有:-
beforeCreate:在實(shí)例初始化之后,數(shù)據(jù)觀測和事件配置之前被調(diào)用,此時(shí)data和methods 等組件屬性還未初始化。
-
created:在實(shí)例創(chuàng)建完成后被立即調(diào)用,此時(shí) data 和 methods 等組件屬性已經(jīng)初始化,但是 DOM 節(jié)點(diǎn)還未掛載。
-
beforeMount:在掛載開始之前被調(diào)用,此時(shí)模板已經(jīng)編譯完成,但是還未渲染成 DOM。
-
mounted:在掛載完成后被調(diào)用,此時(shí)組件已經(jīng)掛載到 DOM 上,可以進(jìn)行 DOM 操作和異步數(shù)據(jù)請求等操作。
-
-
更新階段:更新階段包括數(shù)據(jù)更新和重新渲染等過程。
具體的生命周期函數(shù)有:-
beforeUpdate:在數(shù)據(jù)更新之前被調(diào)用,此時(shí)組件還未重新渲染。
-
updated:在數(shù)據(jù)更新之后被調(diào)用,此時(shí)組件已經(jīng)重新渲染。
-
-
銷毀階段:銷毀階段包括組件銷毀和清理等過程。
具體的生命周期函數(shù)有:-
beforeDestroy:在實(shí)例銷毀之前被調(diào)用,此時(shí)組件還未銷毀,可以進(jìn)行一些清理工作。
-
destroyed:在實(shí)例銷毀之后被調(diào)用,此時(shí)組件已經(jīng)完全銷毀,不再可用。
-
在組件的生命周期中,我們可以使用這些生命周期函數(shù)來執(zhí)行一些初始化、清理和動(dòng)態(tài)更新等操作。例如,在 created 生命周期函數(shù)中可以發(fā)起異步請求獲取數(shù)據(jù),在 beforeDestroy 生命周期函數(shù)中可以清理定時(shí)器或取消訂閱等操作。
測試 Vue 程序
我們先看看默認(rèn)的 Vue 程序如何運(yùn)行。
第一步:npm install
第二步:npm run serve
第三步:打開瀏覽器,進(jìn)入 http://localhost:8080/
友情鏈接:如何運(yùn)行vue項(xiàng)目(超詳細(xì)圖解)
需求分析
整理需求:
- 一個(gè)導(dǎo)航欄
- 三個(gè)頁面
- 頁面中顯示每個(gè)用戶發(fā)出的內(nèi)容
- 可以給內(nèi)容點(diǎn)贊
- 可以發(fā)送內(nèi)容
- 先把整體頁面的框架模型搭建起來,然后再進(jìn)行修飾
在了解了需求之后,就可以開始動(dòng)手操作了。
實(shí)現(xiàn)項(xiàng)目頁面
框架搭建
在 views 目錄下新建 LikesSortedView.vue、NewestView.vue、RandomView.vue。
在 components 目錄下新建 UserList.vue、InputText.vue。
LikesSortedView.vue、NewestView.vue、RandomView.vue 三個(gè)組件作為三個(gè)頁面,內(nèi)容包括所有的 UserList.vue 組件排列起來。
UserList.vue 組件作為單個(gè)用戶發(fā)送的內(nèi)容,上面顯示省份與點(diǎn)贊等內(nèi)容。
InputText.vue 組件作為發(fā)送內(nèi)容的組件,包括一個(gè)輸入框和一個(gè)發(fā)送按鈕。
項(xiàng)目配置
我們需要把項(xiàng)目運(yùn)行端口更改一下,不然默認(rèn)是 8080 端口。在 vue.config.js 文件中更改:
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({transpileDependencies: true,devServer: {port: 8086, // 此處修改你想要的端口號},})
然后在項(xiàng)目中我們會用到 Axios 以及 Element ,所以需要下載相關(guān)依賴并在 main.js 中引入。
-
安裝 Axios 指令:
npm install axios
。 -
安裝 Element 指令:
npm install element-ui
。
安裝好以后,修改 main.js:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'import ElementUI from 'element-ui'; // 引入element-ui
import 'element-ui/lib/theme-chalk/index.css'; // element-ui的css樣式要單獨(dú)引入import axios from 'axios'Vue.prototype.$axios = axiosVue.use(ElementUI);Vue.config.productionTip = falsenew Vue({router,store,render: h => h(App)
}).$mount('#app')
主界面設(shè)計(jì)
主頁面的設(shè)計(jì)在根組件 App.vue 中進(jìn)行,主要負(fù)責(zé)一些全局內(nèi)容的顯示。
在 template 部分為一個(gè)標(biāo)題,一個(gè)導(dǎo)航欄,一個(gè) InputText 組件,一條每頁都需要顯示的提示文字。
<template><div id="app"><h1 class="MyName">Dream House</h1><nav><router-link to="/">排行</router-link> |<router-link to="/Newest">最新</router-link> |<router-link to="/Random">隨機(jī)</router-link></nav><router-view /><InputText /><div class="newText">該列表僅顯示50條內(nèi)容!</div></div>
</template>
在 script 部分引入 InputText.vue 組件。
<script>
import InputText from '@/components/InputText.vue'export default {name: 'App',components: {InputText},
}
</script>
在 style 部分設(shè)置該組件的樣式,以及一些全局效果。
<style lang="scss">
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;
}nav {padding: 20px;font-size: 30px;font-family: pyt;text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.2);a {text-decoration: none;font-weight: bold;color: #2c3e50;&.router-link-exact-active {-webkit-tap-highlight-color: transparent; //清除藍(lán)框text-decoration: none;color: #42b983;}}
}.MyName {font-family: pyt;font-size: 60px;background-clip: text;-webkit-background-clip: text;-webkit-text-fill-color: transparent;background-image: linear-gradient(to right, #41ffa9, rgba(79, 168, 252, 0.6));margin: 20px 10px 0px 10px;text-shadow: 3px 3px 3px rgba(0, 0, 0, 0.2); //文本陰影
}
body {background: rgb(244, 189, 255);background: linear-gradient(90deg,rgb(183, 180, 255) 0%,rgb(255, 255, 255) 50%,rgb(165, 240, 255) 100%);
}// 設(shè)置全局滾動(dòng)條
body::-webkit-scrollbar {width: 5px;background-color: #f5f5f5;
}body::-webkit-scrollbar-thumb {border-radius: 10px;-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);background-color: #c4ffe4;
}.newText {height: 80px;margin: 10px auto;font-size: 13px;
}
</style>
設(shè)置路由
在 router 目錄下的 index.js 中設(shè)置路由:
import Vue from 'vue'
import VueRouter from 'vue-router'Vue.use(VueRouter)const routes = [{path: '/',name: '排行',component: () => import('../views/LikesSortedView.vue')},{path: '/Newest',name: '最新',// route level code-splitting// this generates a separate chunk (Newest.[hash].js) for this route// which is lazy-loaded when the route is visited.component: () => import(/* webpackChunkName: "about" */ '../views/NewestView.vue')},{path: '/Random',name: '隨機(jī)',component: () => import('../views/RandomView.vue')}
]const router = new VueRouter({routes
})export default router
內(nèi)容組件設(shè)計(jì)
UserList.vue 的 template 部分如下,顯示一個(gè)主要內(nèi)容,在左下角顯示省份,右下角顯示點(diǎn)贊數(shù)和一個(gè)圖片按鈕。
<template><div class="UserList"><div class="box"><div class="box1"><div class="str">{{str}}</div></div><div class="box2"><div class="province">{{province}}</div><div class="likes">{{likes}}</div><img:src="imagePath"@click="toggleImage"class="img-btn"/></div></div></div>
</template>
style 部分如下,設(shè)置對應(yīng)的標(biāo)簽顯示的位置,以及樣式等。
<!-- 添加“scoped作用域”屬性以將 CSS 限制為此組件 -->
<style scoped lang="scss">
.UserList {display: flex;justify-content: center;align-items: center;
}.box {border: 1px solid #ddd;border-radius: 10px;box-shadow: 2px 2px 4px #ddd;padding: 10px;display: flex;flex-direction: column; //將 flex 子元素沿豎直方向排列align-items: center; //將 flex 子元素在縱軸上居中對齊margin: 5px;width: 500px;transition: all 0.3s ease; //面板放大
}.box:hover {transform: scale(1.04); //面板放大
}.box1 {width: 100%;float: left;margin: 5px;
}.str {flex: 1;text-align: center;font-size: 20px;//font-family:wpyt;width: 100%;height: 25px;display: flex;justify-content: center;align-items: center;text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.2);
}.box2 {width: 100%;display: flex;
}.province {text-align: center;font-size: 14px;width: 120px;height: 20px;display: flex;
}.likes {text-align: center;font-size: 14px;width: 100px;height: 20px;margin: auto 5px auto auto;display: flex;justify-content: flex-end;align-items: center;
}.img-btn {width: 16px;height: 16px;transition: all 0.3s ease; // 設(shè)置按鈕元素的過渡效果
}
</style>
script 部分如下,獲取從上級頁面?zhèn)鱽淼臄?shù)據(jù),在 created() 鉤子函數(shù)中判斷是否給這個(gè)內(nèi)容點(diǎn)過贊,并將圖片按鈕初始化。
<script>
import axios from 'axios'export default {name: 'UserList',props: {Obj: Object},data() {return {imagePath: require("@/assets/點(diǎn)贊-no.png"),str: this.Obj.str,province: this.Obj.province,likes: this.Obj.likes};},created() {if (localStorage.getItem(`isLiked${this.Obj.id}`)) {this.imagePath = require("@/assets/點(diǎn)贊-yes.png");}},methods: {toggleImage() {// 判斷是否點(diǎn)贊過if (this.imagePath === require("@/assets/點(diǎn)贊-no.png")) {// 獲取按鈕元素const imgBtn = this.$el.querySelector('.img-btn');// 給按鈕元素應(yīng)用縮放和旋轉(zhuǎn)的樣式imgBtn.style.transform = 'scale(1.2) rotate(-10deg)';// 設(shè)置計(jì)時(shí)器,在 300 毫秒之后給按鈕元素應(yīng)用新的樣式setTimeout(() => {imgBtn.style.transform = 'scale(1) rotate(10deg)';setTimeout(() => {imgBtn.style.transform = 'scale(1) rotate(0deg)';}, 300);}, 300);this.likes++;// 將圖片替換為已點(diǎn)贊的圖片this.imagePath = require("@/assets/點(diǎn)贊-yes.png");// 將已點(diǎn)贊的狀態(tài)存入 localStoragelocalStorage.setItem(`isLiked${this.Obj.id}`, true);//向服務(wù)器發(fā)送發(fā)送put請求axios.put(`http://localhost:8087/increaseLikesById/${this.Obj.id}`).then(res => {console.log(res.data);}).catch(error => {console.log("點(diǎn)贊出錯(cuò)!")console.error(error);});} else {this.likes--;// 否則將圖片替換為未點(diǎn)贊的圖片this.imagePath = require("@/assets/點(diǎn)贊-no.png");// 并且刪除已點(diǎn)贊的狀態(tài)localStorage.removeItem(`isLiked${this.Obj.id}`);// 向服務(wù)器發(fā)送發(fā)送put請求axios.put(`http://localhost:8087/decreaseLikesById/${this.Obj.id}`).then(res => {console.log(res.data);}).catch(error => {console.log("取消點(diǎn)贊出錯(cuò)!")console.error(error);});}}}}
</script>
按鈕綁定 toggleImage() 函數(shù),通過點(diǎn)擊按鈕觸發(fā)此函數(shù),觸發(fā)圖片按鈕的動(dòng)作,以及更換圖片,并且向服務(wù)器發(fā)出 put 請求,更新數(shù)據(jù)庫中的數(shù)據(jù),更新頁面中顯示的數(shù)據(jù)。
assets 文件夾下要放圖片:
友情鏈接:阿里巴巴矢量圖標(biāo)庫
發(fā)送組件設(shè)計(jì)
InputText.vue 的 template 部分如下,只有一個(gè)輸入框以及一個(gè)圖片按鈕。
<template><div class="InputText"><inputtype="text"maxlength="15"v-model="inputValue"placeholder="發(fā)送你的夢想!"/><imgsrc="../assets/send.png"class="submit-btn"/></div>
</template>
style 部分如下,依然是設(shè)置整個(gè)發(fā)送組件的樣式。
<style scoped lang="scss">
.InputText {position: fixed;bottom: 10px;left: 0;right: 0;display: flex;justify-content: space-between;padding: 10px;background-color: transparent;width: 300px;margin: 0 auto;//設(shè)置 z-index 值為 999,顯示為最頂部z-index: 999;
}input[type="text"] {flex: 1;height: 40px;padding: 0 10px;background-color: rgba(255, 255, 255, 0.7);border: 1px solid transparent;border-radius: 20px;box-shadow: 0 0 10px #42b983;transition: all 0.3s ease-in-out;
}input[type="text"]:hover {transform: scale(1.05);box-shadow: 0 0 15px rgba(24, 144, 255, 0.8);
}.submit-btn {height: 40px;padding: 2px 0px 0px 10px;background-color: transparent;border: none;cursor: pointer;transform: scale(1);transition: all 0.3s ease-in-out;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}.submit-btn:hover {transform: scale(1.2);
}
</style>
script 部分如下,通過點(diǎn)擊按鈕觸發(fā) submit() 函數(shù),播放按鈕的動(dòng)畫,以及獲取輸入框的內(nèi)容,發(fā)送到服務(wù)器上。
<script>
import axios from 'axios'export default {name: 'InputText',// 定義數(shù)據(jù)data() {return {isAnimating: false, // 用于標(biāo)識動(dòng)畫是否正在播放inputValue: ''};},methods: {// 定義提交方法submit() {// 如果動(dòng)畫正在播放,直接返回if (this.isAnimating) return;//向服務(wù)器發(fā)送發(fā)送post請求axios.post(`http://localhost:8087/Add/${this.inputValue}`).then(res => {console.log(res.data);this.inputValue = '';this.$message({message: '發(fā)送成功!',type: 'success',duration: 2000 // 自動(dòng)關(guān)閉延遲時(shí)間,單位毫秒});}).catch(error => {this.$message({message: '發(fā)送失敗!',type: 'error',duration: 2000 // 自動(dòng)關(guān)閉延遲時(shí)間,單位毫秒});console.log("發(fā)送信息出錯(cuò)!")console.error(error);});this.isAnimating = true; // 標(biāo)記動(dòng)畫正在播放// 獲取提交按鈕元素const btn = this.$el.querySelector(".submit-btn");// 記錄提交按鈕的原始 transform 值const originalTransform = btn.style.transform;// 設(shè)置提交按鈕的 transform 值,使其向右上方移動(dòng)btn.style.transform = "translate(100%, -100%)";// 設(shè)置動(dòng)畫播放完成后的回調(diào)函數(shù)setTimeout(() => {// 將提交按鈕的 transform 值設(shè)置為原始值btn.style.transform = originalTransform;// 標(biāo)記動(dòng)畫已經(jīng)播放完畢this.isAnimating = false;}, 500);}},mounted() {// 在組件掛載完成后,給提交按鈕元素綁定 click 事件this.$el.querySelector(".submit-btn").addEventListener("click", this.submit);}
};
</script>
assets 文件夾下要放圖片:
分頁面設(shè)計(jì)
三個(gè)分頁面的作用是向數(shù)據(jù)庫發(fā)出請求,獲取到內(nèi)容數(shù)據(jù),再調(diào)用內(nèi)容組件將各個(gè)內(nèi)容排列顯示在頁面上。
排行頁面 LikesSortedView.vue
template 部分如下,在該部分中只使用了一個(gè)自定義組件 UserList ,使用 Vue 的模板語法動(dòng)態(tài)的生成一個(gè)組件列表。
<template><div class="PaiHang"><UserListv-for="(item, index) in Obj":key="index":Obj="item"/></div>
</template>
解釋:
-
v-for="(item, index) in Obj"指令表示對 Obj 對象進(jìn)行遍歷,生成對應(yīng)數(shù)量的 組件。
-
:key="index"屬性用于標(biāo)識每個(gè)組件的唯一性,使 Vue 能夠更高效地管理組件的狀態(tài)。
-
:Obj="item"屬性將當(dāng)前元素的值 item 傳遞給 組件,作為組件的參數(shù)之一。
script 部分如下,在該頁面被初始化的的時(shí)候會調(diào)用 getList() 方法從服務(wù)器拿到數(shù)據(jù),然后將 json 數(shù)據(jù)傳入對應(yīng)的 UserList 組件中。
<script>
// @ 是 /src 的別名
import UserList from '@/components/UserList.vue'
import axios from 'axios'export default {name: 'LikesSortedView',components: {UserList},data() {return {Obj: []}},// computed會緩存結(jié)果,methods每次都會重新計(jì)算methods: {getList() {let list = [];let newObjects = {};axios.get('http://localhost:8087/findByLikes').then(res => {list = res.data;for (let i = 0; i < list.length; i++) {newObjects[i] = list[i];}console.log(newObjects);this.Obj = newObjects;}).catch(error => {this.$message({message: '獲取頁面內(nèi)容失敗!',type: 'error',duration: 2000});console.log("獲取排行出錯(cuò)!")console.error(error);});}},created() {this.getList();}}
</script>
最新頁面 NewestView.vue
最新頁面基本上與排行頁面相同,唯一不同的地方就是請求的參數(shù)不同,所以只需要把請求的 url 改一下就行了,改成 API 定義的對應(yīng)的接口。
url:http://(接口IP地址):8087/findByTime
然后就是本組件的名字需要改一下。
<template><div class="Newest"><UserListv-for="(item, index) in Obj":key="index":Obj="item"/></div></template><script>// @ 是 /src 的別名import UserList from '@/components/UserList.vue'import axios from 'axios'export default {name: 'NewestView',components: {UserList},data() {return {Obj: []}},// computed會緩存結(jié)果,methods每次都會重新計(jì)算methods: {getList() {let list = [];let newObjects = {};axios.get('http://localhost:8087/findByTime').then(res => {list = res.data;for (let i = 0; i < list.length; i++) {newObjects[i] = list[i];}console.log(newObjects);this.Obj = newObjects;}).catch(error => {this.$message({message: '獲取頁面內(nèi)容失敗!',type: 'error',duration: 2000});console.log("獲取排行出錯(cuò)!")console.error(error);});}},created() {this.getList();}}</script>
隨機(jī)頁面 RandomView.vue
同理,改一下請求的 url 就行。
url:http://(接口IP地址):8087/findByRand
<template><div class="Random"><UserListv-for="(item, index) in Obj":key="index":Obj="item"/></div></template><script>// @ 是 /src 的別名import UserList from '@/components/UserList.vue'import axios from 'axios'export default {name: 'RandomView',components: {UserList},data() {return {Obj: []}},// computed會緩存結(jié)果,methods每次都會重新計(jì)算methods: {getList() {let list = [];let newObjects = {};axios.get('http://localhost:8087/findByRand').then(res => {list = res.data;for (let i = 0; i < list.length; i++) {newObjects[i] = list[i];}console.log(newObjects);this.Obj = newObjects;}).catch(error => {this.$message({message: '獲取頁面內(nèi)容失敗!',type: 'error',duration: 2000});console.log("獲取排行出錯(cuò)!")console.error(error);});}},created() {this.getList();}}</script>
項(xiàng)目啟動(dòng)
在終端中輸入指令:npm run serve
。
可以看到如下界面,說明項(xiàng)目成功運(yùn)行:
根據(jù)提示訪問本地地址 http://localhost:8086/。
發(fā)現(xiàn)所有功能都正常使用,頁面正常排布。
經(jīng)測試,點(diǎn)贊按鈕和發(fā)送按鈕動(dòng)畫正常播放,頁面數(shù)據(jù)正常更新,夢想內(nèi)容正常發(fā)布,各功能使用正常。
至此,“夢想屋” 小項(xiàng)目成功完成。
源碼下載
GitHub:https://github.com//DreamHouse
CSDN:DreamHouse.zip