-
Springboot Vue Login(从零开始实现Springboot+Vue登录)
2019-05-31 16:19:34最近学习使用Vue实现前端后端分离,在Github上有一个很好的开源项目:mall,正所谓百看不如一练,自己动手实现了一个Springboot+Vue的登录操作,在此记录一下踩过的坑。 文章最后补充两端的GitHub代码,之所以放在...一、简述
最近学习使用Vue实现前端后端分离,在Github上有一个很好的开源项目:mall,正所谓百看不如一练,自己动手实现了一个Springboot+Vue的登录操作,在此记录一下踩过的坑。
文章最后补充两端的GitHub代码,之所以放在最后,是因为文章写的很细致了,动手操作一下会更有帮忙,如果有很大出入可以比对原码,找找问题。
二、开发工具
VSCode
IDEA
Vue 的安装就不说了,有很多文章,但是Springboot+Vue整合的完整文章相对较少,所以我主要记录一下这两端整合时的内容。
(Vue安装后就会有 npm 或 cnpm,相应的介绍也不说了,Vue官网可查看)
-------------------------------Vue 开发-------------------------------
一、打开 cmd 创建 Vue 项目,并添加 Vue 依赖的框架:
1.创建 Vue 项目(进入自己想创建的文件夹位置,我放在 D:\VSCodeWorkSpace),创建语句 vue create vue-spring-login-summed,方向键选择创建方式,我选择的默认
2.进入到创建的 Vue 项目目录,添加依赖框架:
cd vue-spring-login-summed (进入到项目根目录) vue add element (添加 element,一个 element 风格的 UI 框架) npm install axios (安装 axios,用于网络请求) npm install vuex --save(安装 Vuex,用于管理状态) npm install vue-router (安装 路由,用于实现两个 Vue 页面的跳转)
以上命令截图如下:
1)添加 Element
2)添加 axios
3)添加 Vuex
4)添加 路由
到此相关依赖的架包添加完毕,输入 code . 打开 VSCode
二、添加目录结构
在 VSCode 下看到 Vue 整体项目结构如下
现在需要创建相应功能的目录结构,进行分层开发,需要在 src 目录下创建下面几个目录
api (网络请求接口包) router (路由配置包) store (Vuex 状态管理包) utils (工具包) views (vue 视图包,存放所有 vue 代码,可根据功能模块进行相应分包)
创建后的目录结构如下
三、运行项目
现在可以运行项目了,在 VSCode 菜单栏依次选择:终端 —— 运行任务...
这里使用的是 serve 模式,即开发模式运行的项目
在浏览器输入:http://localhost:8080/
这是 Vue 默认的页面,代表项目创建成功了,在进行代码开发前,先贴上项目整体结构,防止不知道在哪创建
四、View 层代码编写
编写三个 vue 文件:login.vue(登录页面)、success.vue(登录成功页面)、error.vue(登录失败页面)
1.login.vue
代码如下(比较懒,直接从 mall 扒下来的代码,去掉了一些功能)
<template> <div> <el-card class="login-form-layout"> <el-form autocomplete="on" :model="loginForm" ref="loginForm" label-position="left" > <div style="text-align: center"> <svg-icon icon-class="login-mall" style="width: 56px;height: 56px;color: #409EFF"></svg-icon> </div> <h2 class="login-title color-main">mall-admin-web</h2> <el-form-item prop="username"> <el-input name="username" type="text" v-model="loginForm.username" autocomplete="on" placeholder="请输入用户名" > <span slot="prefix"> <svg-icon icon-class="user" class="color-main"></svg-icon> </span> </el-input> </el-form-item> <el-form-item prop="password"> <el-input name="password" :type="pwdType" @keyup.enter.native="handleLogin" v-model="loginForm.password" autocomplete="on" placeholder="请输入密码" > <span slot="prefix"> <svg-icon icon-class="password" class="color-main"></svg-icon> </span> <span slot="suffix" @click="showPwd"> <svg-icon icon-class="eye" class="color-main"></svg-icon> </span> </el-input> </el-form-item> <el-form-item style="margin-bottom: 60px"> <el-button style="width: 100%" type="primary" :loading="loading" @click.native.prevent="handleLogin" >登录</el-button> </el-form-item> </el-form> </el-card> </div> </template> <script> export default { name: "login", data() { return { loginForm: { username: "admin", password: "123456" }, loading: false, pwdType: "password", }; }, methods: { showPwd() { if (this.pwdType === "password") { this.pwdType = ""; } else { this.pwdType = "password"; } }, handleLogin() { this.$refs.loginForm.validate(valid => { if (valid) { this.loading = true; this.$store .dispatch("Login", this.loginForm) .then(response => { this.loading = false; let code = response.data.code; if (code == 200) { this.$router.push({ path: "/success", query: { data: response.data.data } }); } else { this.$router.push({ path: "/error", query: { message: response.data.message } }); } }) .catch(() => { this.loading = false; }); } else { // eslint-disable-next-line no-console console.log("参数验证不合法!"); return false; } }); } } }; </script> <style scoped> .login-form-layout { position: absolute; left: 0; right: 0; width: 360px; margin: 140px auto; border-top: 10px solid #409eff; } .login-title { text-align: center; } .login-center-layout { background: #409eff; width: auto; height: auto; max-width: 100%; max-height: 100%; margin-top: 200px; } </style>
2.success.vue
<template> <div> <h1>Welcome!{{msg}}</h1> </div> </template> <script> export default { data() { return { msg: this.$route.query.data }; }, // data() { //这种方式也可以 // return { // msg: null // }; // }, // created() { // this.msg = this.$route.query.data; // } } </script>
3.error.vue
<template> <div> <h1>登录错误:{{msg}}</h1> </div> </template> <script> export default { // data() { // return { // msg: this.$route.query.data // }; // }, //使用这种方式也可以显示 msg data() { return { msg: null }; }, created() { this.msg = this.$route.query.message; } }; </script>
五、路由
页面写好了,我们需要依次显示这三个页面,这里我们统一使用路由来管理显示页面,路由的官方文档见:vue 路由
本着先实践,后理解的码农学习方式。我们先使用路由显示三个页面后,再去理解Vue路由这个功能点。
1.创建路由配置文件
在刚才建立的 router 文件夹下创建一个 index.js 文件,内容如下
import Vue from 'vue' //引入 Vue import VueRouter from 'vue-router' //引入 Vue 路由 Vue.use(VueRouter); //安装插件 export const constantRouterMap = [ //配置默认的路径,默认显示登录页 { path: '/', component: () => import('@/views/login')}, //配置登录成功页面,使用时需要使用 path 路径来实现跳转 { path: '/success', component: () => import('@/views/success')}, //配置登录失败页面,使用时需要使用 path 路径来实现跳转 { path: '/error', component: () => import('@/views/error'), hidden: true } ] export default new VueRouter({ // mode: 'history', //后端支持可开 scrollBehavior: () => ({ y: 0 }), routes: constantRouterMap //指定路由列表 })
2.将路由添加到程序入口
路由配置文件写好,我们需要把他引入到 main.js 中,在项目的 src 目录根节点下,找到 main.js,添加内容如下:
import Vue from 'vue' import App from './App.vue' import './plugins/element.js' import router from './router' //引入路由配置 Vue.config.productionTip = false new Vue({ render: h => h(App), router, //使用路由配置 }).$mount('#app')
3.配置路由的出入口
现在路由已经完全引入到项目了,但是路由还需要一个出入口,这个出入口用来告诉路由将路由的内容显示在这里。上面 main.js 配置的第一个 vue 显示页面为 App.vue ,因此我们修改 App.vue 内容如下
<template> <div id="app"> <!-- 路由的出入口,路由的内容将被显示在这里 --> <router-view/> </div> </template> <script> export default { name: 'App' } </script>
<router-view/> 就是显示路由的出入口。
现在保存 App.vue 文件后,当前项目会被重新装载运行,在刚才浏览的界面就会看到登录界面如下:
4.路由跳转
在 login.vue 中可以使用 this.$router.push({path: "路径"}) 来跳转到指定路径的路由组件中,下面是通过路由跳转到 error.vue 与 success.vue的代码
this.$router.push({path: "/success"}); //跳转到成功页 或 this.$router.push({path: "/error"}); //跳转到失败页
六、使用 Vuex + Axios 方式进行网络请求
1.Axios
axios 是一个网络请求构架,官方推荐使用这种方式进行 http 的请求。
1) 在 utils 包下封装一个请求工具类 request.js
import axios from 'axios' //引入 axios import baseUrl from '../api/baseUrl' //使用环境变量 + 模式的方式定义基础URL // 创建 axios 实例 const service = axios.create({ baseURL: baseUrl, // api 的 base_url timeout: 15000, // 请求超时时间 }) export default service
这里的 baseUrl 涉及 Vue CLI3 的环境变量与模式的概念,见:Vue 环境变量和模式(设置通用baseUrl)
2) 登录请求接口 API
在 api 文件夹下,创建一个登录API文件:login.js
import request from '@/utils/request' //引入封装好的 axios 请求 export function login(username, password) { //登录接口 return request({ //使用封装好的 axios 进行网络请求 url: '/admin/login', method: 'post', data: { //提交的数据 username, password } }) }
2.使用 Vuex 封装 axios
Vuex 是一个状态管理构架,官方文档:Vuex
1)封装 Vuex 中的 module
在 store 文件夹下创建一个 modules 文件夹,然后在此文件夹下创建一个 user.js 文件
import { login } from '@/api/login'//引入登录 api 接口 const user = { actions: { // 登录 Login({ commit }, userInfo) { //定义 Login 方法,在组件中使用 this.$store.dispatch("Login") 调用 const username = userInfo.username.trim() return new Promise((resolve, reject) => { //封装一个 Promise login(username, userInfo.password).then(response => { //使用 login 接口进行网络请求 commit('') //提交一个 mutation,通知状态改变 resolve(response) //将结果封装进 Promise }).catch(error => { reject(error) }) }) }, } } export default user
这里的代码值得解释一下:官方文档对应:Vuex actions
1.首先引入 login 接口,之后使用登录接口进行网络请求。
2.定义一个 名为 Login 的 action 方法,Vue 组件通过 this.$store.dispatch("Login") 调用
3.Promise,这个类很有意思,官方的解释是“
store.dispatch
可以处理被触发的 action 的处理函数返回的 Promise,并且store.dispatch
仍旧返回 Promise”。这话的意思组件中的 dispatch 返回的仍是一个 Promise 类,因此推测 Promise 中的两个方法 resolve() 与 reject() 分别对应 dispatch 中的 then 与 catch。2)创建 Vuex
在 store 文件夹下创建一个 index.js 文件
import Vue from 'vue' //引入 Vue import Vuex from 'vuex' //引入 Vuex import user from './modules/user' //引入 user module Vue.use(Vuex) const store = new Vuex.Store({ modules: { user //使用 user.js 中的 action } }) export default store
3) 将 Vuex 添加到 main.js 文件
修改之前的 main.js 文件如下:
import Vue from 'vue' import App from './App.vue' import './plugins/element.js' import router from './router' //引入路由配置 import store from './store' //引入 Vuex 状态管理 Vue.config.productionTip = false new Vue({ render: h => h(App), router, //使用路由配置 store //使用 Vuex 进行状态管理 }).$mount('#app')
重新运行项目,在 Chrome 浏览器中进入调试模式,点击登录按钮
可以看到有发送一个 8088 端口的请求,至此 Vue 端的所有代码已经完成。
-------------------------------Springboot 开发-------------------------------
项目创建就不提了,网上有很多,只要使用 Spring Assistant 创建就好。
整体目录结构如下
1.在 application.yml 修改端口号
不要和 Vue 在一个 8080 端口上:
server: port: 8088
2.解决跨域问题
这里有一个跨域问题,即 Vue 使用 8080 端口,要访问 8088 端口的服务器,会报错。错误信息如下:
Access to XMLHttpRequest at 'http://localhost:8088/admin/login' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
这个问题在 Vue 端或在 Springboot 端处理都可以,我在 Springboot 端处理的,写一个 CorsConfig 类内容如下,不要忘了 @Configuration 注解。
@Configuration public class CorsConfig { private CorsConfiguration buildConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.addAllowedOrigin("*"); // 1 corsConfiguration.addAllowedHeader("*"); // 2 corsConfiguration.addAllowedMethod("*"); // 3 return corsConfiguration; } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", buildConfig()); // 4 return new CorsFilter(source); } }
3.IErrorCode 接口
Java 版本
public interface IErrorCode { long getCode(); String getMessage(); }
Kotlin 版本
interface IErrorCode { fun getCode(): Long fun getMessage(): String }
4.CommonResult 类
Java 版本
public class CommonResult<T> { private long code; private String message; private T data; protected CommonResult() { } protected CommonResult(long code, String message, T data) { this.code = code; this.message = message; this.data = data; } /** * 成功返回结果 * * @param data 获取的数据 */ public static <T> CommonResult<T> success(T data) { return new CommonResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data); } /** * 成功返回结果 * * @param data 获取的数据 * @param message 提示信息 */ public static <T> CommonResult<T> success(T data, String message) { return new CommonResult<T>(ResultCode.SUCCESS.getCode(), message, data); } /** * 失败返回结果 * * @param errorCode 错误码 */ public static <T> CommonResult<T> failed(IErrorCode errorCode) { return new CommonResult<T>(errorCode.getCode(), errorCode.getMessage(), null); } /** * 失败返回结果 * * @param message 提示信息 */ public static <T> CommonResult<T> failed(String message) { return new CommonResult<T>(ResultCode.FAILED.getCode(), message, null); } /** * 失败返回结果 */ public static <T> CommonResult<T> failed() { return failed(ResultCode.FAILED); } /** * 参数验证失败返回结果 */ public static <T> CommonResult<T> validateFailed() { return failed(ResultCode.VALIDATE_FAILED); } /** * 参数验证失败返回结果 * * @param message 提示信息 */ public static <T> CommonResult<T> validateFailed(String message) { return new CommonResult<T>(ResultCode.VALIDATE_FAILED.getCode(), message, null); } /** * 未登录返回结果 */ public static <T> CommonResult<T> unauthorized(T data) { return new CommonResult<T>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data); } /** * 未授权返回结果 */ public static <T> CommonResult<T> forbidden(T data) { return new CommonResult<T>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data); } public long getCode() { return code; } public void setCode(long code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public T getData() { return data; } public void setData(T data) { this.data = data; } }
Kotlin 版本
class CommonResult<T> { var code: Long = 0 var message: String? = null var data: T? = null constructor(code: Long, message: String, data: T?) { this.code = code this.message = message this.data = data } companion object { /** * 成功返回结果 * @param data 获取的数据 */ fun <T> success(data: T): CommonResult<T> { return CommonResult(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data) } /** * 成功返回结果 * @param data 获取的数据 * @param message 提示信息 */ fun <T> success(data: T, message: String): CommonResult<T> { return CommonResult(ResultCode.SUCCESS.getCode(), message, data) } /** * 失败返回结果 * @param errorCode 错误码 */ fun <T> failed(errorCode: IErrorCode): CommonResult<T> { return CommonResult<T>(errorCode.getCode(), errorCode.getMessage(), null) } /** * 失败返回结果 * @param message 提示信息 */ fun <T> failed(message: String): CommonResult<T> { return CommonResult<T>(ResultCode.FAILED.getCode(), message, null) } /** * 失败返回结果 */ fun failed(): CommonResult<Any> { return failed(ResultCode.FAILED) } /** * 参数验证失败返回结果 */ fun validateFailed(): CommonResult<Any> { return failed(ResultCode.VALIDATE_FAILED) } /** * 参数验证失败返回结果 * @param message 提示信息 */ fun <T> validateFailed(message: String): CommonResult<T> { return CommonResult<T>(ResultCode.VALIDATE_FAILED.getCode(), message, null) } /** * 未登录返回结果 */ fun <T> unauthorized(data: T): CommonResult<T> { return CommonResult(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data) } /** * 未授权返回结果 */ fun <T> forbidden(data: T): CommonResult<T> { return CommonResult(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data) } } }
5.ResultCode 枚举
Java 版本
public enum ResultCode implements IErrorCode { SUCCESS(200, "操作成功"), FAILED(500, "操作失败"), VALIDATE_FAILED(404, "参数检验失败"), UNAUTHORIZED(401, "暂未登录或token已经过期"), FORBIDDEN(403, "没有相关权限"); private long code; private String message; private ResultCode(long code, String message) { this.code = code; this.message = message; } public long getCode() { return code; } public String getMessage() { return message; } }
Kotlin 版本
enum class ResultCode(private val code: Long, private val message: String) : IErrorCode { SUCCESS(200, "操作成功"), FAILED(500, "操作失败"), VALIDATE_FAILED(404, "参数检验失败"), UNAUTHORIZED(401, "暂未登录或token已经过期"), FORBIDDEN(403, "没有相关权限"); override fun getCode(): Long { return code } override fun getMessage(): String { return message } }
6.User类
Java 版本
public class User { private int id; private String username; private String password; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
Kotlin 版本
data class User( val id: Int, val username: String, val password: String)
7.LoginController 类
Java 版本
@RestController public class LoginController { @RequestMapping(value = "/admin/login", method = RequestMethod.POST) public CommonResult login(@RequestBody User user) { if (user.getUsername().equals("admin") && user.getPassword().equals("123456")) return CommonResult.success("admin"); else return CommonResult.validateFailed(); } }
Kotlin 版本
@RestController //此注解是 @ResponseBody 和 @Controller 的组合注解,可返回一个 JSON class LoginController { @RequestMapping(value = ["/admin/login"], method = [RequestMethod.POST]) fun admin(@RequestBody user: User): CommonResult<*> { return if (user.username == "admin" && user.password == "123456") { CommonResult.success("admin") } else { CommonResult.validateFailed() } } }
启动两端程序
输入正确的账号密码
输入错误的账号密码
七、GitHub源码地址
vue端:https://github.com/xiaojinlai/vue-spring-login-summed
Java端:https://github.com/xiaojinlai/vue-login-java
Java端 - Kotlin版本:https://github.com/xiaojinlai/vue-login-kotlin
注:Kotlin 版本只是我本人用习惯了 Kotlin,就功能而言与Java是一样的。大家如果不喜欢可以不用理会,如果有感兴趣的可以看看,Kotlin 是 Google 推出的一种简洁性语言,主推在 Android 上,用习惯后还是蛮喜欢的。学习起来也不难,内容也不多,推荐一个学习 Kotlin 的网址:https://www.kotlincn.net/docs/reference/
-
-
SpringBoot+Vue
2018-07-24 10:17:53SpringBoot+Vue 实现的前后端分离技术,感谢代码技术的编写者,看了之后学到了很多,建议可以看看。Vue是建立在node.js环境搭好的情况下运行,里面有一个自己整理的Vue环境搭建文档,希望可以帮到,有什么问题望大家... -
SpringBoot+vue.zip
2020-04-21 11:20:45SpringBoot+vue -
springboot+vue+swagger+redis+mysql+mybatis+pagehelper+druid+quartz
2020-04-09 11:30:44开源项目后台管理springboot+vue+swagger+redis+mysql+mybatis+pagehelper+druid+quartz 前后端在一起打包的 有安装步骤 亲测有效 -
springboot+vue
2018-11-21 10:48:09参考类,结合网上已有的部分源码使用springboot与vue实现前后端分离,简单的登录功能,系统设置功能 -
SpringBoot+Vue商城.rar
2020-07-30 16:28:21SpringBoot+Vue商城 -
带你开发前后端分离的用户管理系统(SpringBoot+Vue+Boo
2021-01-03 16:53:12本套课程为前后端分离系列专题中的SpringBoot+Vue+BootStrap开发前后端分离的用户管理系统,通过企业的实际应用场景,带大家开发一个简单的用户管理系统,让学员能够学以致用,体验企业开发过程。 技术架构 ... -
java外卖小程序源码+springboot+vue+mybatis
2020-11-23 17:30:48外卖小程序最新开发,springboot+vue+mybatis技术 餐饮小程序-点餐,商家轻松管理,更高效,更便捷的经营方式,餐饮微信领航者,助您轻松实现线上线下融合! 主要功能: 多店商家版、 预定菜品、跑腿、商家、用户注册 -
企业快速开发后台管理平台(SpringBoot+Vue+BootStrap)
2019-08-28 13:17:26本套课程为SpringBoot系列课程中的SpringBoot+Vue+BootStrap后台管理平台,通过企业的实际应用场景,给大家展示一个后台管理系统,让学员能够学以致用,快速进行实战。 项目截图: -
带你开发前后端分离的图书管理系统(SpringBoot+Vue+Ele
2021-02-05 11:31:08本套课程为前后端分离系列专题中的SpringBoot+Vue+ElementUI开发前后端分离的图书管理系统,通过企业的实际应用场景,带大家开发一个简单的图书管理系统,让学员能够学以致用,体验企业开发过程。 技术架构 ... -
Neo4j+springboot+vue+d3.js知识图谱构建和可视化
2021-01-17 13:39:45Neo4j+springboot+vue+d3.js知识图谱构建和可视化 -
SpringBoot+Vue.zip
2020-04-14 22:32:39SpringBoot+Vue的增删改查Demo,前后端分离,前后端的代码都有,数据库操作用的jpa。 -
springboot+vue.rar
2019-08-10 16:51:45这是关于springboot+vue前后端分离的入门级项目,前端使用了vue框架,后端使用了springboot+mybatis+mysql+shiro,前端包含四个页面,包括首页,笔记本,图书页面的增删改查功能,后台页面,其中后台页面只有页面,... -
springboot+vue+bootstrap实现用户增删改查
2020-12-19 19:05:34springboot+vue+bootstrap实现用户增删改查,个人手敲源码 -
SpringBoot+Vue前后端分离
2020-03-31 17:23:38此项目是基于SpringBoot+Vue的前后端分离开发基础项目,非常简单.使用前记得npm install -
整合Springboot+Vue(基础框架)
2019-06-11 22:34:10初期技术栈计划:Springboot2.13 + Vue + Mybatis + Postgresql + redies + FreeMarker(这个不一定会用到) 前期准备: Vue安装:查看node.js 和npm是否安装,这需要注意一点默认npm版本很低,需要自己upgrade下: ...初期技术栈计划:Springboot2.13 + Vue + Mybatis + Postgresql + redies + FreeMarker(这个不一定会用到)
整合配置:Logback日志管理 + Shiro权限管理 + activiti流程引擎
前期准备:
Vue安装:查看node.js 和npm是否安装,这需要注意一点默认npm版本很低,需要自己upgrade下:
我这里npm的版本已经很高了:
npm update常用命令使用
一、更新
1. npm-check检查更新
npm install -g npm-check
npm-check
2. npm-upgrade更新
npm install -g npm-upgrade
npm-upgrade
3. 更新全局包:
npm update -g
4. 更新生产环境依赖包:
npm update --save
5. 更新开发环境依赖包:
npm update --save-dev
开始安装Vue安装(cls清空dos界面内容后):
日志及命令:// 安装脚手架vue-cli G:\>npm install --global vue-cli npm WARN deprecated coffee-script@1.12.7: CoffeeScript on NPM has moved to "coffeescript" (no hyphen) C:\Users\zy962\AppData\Roaming\npm\vue -> C:\Users\zy962\AppData\Roaming\npm\node_modules\vue-cli\bin\vue C:\Users\zy962\AppData\Roaming\npm\vue-list -> C:\Users\zy962\AppData\Roaming\npm\node_modules\vue-cli\bin\vue-list C:\Users\zy962\AppData\Roaming\npm\vue-init -> C:\Users\zy962\AppData\Roaming\npm\node_modules\vue-cli\bin\vue-init + vue-cli@2.9.6 updated 1 package in 2.074s // 安装webpack模块打包器 G:\>npm install -g webpack C:\Users\zy962\AppData\Roaming\npm\webpack -> C:\Users\zy962\AppData\Roaming\npm\node_modules\webpack\bin\webpack.js npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.9 (node_modules\webpack\node_modules\fsevents): npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.9: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"}) + webpack@4.33.0 added 324 packages from 199 contributors in 35.083s //初始化webpack G:\>vue init webpack ZYVue 'git' �����ڲ����ⲿ���Ҳ���ǿ����еij��� ���������ļ��� ? Project name vu ? Project description A Vue.js project ? Author zhouyi ? Vue build standalone ? Install vue-router? Yes // 这一步如果是大项目需要,作用是:在写代码过程中,它会进行很严格的检查,比如缩进, //空格问题都非常的敏感,加入报错。但是相反编写会很烦。所以这里不要。 ? Use ESLint to lint your code? No ? Set up unit tests Yes ? Pick a test runner jest ? Setup e2e tests with Nightwatch? Yes ? Should we run `npm install` for you after the project has been created? (recommended) npm vue-cli · Generated "ZYVue". # Installing project dependencies ... # ======================== npm WARN deprecated extract-text-webpack-plugin@3.0.2: Deprecated. Please use https://github.com/webpack-contrib/mini-css-extract-plugin npm WARN deprecated browserslist@2.11.3: Browserslist 2 could fail on reading Browserslist >3.0 config used in other tools. npm WARN deprecated bfj-node4@5.3.1: Switch to the `bfj` package for fixes and new features! npm WARN deprecated json3@3.3.2: Please use the native JSON object instead of JSON 3 npm WARN deprecated browserslist@1.7.7: Browserslist 2 could fail on reading Browserslist >3.0 config used in other tools. npm WARN deprecated flatten@1.0.2: I wrote this module a very long time ago; you should use something else. npm WARN deprecated socks@1.1.10: If using 2.x branch, please upgrade to at least 2.1.6 to avoid a serious bug with socket data flow and an import issue introduced in 2.1.0 npm WARN deprecated left-pad@1.3.0: use String.prototype.padStart() > chromedriver@2.46.0 install G:\ZYVue\node_modules\chromedriver > node install.js Current existing ChromeDriver binary is unavailable, proceding with download and extraction. Downloading from file: https://chromedriver.storage.googleapis.com/2.46/chromedriver_win32.zip Saving to file: C:\Users\zy962\AppData\Local\Temp\2.46\chromedriver\chromedriver_win32.zip Received 781K... Received 1568K... Received 2352K... Received 3136K... Received 3920K... Received 4523K total. Extracting zip contents Copying to target path G:\ZYVue\node_modules\chromedriver\lib\chromedriver Done. ChromeDriver binary available at G:\ZYVue\node_modules\chromedriver\lib\chromedriver\chromedriver.exe > core-js@2.6.9 postinstall G:\ZYVue\node_modules\core-js //这里可以看到位置 > node scripts/postinstall || echo "ignore" Thank you for using core-js ( https://github.com/zloirock/core-js ) for polyfilling JavaScript standard library! The project needs your help! Please consider supporting of core-js on Open Collective or Patreon: > https://opencollective.com/core-js > https://www.patreon.com/zloirock Also, the author of core-js ( https://github.com/zloirock ) is looking for a good job -) > uglifyjs-webpack-plugin@0.4.6 postinstall G:\ZYVue\node_modules\webpack\node_modules\uglifyjs-webpack-plugin > node lib/post_install.js npm notice created a lockfile as package-lock.json. You should commit this file. npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.9 (node_modules\fsevents): npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.9: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"}) added 1518 packages from 1065 contributors and audited 31274 packages in 129.427s found 84 vulnerabilities (65 low, 8 moderate, 10 high, 1 critical) run `npm audit fix` to fix them, or `npm audit` for details # Project initialization finished! # ======================== To get started: cd ZYVue npm run dev Documentation can be found at https://vuejs-templates.github.io/webpack G:\> cd ZYVue G:\ZYVue> npm run dev // 运行命令,运行结果如下 > vu@1.0.0 dev G:\ZYVue > webpack-dev-server --inline --progress --config build/webpack.dev.conf.js 12% building modules 21/31 modules 10 active ...template&index=0!G:\ZYVue\src\App.vue{ parser: "babylon" } is depre 95% emitting DONE Compiled successfully in 2274ms 22:31:51 I Your application is running here: http://localhost:8080
在浏览器上运行上面的命令:(如下图,则成功)
任务结束命令:
ctrl+c,选择Y即可停止项目的运行(关于这个命令有时是ctrl+v)DONE Compiled successfully in 2274ms 22:31:51 I Your application is running here: http://localhost:8080 终止批处理操 作吗(Y/N)? y G:\ZYVue>
去G盘查看自己创建ZYVue
打开webstream打开该文件,可看到目录如下(并附上解释):
那么Vue的运行过程:(个人认为这个过程你懂了后面写Vue理解更加深刻)
测试Vue(其实你只需要模仿HelloWorld一套流程即可):
1.在从components下新建test的Directory目录用于测试,并建一个Test.vue,模板植入,运行。
访问http://localhost:8080/test但是始终没有得到我想要的效果
于是我修改了HelloWord
访问成功。这显然无法完成后续的开发工作。于是去解决上面,访问始终都是hello界面的问题。
仔细看了代码,发现并没有问题,但是终于看到图片是在App.vue中就引入了,删除得到预想的结果:(给出不删除的结果)
到此我们Vue环境就配置好了,其实配置什么,你真的没做什么,只不过,你要搞清楚这一套流程。关于SpringBoot项目的创建,就更简单了直接maven创建或者去官网Spring Initializr下载demo.zip即可。
创建一个SpringBoot项目,后建议个controller里面编写一个测试类:package com.cun.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class TestController { @RequestMapping("/testSpringBoot") @ResponseBody public String testSpringBoot() { return "恭喜您!测试成功,您可以开心的编写代码了,码农哥哥!"; } }
运行结果:(到此,我们就完成了,前后端的请求是能够处理的)
接下来就是SpringBoot的常用配置。
1,Logback日志管理配置。这也是Springboot默认的日志管理。我们只需要级别输出即可。
在resources下新建logback.xml(官方推荐使用logback-spring.xml)<?xml version="1.0" encoding="UTF-8"?> <configuration scan="true" scanPeriod="60 seconds" debug="false"> <!-- 定义日志文件 输入位置 --> <property name="log_dir" value="G:/logs" /> <!-- 日志最大的历史 30天 --> <property name="maxHistory" value="30"/> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger -%msg%n</pattern> </encoder> </appender> <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/error-log.log</fileNamePattern> <maxHistory>${maxHistory}</maxHistory> </rollingPolicy> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern> </encoder> </appender> <appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>INFO</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/info-log.log</fileNamePattern> <maxHistory>${maxHistory}</maxHistory> </rollingPolicy> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern> </encoder> </appender> <appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>DEBUG</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/debug-log.log</fileNamePattern> <maxHistory>${maxHistory}</maxHistory> </rollingPolicy> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern> </encoder> </appender> <logger name="com.cun" level="debug"/> <root level="debug"> <appender-ref ref="STDOUT" /> <appender-ref ref="ERROR" /> <appender-ref ref="INFO" /> <appender-ref ref="DEBUG" /> </root> </configuration>
还需要/vsCode/src/main/resources/application.yml或application.properties中配置
运行项目请求上面的测试类:(先清空,在请求,可以看到日志打印出)
查看我们配置目录:(也可以看到日志写入文件)
到此,日志管理就配置完成。(这里特意说明一点配置debug的配置可以考虑不要,其次还可以优化具体导指定的包,比如这里我偷懒logger name=“com.cun” level="debug"直接配了总体包,其实完全可以具体指定哪里输出)2,Mybatis的整合。(这里就采用springboot 2.0 默认连接池Hikari)
在我们上面配置的日志后,测试。依赖你有但是没用到,就可以看到console中很多配置信息,需要加载和怎么条件下加载。里面就涉及到数据库信息。
那么进入主题,整合Mybatis + Postgresql。
在pom.xml中导入mybatis和postgresql依赖:<!-- 为了数据能够显出,引入对象互转json形式依赖 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.8</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.8</version> </dependency> <!-- 数据库+Mybatis依赖 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.1</version> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.2.5</version> </dependency> <!-- 这里引入lombok来简化代码,不过本地你也必须保证要添加配置,详细请看lombok官网的介绍 否则你是运行不成功的--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.8</version> <scope>provided</scope> </dependency>
首先说明这里有两种方式整合mybatis:① 依赖于mapper;② 依赖注解。
① 依赖于mapper
我们先用我们最为常见的方式,方式一依赖于mapper文件。
数据库建表+数据insertdrop table test_user; // 删除可能存在表 // 创建表 create table "public"."test_user" ( user_no Integer, user_name varchar(16) COLLATE "default" NOT NULL, user_sex varchar(2) COLLATE "default" NOT NULL ) WITH (OIDS=FALSE) ; ALTER TABLE "public"."test_user" OWNER TO "postgres"; select * from test_user //测试建表结果 // 插入数据 insert into test_user(user_no,user_name,user_sex) values(1001,'zhouyi','男'); insert into test_user(user_no,user_name,user_sex) values(1004,'qianqian','女')
结果:
编写程序严格过程(从底层写道controller(这个过程仅纯个人想法,觉得这样逻辑会省很多事情,因为有了请求是开始,有了mapper映射是想要结果。剩下的都是中间过程,这样在写大型的项目处理中,你得逻辑不会走着走着就不知道了)):
对应的数据库实体对象:package com.cun.entity; import org.springframework.stereotype.Component; import lombok.Data; import lombok.Getter; import lombok.Setter; @Data @Getter @Setter @Component public class TestUser { private Integer testUserNo; private String testUserName; private String testUserSex; }
先写mapper(即你请求时什么,想得到什么)
package com.cun.mapper; import java.util.List; import org.apache.ibatis.annotations.Mapper; import org.mybatis.spring.annotation.MapperScan; import com.cun.entity.TestUser; // 这里说下编程的思路(仅个人意见):写代码的顺序从底层代码写起 @Mapper // 利用这个注解让mybatis自动加载 public interface TestUserMapper { // 这里我们先规划:我们要去跟增删改查 // 1, 查数据 public List<TestUser> geTestUsers() throws Exception; // 2,改数据 public void updateTestUser(TestUser testUser) throws Exception; // 3, 新增用户 public void addTestUser(TestUser testUser) throws Exception; //4, 根据no删除 public void deleteTestUser(Integer testUserNo) throws Exception; }
写好对应的TestUser.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://www.mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.cun.mapper.TestUserMapper"> <!-- 对应查数据,带返回值,无参数入力 --> <select id="geTestUsers" resultType="com.cun.entity.TestUser"> select * from test_user </select> <!-- 关于改数据,总体入力是不好的,这里单纯为了说明问题 --> <!-- 想了想怕误导别人这里就不写了 --> <!-- 新增用户,不带返回值,带入力参数 --> <insert id="addTestUser" parameterType="com.cun.entity.TestUser"> insert into test_user(user_no,user_name,user_sex) values(#{testUserNo},#{testUserName},${testUserSex}) </insert> <delete id="deleteTestUser" parameterType="Integer"> delete from test_user where user_no = #{testUserNo} </delete> </mapper>
接着在application.yml或application.properties中配置数据库+mapper加载路径。
#数据库连接 MyFramework 数据库名 spring: datasource: driverClassName: org.postgresql.Driver url: jdbc:postgresql://localhost/MyFramework username: postgres password: zhouyi #Mapper配置加载路径 mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true
相应的接口类:
package com.cun.service; import java.util.List; import com.cun.entity.TestUser; //@Service // 这里接口不加这个注解 public interface TestUserService { public List<TestUser> geTestUsers() throws Exception; public void updateTestUser(TestUser testUser) throws Exception; public void addTestUser(TestUser testUser) throws Exception; public void deleteTestUser(Integer testUserNo) throws Exception; }
接口对应的实现类:
package com.cun.serviceImpl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.cun.entity.TestUser; import com.cun.mapper.TestUserMapper; import com.cun.service.TestUserService; @Service // 这个注解应该加载具体的实现类上 public class TestUserServiceImpl implements TestUserService { @Autowired private TestUserMapper mapper; @Override public List<TestUser> geTestUsers() throws Exception { return mapper.geTestUsers(); } @Override public void updateTestUser(TestUser testUser) throws Exception { mapper.updateTestUser(testUser); } @Override public void addTestUser(TestUser testUser) throws Exception { mapper.addTestUser(testUser); } @Override public void deleteTestUser(Integer testUserNo) throws Exception { mapper.deleteTestUser(testUserNo); } }
最后到控制类:
package com.cun.controller; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.cun.entity.TestUser; import com.cun.service.TestUserService; @Controller public class TestUsercontroller { // 依赖注入 @Autowired private TestUserService service; @Autowired private TestUser user; // 这个你也可以不用注解的方式,自己new // 显示出所有使用者 @RequestMapping("/showList") @ResponseBody public List<TestUser> showList() throws Exception { List<TestUser> users = service.geTestUsers(); System.out.println(users); return users; } // 增加使用者 @RequestMapping("/addUser") public String addUser(TestUser user) throws Exception { service.addTestUser(user); return user.getTestUserName() + "增加使用者成功"; } // 删除 @RequestMapping("/deleteUser") public String deleteUser(Integer testUserNo) throws Exception { service.deleteTestUser(testUserNo); return "删除"+ testUserNo +"成功"; } }
这里有一个坑:
我还需要在主类:package com.cun; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("com.cun.mapper") // 这个配置和上面的mapper配置中@Mapper 相互作用才能正常读到mapper public class VsCodeApplication { public static void main(String[] args) { SpringApplication.run(VsCodeApplication.class, args); } }
到此我们就配置写完了。
利用postman测试:(结果让我失望了,数据库有数据,查出却为空)
回头从新看一遍程序,debug下查了Console日志。发现数据库的值是已经取出来的,但是写出对象是出错。这是为什么呢?于是我们去查出数据转为对象的地方。
其实原因很简单。看如图分析:
resultMap这个主要针对你有返回值的情况。不多说我们在映射中添加两者转化过程,直接代码。
写之前,你需要的认知:https://blog.csdn.net/weixin_42603009/article/details/91981037<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://www.mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.cun.mapper.TestUserMapper"> <result property="testUserNo" column="user_no"/> <result property="testUserName" column="user_name"/> <result property="testUserSex" column="user_sex"/> </resultMap> <select id="geTestUsers" resultMap="TestUser"> select * from test_user </select> <insert id="addTestUser" parameterType="com.cun.entity.TestUser"> insert into test_user(user_no,user_name,user_sex) values(#{testUserNo},#{testUserName},${testUserSex}) </insert> <delete id="deleteTestUser" parameterType="Integer"> delete from test_user where user_no = #{testUserNo} </delete> </mapper>
再次利用ppostman请求得到如下结果:(另外两个测试不给出,后面的整合篇幅还很大)
第一种就整合完成了另外这里说说第二种mybatis配置方式(个人觉得这种方式用来玩还不错,但是你要用大型项目是不可能,纯属个人见解,或许随着认知的加深这种方式也有大做用)
针对第一种方式中以上的增删改查的操作,有四个注解@Select、@Delete、@Insert、@Update,在注解中加上之前在xml中写的sql就行了。这样子就不需要我们的xml文件了。
代码示例如下:(以下代码职位说明这种概念,并未测试)package com.cun.mapper; import java.util.List; import org.apache.ibatis.annotations.Mapper; import org.mybatis.spring.annotation.MapperScan; import com.cun.entity.TestUser; @Mapper // 利用这个注解让mybatis自动加载 public interface TestUserMapper { // 1, 查数据 // 这里查数据不太举例子,个人能力有限,理解不到用这种方式来写查并返回给实体 @Select("select * from test_user") public List<TestUser> geTestUsers() throws Exception; // 2,改数据 // 这个也不太说,大致参考下面两种。 @Update("update user set username = #{name} where id = #{id}") public void updateTestUser(TestUser testUser) throws Exception; // 3, 新增用户 @Insert("insert into test_user(user_no,user_name,user_sex) values(#{testUserNo},#{testUserName},${testUserSex})") public void addTestUser(TestUser testUser) throws Exception; //4, 根据no删除 @Delete("delete from test_user where user_no = #{testUserNo}") public void deleteTestUser(Integer testUserNo) throws Exception; }
到此我们mybatis整合+相关概念就完成了。
接下来就是前后台数据对接如何对接上的问题。
但是如何对接?目前的对接处理方式我所了解的有三种:ajax、axios、fetch。在接着写下面的内容之前,我想我有必要去研究这三个的用法区别。
请参考:https://blog.csdn.net/weixin_42603009/article/details/92079285
完全没有用过axios,这也是现在流行,浏览器支持最好的方式。我们就来尝试按官方文档使用axios实现我们前后台分离数据对接。axios安装有三种方式:1,使用 npm;2,使用 bower;3,使用 cdn
这里使用npm,在WebStorm的项目根目录下:npm install axios -S
结果如下:
这里我安装完成,照理来说是可以直接使用的,但是这不符合现实情况,比如请求可能出现各种错误,最常见是代码错误,业务请求方式错误。我们的目标是只是处理请求成功。因为我们有必要对前端请求进行封装,封装完成后,将前端错误统一处理。这样,开发者只需要在每一次发送请求的地方处理请求成功的情况即可。
首先我们来尝试用两种方式fetch和axios实现跨域请求处理:
我们后台的URI:http://127.0.0.1:8000/showList
首先在config目录下的index.js进行跨域设置:proxyTable: { '/api': { target:"http://127.0.0.1:8000/", chunkOrigins: true,// 允许跨域 pathRewrite:{ '^/api': '' // 路径重写,使用"/api"代替target. } } },
fetch测试请求处理:
在App.vue中:<template> <div id="app"> <router-view/> </div> </template> <script> export default { name: 'App', data(){ return{ } }, // fetch实现跨域 created() { // fetch方式实现跨域 fetch("/api/showList",{ method:"POST", }).then(res => { console.log(res) }) } } </script>
在后台设置断点,访问http://127.0.0.1:8080/#/
后台进入断点:
结果:(请求处理成功,但是返回值,并没有打印处理来,这个问题后面处理)
Axios请求测试:(操作同上,下面只给出程序 + 结果)<template> <div id="app"> <h3><router-link to="/test">test画面</router-link></h3> <router-view/> </div> </template> <script> export default { name: 'App', data(){ return{ } }, //实现跨域 created() { // fetch方式实现跨域 this.$axios.post("/api/showList").then(res=>{ console.log(res) }) } } </script>
测试结果:(结果为自己预想的结果,可以看到请求成功,并返回和上面postman测试相同的结果)
到此关于跨域就解决了,并且前后台也贯通了。数据报格式设计:
请求格式设计:(暂时这样设计,后面可能根据实际改动)// 请求格式,针对postman { "requestNo":null, "requsetTimeStamp":null, "type":null, "userid":null, "AdminId":null, "userType":null, "token":null, "usedates":null, "requestData":null }
3,定义一个响应拦截器。响应这一块数据结构是需要设计的。
具体设计:
① 返回的数据格式:// 返回格式定义,这里就是用最常见的格式 { "requestNo":null, "responseTimestamp":null, "token":null, "userid":null, "userType":null, "responseCode":null, "errorCode":null, "responseData":{ "usr":{ "testUserNo":123, "testUserName":"周毅", "testUserSex":"男" } } }
简单来说上面封装的对应代码:
回到上面的问题。上面数据报的设计就是为了更好写拦截器。
正如上面所说,存在的各种各样的问题,我们关心的只有正确的请求,所以进一步前端请求封装:
首先我们要明白设置拦截器的目的是什么,当我们需要统一处理http请求和响应时我们通过设置拦截器。处理方便很多axios 中,有请求拦截器,也有响应拦截器。都在axios.interceptors中。
大致思路:
1,引入axios、错误/成功信息显示element-ui的loading和message
2,定义请求拦截器处理。
请求/响应拦截(这里是指对http的拦截,暂时这么定义,后面权限设计时会改动)
编写测试接口用返回封装:package com.cun.controller; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.cun.common.JsonAndObject; import com.cun.common.JsonRequestBody; import com.cun.common.JsonResponseBody; import com.cun.entity.TestUser; @Controller public class TestController { @RequestMapping("/testResAndReq") @ResponseBody // @RequestBody 不能是空参数,这是这样写巨大缺陷,请求不能为空 // // @RequestBody(required = false) public String testResAndReq(@RequestBody JsonRequestBody<Map<String, Object>> requestBody, HttpServletRequest request, HttpServletResponse response) { String jsonString = null; TestUser usr = new TestUser(); usr.setTestUserNo(123); usr.setTestUserName("周毅"); usr.setTestUserSex("男"); Map<String, Object> map = new HashMap<String, Object>(); map.put("usr", usr); JsonResponseBody<Map<String, Object>> responseBody = new JsonResponseBody<>(); responseBody.setResponseData(map); jsonString = JsonAndObject.translateObjectToJsonString(responseBody); return jsonString; } }
postman的结果:(很多值没设置,单纯是为了测试)
Vue画面上测试:
上面报错org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing解决方式:(这里在说明一点,深入研究的心要深一点,我们点击equest body,我我们可以看到默认是true,也就是必须带参数,所以我设置required = false即可)
到此我们基础框架就搭完了。接下来是Vue画面设计和后台相关技术的整合。 -
vue-基于springboot+vue+redis实现的前后端分离员工管理系统
2020-10-22 01:38:20vue-基于springboot+vue+redis实现的前后端分离员工管理系统,在springboot基础上,实现前后端分离,同时涉及Redis(NoSql数据库),解决业务缓存问题 -
tomcat部署springboot+vue.doc
2020-04-22 09:53:37政府门户网站,企业官网等基本都是web项目,web项目所使用的技术也是在不断的更新,前几年的技术基本上都是jsp+ssh,到后来的h5+ssh,h5+spring+mybatis,目前使用最多的是springboot+VUE前后端分离的技术,... -
springboot+vue实现文件上传下载
2021-01-18 17:04:45本文实例为大家分享了springboot+vue实现文件上传下载的具体代码,供大家参考,具体内容如下 一、文件上传(基于axios的简单上传) 所使用的技术:axios、springboot、vue; 实现思路:通过h5 :input元素标签进行... -
springboot+vue前后端分离开发项目源码
2021-01-02 21:32:54springboot+vue前后端分离开发项目源码 -
SpringBoot+Vue前后端分离的分布式项目实战教程
2020-06-10 11:40:08Web开发发展至今,前后端分离已经成为一种大趋势。 前后端分离会为以后的... 本课程通过一个在线教育为雏形,进行前后端分离的实战,可以让你快速的掌握SpringBoot+Vue进行项目的搭建,应对企业开发和企业面试。 -
SpringBoot+Vue前后端分离 .zip
2020-06-14 19:13:09采用SpringBoot+Vue前端端分离的的方式进行二手书的设计 前端主要使用:Vue+ElementUi,以Nginx作为服务器 性能优化:上传图片压缩,前端代码gzip压缩,ElementUI 按需使用,Vue模块按需加载,CDN引用文件加速。 ... -
WebCollector+SpringBoot+Vue+Bootstrap,简易通用式Web应用爬虫
2019-10-18 14:08:12利用WebCollector+SpringBoot+Vue+Bootstrap搭建的一个Web应用版的爬虫,可以应付多种直观可以见的页面爬取,通过http://localhost:8886/index.jsp即可访问,其中网站输入为爬取网站首页网址,其余分别填写CSS选择器... -
SpringBoot+VUE前后端分离demo
2020-04-20 13:45:10SpringBoot+VUE前后端分离demo,后端 Spring Boot 技术和前端 Vue 技术来简单开发一个增删改查demo,该demo以简单、方便理解的方式来记录前后端结合使用的过程,方便正式开发复杂项目时能提前整体理解流程 -
SpringBoot + Vue + Electron 开发 QQ 版聊天工具的详细教程
2020-10-15 07:29:12主要介绍了SpringBoot + Vue + Electron 开发 QQ 版聊天工具的教程,本文通过截图实例代码相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下 -
springboot+vue部署按照及运行方法
2020-08-25 03:26:07在本篇文章里小编给大家整理了关于springboot+vue部署按照及运行方法和实例内容,需要的朋友们学习参考下。 -
springboot+vue实现websocket配置过程解析
2020-08-19 08:51:23主要介绍了springboot+vue实现websocket配置过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 -
(前后端分离)SpringBoot+Vue实现视频播放
2020-02-25 14:22:49SpringBoot+vue的项目中,实现前端播放视频 SpringBoot 定义GET请求ApI,返回视频流,前端通过 话不多说,走起 一、新建如下类,用于返回视频流 import org.springframework.core.io.FileSystemResource; ...跳坑日志
SpringBoot+vue的项目中,实现前端播放视频
SpringBoot 定义GET请求ApI,返回视频流,前端通过
话不多说,走起
一、新建如下类,用于返回视频流
import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.stereotype.Component; import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; import javax.servlet.http.HttpServletRequest; import java.nio.file.Path; @Component public class NonStaticResourceHttpRequestHandler extends ResourceHttpRequestHandler { public final static String ATTR_FILE = "NON-STATIC-FILE"; @Override protected Resource getResource(HttpServletRequest request) { final Path filePath = (Path) request.getAttribute(ATTR_FILE); return new FileSystemResource(filePath); } }
二、再写Controller层
这里主要的是得到视频所在的物理地址
@RestController @RequestMapping("/file") @AllArgsConstructor public class FileRestController { private final NonStaticResourceHttpRequestHandler nonStaticResourceHttpRequestHandler; @GetMapping("/video") public void videoPreview(HttpServletRequest request, HttpServletResponse response) throws Exception { //假如我把视频1.mp4放在了static下的video文件夹里面 //sourcePath 是获取resources文件夹的绝对地址 //realPath 即是视频所在的磁盘地址 String sourcePath = ClassUtils.getDefaultClassLoader().getResource("").getPath().substring(1); String realPath = sourcePath +"static/video/1.mp4"; Path filePath = Paths.get(realPath ); if (Files.exists(filePath)) { String mimeType = Files.probeContentType(filePath); if (!StringUtils.isEmpty(mimeType)) { response.setContentType(mimeType); } request.setAttribute(NonStaticResourceHttpRequestHandler.ATTR_FILE, filePath); nonStaticResourceHttpRequestHandler.handleRequest(request, response); } else { response.setStatus(HttpServletResponse.SC_NOT_FOUND); response.setCharacterEncoding(StandardCharsets.UTF_8.toString()); } } }
三、后端url测试
到了这一步基本可以通过访问后端url进行视频播放了
测试:http://localhost:8080/file/video
不出意外的话是可以播放的,如果播放不了的,再检查下视频所在的物理地址
第三步没问题的话进行第四步
四、前端播放
前端播放代码
<video controls autoplay src="http://localhost:8080/file/video"/>
这里有个要注意的点:建议 src属性 直接放在<video>里,别放在<video>标签下的<source>里,会播放不了的
over
-
Unity 热更新技术-ILRuntime
-
具有超窄带宽的稳定锁模纳秒无Chi脉冲产生
-
基于杜鹃搜索的磷虾群算法解决工程优化问题
-
朱老师c++课程第3部分-3.5STL的其他容器讲解
-
百亿级日志系统架构设计及优化
-
从 Notbook 到 JupyterLab, 再配上代码帮手 Kite
-
通过有源射频锁相实现稳定的光纤时间传输
-
辅助驾驶的哈密顿量对绝热演化是否总是有用?
-
uniapp下拉刷新
-
使用内置传感器的LED进行LED热阻和TIM评估的研究
-
美图大数据平台架构实践
-
单精度和半精度混合训练
-
Golang零基础-->高级编程
-
Unity ILRuntime框架设计
-
基于Qt的LibVLC开发教程
-
基于电商业务的全链路数据中台落地方案(全渠道、全环节、全流程)
-
2009年下半年 电子商务设计师 上午试卷 综合知识 软考真题【含答案和答案解析】
-
两种新的具有最优修复的2位奇偶校验MDS阵列代码
-
项目经理成长之路
-
ASP.NET学习——用户增删改查(三层,数据库+源码)