day01-项目介绍和功能实现
项目练习01
1.项目介绍
这是一个简单的项目练习,用于掌握新学习的SpringBoot技术。
- 项目操作界面

● 技术栈
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需求分析
页面原型:

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>

(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)项目的基本页面如下:

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); } }
测试启动,运行成功:

(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;

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); } }
测试结果:

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); } } }

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",否则会出错)

测试结果:

数据库显示插入成功:

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表明主键字段,并且为自增类型。

5.3.8前端代码
(1)在前端项目中安装axios,用于发送ajax请求给后台:npm i axios -S

(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中添加表单,添加添加按钮,可以出现添加家居的对话框:代码略

(4)解决跨域问题
浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域。在前后端分离的模式下,前后端的域名是不一致的,此时就会发生跨域访问问题。在请求的过程中我们要想回去数据一般都post/get请求,所以跨域问题出现。

解决跨域问题的方案很多,这里在vue.config.js文件中修改跨域配置来解决:

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
,
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战