項目練習01 1.項目介紹 這是一個簡單的項目練習,用於掌握新學習的SpringBoot技術。 項目操作界面 ● 技術棧 Vue3+ElementPlus+Axios+MyBatisPlus+SpringBoot 前後端分離 前後端分離開發,前端主體框架 Vue3 + 後端基礎框架 SpringBo ...
項目練習01
1.項目介紹
這是一個簡單的項目練習,用於掌握新學習的SpringBoot技術。
- 項目操作界面
![image-20230326161555846](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20230326161555846.png)
● 技術棧
Vue3+ElementPlus+Axios+MyBatisPlus+SpringBoot 前後端分離
前後端分離開發,前端主體框架 Vue3 + 後端基礎框架 SpringBoot
- 前端技術棧:Vue3+Axios+ElementPlus
- 後端技術棧:Spring Boot + MyBatisPlus
- 資料庫-MySQL
- 項目的依賴管理-Maven
- 分頁-MyBatis Plus 的分頁插件
2.功能01-搭建Vue前端工程
2.1代碼實現
以下過程,前面在講解SSM_VUE項目時已經安裝過了,包括整個vue項目的創建過程、項目結構,具體看SSM整合day02的功能01實現的筆記
-
先下載node.js LTS 並安裝node.js的npm,用於管理前端項目包依賴
-
創建Vue項目(需要聯網)
1)創建項目,指令
vue create <項目名>
2)選擇 Manually select features
3)用空格鍵選擇Bebel、Router、Vuex
4)選擇3.x
5)Use history mode for router? 輸入Y,回車
6)Where do you prefer placing config for Babel, ESLint, etc.? 選擇In package. json
7)Save this as a preset for future projects? (y/N) 是否要保存當前的設置,根據你意思隨意選擇,如果選擇了的話會有 Save preset as: 讓你命名當前保存的設置
8)最後回車
9)創建完畢的顯示結果如下
-
使用idea打開剛創建的vue項目,並配置項目啟動
1)點擊配置Configuration,配置npm方式啟動項目
2)選擇serve,其他保持預設,保存
3)點擊運行,即可在提示的地址訪問項目
-
停止項目,安裝element-plus插件,在項目中運行指令
npm install element-plus --save
-
在vue.config.js中修改項目的埠,防止埠占用
module.exports = {
devServer: {
port: 10000//啟動埠
}
}
3.功能02-創建項目基礎界面
這個功能步驟在ssm整合框架day01-功能02中也有詳細描述,這裡不再贅述
3.1需求分析
頁面原型:
![image-20230326161555846](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20230326161555846.png)
3.2思路分析
使用vue3+ElementPlus完成
3.3代碼實現
(1)修改Home.vue,引入表單、搜索框、按鈕組件
<template>
<div>
<div style="margin: 10px 0">
<el-button type="primary">新增</el-button>
<el-button>其它</el-button>
</div>
<!--搜索-->
<div style="margin: 10px 0">
<el-input v-model="search" placeholder=" 請 輸 入 關 鍵 字 " style="width:30%"></el-input>
<el-button style="margin-left: 10px" type="primary">查詢</el-button>
</div>
<el-table :data="tableData" stripe style="width: 100%">
<el-table-column sortable prop="date" label="日期"></el-table-column>
<el-table-column prop="name" label="姓名"></el-table-column>
<el-table-column prop="address" label="地址"></el-table-column>
<el-table-column fixed="right" label="操作" width="100">
<template #default="scope">
<el-button @click="handleEdit(scope.row)" type="text">編輯</el-button>
<el-button type="text">刪除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
name: 'HomeView',
components: {
},
data() {
return {
search : '',
tableData: [{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀區金沙江路 1518 弄',
},
{
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀區金沙江路 1517 弄',
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀區金沙江路 1519 弄',
}
]
}
},
methods: {
handleEdit() {
}
}
}
</script>
(2)刪除HelloWorld.vue組件
(3)創建components/Header.vue
(4)創建全局的global.css(src/assets/css/global.css),以後有全局樣式可以放在這裡
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
(5)修改src/main.js,引入global.css,同時引入ElementPlus,順便國際化
import {createApp} from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
//引入css
import '@/assets/css/global.css'
//引入ElementPlus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
//國際化
import zhCn from 'element-plus/es/locale/lang/zh-cn'
createApp(App).use(store).use(router).use(ElementPlus, {locale: zhCn}).mount('#app')
(6)Header.vue,引入ElementPlus組件的下拉框
<template>
<div style="height: 50px; line-height: 50px; border-bottom: 1px solid #ccc; display:flex">
<div style="width: 200px; padding-left: 30px;
font-weight: bold; color: dodgerblue">後臺管理
</div>
<div style="flex: 1"></div>
<div style="width: 100px">
<el-dropdown>
<span class="el-dropdown-link" style="padding: 18px">
tom<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>個人信息</el-dropdown-item>
<el-dropdown-item>退出登錄</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<script>
export default {
name: "Header"
}
</script>
<style scoped>
</style>
![image-20230326180733797](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20230326180733797.png)
(7)創建側邊欄組件Aside.vue,引入導航菜單組件
<template>
<div>
<!--說明-先去掉這兩個方法, 否則會報錯-->
<!--@open="handleOpen"-->
<!--@close="handleClose"-->
<el-menu
style="width: 200px"
default-active="2"
class="el-menu-vertical-demo">
<el-sub-menu index="1-4">
<template #title>選項 4</template>
<el-menu-item index="1-4-1">選項 1</el-menu-item>
</el-sub-menu>
<el-menu-item index="2">
<i class="el-icon-menu"></i>
<template #title>導航二</template>
</el-menu-item>
<el-menu-item index="3" disabled>
<i class="el-icon-document"></i>
<template #title>導航三</template>
</el-menu-item>
<el-menu-item index="4">
<i class="el-icon-setting"></i>
<template #title>導航四</template>
</el-menu-item>
</el-menu>
</div>
</template>
<script>
export default {
name: "Aside"
}
</script>
<style scoped>
</style>
(8)在App.vue將頁面分成三部分
<template>
<div>
<!--頭部-->
<Header/>
<!--主體-->
<div style="display: flex">
<!--側邊欄-->
<Aside/>
<!--內容區域,表格, 這個部分是從 HomeView.vue 組件來的-->
<router-view style="flex: 1"/>
</div>
</div>
</template>
<style>
</style>
<script>
import Header from "@/components/Header";
import Aside from "@/components/Aside";
export default {
name: "Layout",
components: {
Header,
Aside
}
}
</script>
(9)項目的基本頁面如下:
![image-20230327202052299](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20230327202052299.png)
4.功能03-創建SpringBoot後端項目
4.1需求分析
項目前後端分離情況如下:分成兩個子項目(前端和後端),現在來完成後端的子項目創建。
4.2代碼實現
(1)創建maven項目,引入需要的依賴
<!--導入SpringBoot父工程-->
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.5.3</version>
</parent>
<dependencies>
<!--web starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.3</version>
</dependency>
<!--mysql驅動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<!--配置處理器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--test starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--druid依賴-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
<!--mybatis-plus starter-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
</dependencies>
(2)application.yml中配置port和配置DB連接信息
server:
port: 9090
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot_furn?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
(3)主程式
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
測試啟動,運行成功:
![image-20230327203807752](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20230327203807752.png)
(4)配置數據源
package com.li.furn.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
/**
* @author 李
* @version 1.0
*/
@Configuration
public class DruidInitConfig {
@ConfigurationProperties("spring.datasource")
@Bean
public DataSource getDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
}
5.功能04-添加家居信息
5.1需求分析
在前端點擊添加家居,彈出一個對話框,可以讓我們填寫家居的數據,填完之後點擊確定,可以將數據發送到後端,然後保存到資料庫表中。
5.2思路分析
-
完成後臺代碼從mapper->service->controller,並第每層代碼進行測試,到controller這一層,使用postman發送http請求完成測試。
-
完成前臺代碼,使用axios發送json數據給後臺,實現添加家居信息
5.3代碼實現
5.3.1創建資料庫和表
-- 創建資料庫
DROP DATABASE IF EXISTS springboot_furn;
CREATE DATABASE springboot_furn;
USE springboot_furn;
-- 創建表
CREATE TABLE furn(
`id` INT(11) PRIMARY KEY AUTO_INCREMENT,#id
`name` VARCHAR(64) NOT NULL,#家居名
`maker` VARCHAR(64) NOT NULL,#廠商
`price` DECIMAL(11,2) NOT NULL,#價格
`sales` INT(11) NOT NULL,#銷量
`stock` INT(11) NOT NULL#庫存
)CHARSET=utf8;
![image-20230327204908681](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20230327204908681.png)
5.3.2工具類
(2)創建 com/li/furn/util/Result.java,該工具類用於返回結果(json 格式)
package com.li.furn.util;
/**
* @author 李
* @version 1.0
*/
public class Result<T> {
private String code;//狀態碼 200-success 400-fail
private String msg;//狀態說明
private T data;//返回的數據,使用泛型
public Result() {
}
public Result(T data) {
this.data = data;
}
//返回需要的result對象,表示成功
public static Result success() {
Result result = new Result<>();
result.setCode("200");
result.setMsg("success");
return result;
}
//返回成功的result對象,表示成功,同時攜帶數據
//如果需要在static方法中使用泛型,需要在static關鍵字後添加<T>
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>(data);
result.setCode("200");
result.setMsg("success");
return result;
}
//返回需要的result對象-表示失敗
//因為失敗的原因有很多中,因此直接將其作為參數傳進來
public static Result error(String code, String msg) {
Result result = new Result<>();
result.setCode(code);
result.setMsg(msg);
return result;
}
//返回成功的result對象,表示失敗,同時攜帶數據
public static <T> Result<T> error(String code, String msg, T data) {
Result<T> result = new Result<>(data);
result.setCode(code);
result.setMsg(msg);
return result;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
5.3.3bean層
創建furn表映射的bean--Furn.java
package com.li.furn.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author 李
* @version 1.0
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Furn {
private Integer id;
private String name;
private String maker;
private double price;
private Integer sales;
private Integer stock;
}
5.3.4mapper層
創建funMapper.java介面
package com.li.furn.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.li.furn.bean.Furn;
import org.apache.ibatis.annotations.Mapper;
/**
* @author 李
* @version 1.0
* 如果是MyBatisPlus,FurnMapper介面可以通過mp提供的BaseMapper介面來擴展功能
*/
//@Mapper 這裡如果沒有添加mapper註解,可以在主程式中指定掃描
public interface FurnMapper extends BaseMapper<Furn> {
}
如果使用@MapperScan(basePackages = "xxx") ,需要指定到確切的包
測試FurnMapper介面
package com.li.furn;
import com.li.furn.bean.Furn;
import com.li.furn.mapper.FurnMapper;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
/**
* @author 李
* @version 1.0
*/
//註意,測試時,如果測試的類和對應源碼中的類的包名不同,需要手動指定
@SpringBootTest
public class FurnMapperTest {
//裝配FurnMapper對象(實際上是該介面的代理對象)
@Resource
private FurnMapper furnMapper;
@Test
public void furnMapperTest() {
Furn furn = furnMapper.selectById(1);
System.out.println("furn=" + furn);
}
}
測試結果:
![image-20230327214046416](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20230327214046416.png)
5.3.5service層
FurnService介面:
package com.li.furn.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.li.furn.bean.Furn;
/**
* @author 李
* @version 1.0
*/
public interface FurnService extends IService<Furn> {
}
FurnServiceImpl實現類:
package com.li.furn.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.li.furn.bean.Furn;
import com.li.furn.mapper.FurnMapper;
import com.li.furn.service.FurnService;
import org.springframework.stereotype.Service;
/**
* @author 李
* @version 1.0
*/
@Service
public class FurnServiceImpl
extends ServiceImpl<FurnMapper, Furn>
implements FurnService {
}
service層測試:測試成功
package com.li.furn;
...
@SpringBootTest
public class ApplicationTest {
@Resource
private FurnService furnService;
@Test
public void furnServiceTest() {
List<Furn> furns = furnService.list();
for (Furn furn : furns) {
System.out.println("furn=" + furn);
}
}
}
![image-20230328182406797](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20230328182406797.png)
5.3.6controller層
註意:如果是以表單形式提交數據,則不需要在參數前添加@RequestBody註解;如果使用了@RequestBody註解,要註意測試時,向後端發送的數據是json格式(Content-Type)。
package com.li.furn.controller;
import com.li.furn.bean.Furn;
import com.li.furn.service.FurnService;
import com.li.furn.util.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @author 李
* @version 1.0
* 因為當前項目為前後端分離,在預設情況下,前端發出請求
* 後端返回json數據,為了方便,我們就在類上使用@RestController
*/
@RestController//ResponseBody+Controller
@Slf4j
public class FurnController {
@Resource
private FurnService furnService;
/**
* 完成添加添加家居信息的功能
*
* 1.因為前端發送的數據通常也是使用json格式的,
* 因此使用 @RequestBody 將前端提交的 json數據,封裝成 Javabean 對象
* 2.如果前端是以表單形式提交的,則不能使用 @RequestBody
*
* @param furn
* @return
*/
@PostMapping("/save")
public Result save(@RequestBody Furn furn) {
log.info("furn={}", furn);
furnService.save(furn);
return Result.success();//返回成功數據
}
}
postman進行測試:(註意Headers中的Content-Type屬性要指定為"application/json",否則會出錯)
![image-20230328184442625](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20230328184442625.png)
測試結果:
![image-20230328184510231](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20230328184510231.png)
資料庫顯示插入成功:
![image-20230328184923135](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20230328184923135.png)
5.3.7解決id自增長問題
furn表的id欄位被設計為自增長,但是當實際插入數據時,如果沒有給定id的值,底層執行的時候將會報錯:
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: Could not set property 'id' of 'class com.li.furn.bean.Furn' with value '1640682391397732353' Cause: java.lang.IllegalArgumentException: argument type mismatch
這是因為底層沒有獲取到自增長欄位的值。我們可以使用@TableId來解決,該註解可以標識表的主鍵,並且如果指定的是自增主鍵,會將自增主鍵值返回到實體類中。
Mybatis中需要使用 useGeneratedKeys,keyProperty,keyColumn 設置自增主鍵值的回返,在實體類對象中獲取即可。在MybatisPlus中在進行數據新增時,在新增成功後,會自動的將自增的主鍵值返回到實體類對象中,前提是需要在實體類中使用@TableId表明主鍵欄位,並且為自增類型。
![image-20230328200053910](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20230328200053910.png)
5.3.8前端代碼
(1)在前端項目中安裝axios,用於發送ajax請求給後臺:npm i axios -S
![image-20230328202927959](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20230328202927959.png)
(2)創建工具文件src/utils/request.js,用於創建axios request對象
//引入axios
// 如果在啟動前端項目,提示找不到axios,把游標放在import axios from 'axios' 的 'axios' 上
// 會有一個修複提示,導入 axios,點擊導入即可正常使用
import axios from "axios";
//通過axios創建對象-request對象,用於發送請求到後端
const request = axios.create({
timeout: 5000
})
//對request攔截器的處理
//1.可以對請求做統一的處理
//2.比如統一地加入token,Content-Type等
request.interceptors.request.use(
config => {
config.headers['Content-Type'] = 'application/json;charset=uft-8';
return config;
},
error => {
return Promise.reject(error)
})
//導出request對象
export default request;
(3)在Home.vue中添加表單,添加添加按鈕,可以出現添加家居的對話框:代碼略
![image-20230328204544786](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20230328204544786.png)
(4)解決跨域問題
瀏覽器從一個功能變數名稱的網頁去請求另一個功能變數名稱的資源時,功能變數名稱、埠、協議任一不同,都是跨域。在前後端分離的模式下,前後端的功能變數名稱是不一致的,此時就會發生跨域訪問問題。在請求的過程中我們要想回去數據一般都post/get請求,所以跨域問題出現。
![image-20230328210524186](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20230328210524186.png)
解決跨域問題的方案很多,這裡在vue.config.js文件中修改跨域配置來解決:
![image-20230328213339347](https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20230328213339347.png)
const {defineConfig} = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true
})
module.exports = {
devServer: {
port: 10000,//啟動埠
proxy: {//設置代理
'api/': {//設置攔截器 攔截器格式(斜杠+攔截器名字)
target: 'http://localhost:9090',//目標地址(後端地址)
changeOrigin: true,//是否設置同源,實現跨域
pathRewrite: {//路徑重寫
'/api': ''//選擇忽略攔截器裡面的單詞
}
}
}
}
}
註意,設置之後需要重新啟動前端項目。
註意兩點問題:
- 一定要確定 request.post("/api/save") 替換後是項目後臺服務對應提供的API介面url,否則報404錯誤
- 當執行跨域請求時,如果瀏覽器仍然提示
http://localhost:9090/api/xxx
,