精华内容
下载资源
问答
  • 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:53
    SpringBoot+Vue 实现的前后端分离技术,感谢代码技术的编写者,看了之后学到了很多,建议可以看看。Vue是建立在node.js环境搭好的情况下运行,里面有一个自己整理的Vue环境搭建文档,希望可以帮到,有什么问题望大家...
  • SpringBoot+vue.zip

    2020-04-21 11:20:45
    SpringBoot+vue
  • 开源项目后台管理springboot+vue+swagger+redis+mysql+mybatis+pagehelper+druid+quartz 前后端在一起打包的 有安装步骤 亲测有效
  • springboot+vue

    2018-11-21 10:48:09
    参考类,结合网上已有的部分源码使用springbootvue实现前后端分离,简单的登录功能,系统设置功能
  • SpringBoot+Vue商城.rar

    2020-07-30 16:28:21
    SpringBoot+Vue商城
  • 本套课程为前后端分离系列专题中的SpringBoot+Vue+BootStrap开发前后端分离的用户管理系统,通过企业的实际应用场景,带大家开发一个简单的用户管理系统,让学员能够学以致用,体验企业开发过程。   技术架构 ...
  • 外卖小程序最新开发,springboot+vue+mybatis技术 餐饮小程序-点餐,商家轻松管理,更高效,更便捷的经营方式,餐饮微信领航者,助您轻松实现线上线下融合! 主要功能: 多店商家版、 预定菜品、跑腿、商家、用户注册
  • 本套课程为SpringBoot系列课程中的SpringBoot+Vue+BootStrap后台管理平台,通过企业的实际应用场景,给大家展示一个后台管理系统,让学员能够学以致用,快速进行实战。  项目截图: 
  • 本套课程为前后端分离系列专题中的SpringBoot+Vue+ElementUI开发前后端分离的图书管理系统,通过企业的实际应用场景,带大家开发一个简单的图书管理系统,让学员能够学以致用,体验企业开发过程。   技术架构 ...
  • Neo4j+springboot+vue+d3.js知识图谱构建和可视化
  • SpringBoot+Vue.zip

    2020-04-14 22:32:39
    SpringBoot+Vue的增删改查Demo,前后端分离,前后端的代码都有,数据库操作用的jpa。
  • springboot+vue.rar

    2019-08-10 16:51:45
    这是关于springboot+vue前后端分离的入门级项目,前端使用了vue框架,后端使用了springboot+mybatis+mysql+shiro,前端包含四个页面,包括首页,笔记本,图书页面的增删改查功能,后台页面,其中后台页面只有页面,...
  • springboot+vue+bootstrap实现用户增删改查,个人手敲源码
  • 此项目是基于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文件。
    数据库建表+数据insert

    drop 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实现的前后端分离员工管理系统,在springboot基础上,实现前后端分离,同时涉及Redis(NoSql数据库),解决业务缓存问题
  • 政府门户网站,企业官网等基本都是web项目,web项目所使用的技术也是在不断的更新,前几年的技术基本上都是jsp+ssh,到后来的h5+ssh,h5+spring+mybatis,目前使用最多的是springboot+VUE前后端分离的技术,...
  • 本文实例为大家分享了springboot+vue实现文件上传下载的具体代码,供大家参考,具体内容如下 一、文件上传(基于axios的简单上传) 所使用的技术:axios、springboot、vue; 实现思路:通过h5 :input元素标签进行...
  • springboot+vue前后端分离开发项目源码
  • Web开发发展至今,前后端分离已经成为一种大趋势。 前后端分离会为以后的... 本课程通过一个在线教育为雏形,进行前后端分离的实战,可以让你快速的掌握SpringBoot+Vue进行项目的搭建,应对企业开发和企业面试。
  • 采用SpringBoot+Vue前端端分离的的方式进行二手书的设计 前端主要使用:Vue+ElementUi,以Nginx作为服务器 性能优化:上传图片压缩,前端代码gzip压缩,ElementUI 按需使用,Vue模块按需加载,CDN引用文件加速。 ...
  • 利用WebCollector+SpringBoot+Vue+Bootstrap搭建的一个Web应用版的爬虫,可以应付多种直观可以见的页面爬取,通过http://localhost:8886/index.jsp即可访问,其中网站输入为爬取网站首页网址,其余分别填写CSS选择器...
  • SpringBoot+VUE前后端分离demo,后端 Spring Boot 技术和前端 Vue 技术来简单开发一个增删改查demo,该demo以简单、方便理解的方式来记录前后端结合使用的过程,方便正式开发复杂项目时能提前整体理解流程
  • 主要介绍了SpringBoot + Vue + Electron 开发 QQ 版聊天工具的教程,本文通过截图实例代码相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
  • 在本篇文章里小编给大家整理了关于springboot+vue部署按照及运行方法和实例内容,需要的朋友们学习参考下。
  • 主要介绍了springboot+vue实现websocket配置过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • SpringBoot+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

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,483
精华内容 3,393
关键字:

springboot+vue

vue 订阅
spring 订阅