一步步使用SpringBoot结合Vue实现登录和用户管理功能,附带学习经验

@Test

@DisplayName(“删除”)

public void testDelete(){

userMapper.deleteById(1);

}

}

至此前后端项目基本搭建完成,接下来开始进行功能开发。

三、登录功能开发

====================================================================

1、前端开发


1.1、登录界面

在前面访问页面的时候,有一个 V logo,看起来比较奇怪,我们先把它去掉,这个图片的引入是在根组件中——src\App.vue ,把下面一行注释或者去掉。

image-20210117140457353

在src目录下新建文件夹views,在views下新建文件 login.vue

登录

用户名:



密码:



登录

1.2、添加路由

router\index.js 里添加路由,代码如下:

import Vue from ‘vue’

import Router from ‘vue-router’

import HelloWorld from ‘@/components/HelloWorld’

//导入登录页面组件

import Login from ‘@/views/login.vue’

Vue.use(Router)

export default new Router({

routes: [

{

path: ‘/’,

name: ‘HelloWorld’,

component: HelloWorld

},

//添加登录页面路由

{

path:‘/login’,

name: ‘Login’,

component: Login

}

]

})

OK,现在在浏览器里输入 http://localhost:8080/#/login ,就可以访问登录页面:

image-20210117141836421

页面有点粗糙简陋对不对,没关系,我们可以引入ElmentUI ,使用ElementUI中已经成型的组件。

1.3、引入ElementUI美化界面

Element 的官方地址为 http://element-cn.eleme.io/#/zh-CN ,官方文档比较好懂,大部分组件复制粘贴即可。

image-20210118200916023

1.3.1、安装Element UI

在vscode 中打开终端,运行命令npm i element-ui -S ,就安装了 element ui 最新版本—当前是 2.15.0

image-20210117142500415

1.3.2、引入 Element

引入分为完整引入和按需引入两种模式,按需引入可以缩小项目的体积,这里我们选择完整引入。

根据文档,我们需要修改 main.js 为如下内容:

// The Vue build version to load with the import command

// (runtime-only or standalone) has been set in webpack.base.conf with an alias.

import Vue from ‘vue’

import App from ‘./App’

import router from ‘./router’

//引入ElementUI

import ElementUI from ‘element-ui’

import ‘element-ui/lib/theme-chalk/index.css’

Vue.config.productionTip = false

/* eslint-disable no-new */

Vue.use(ElementUI)

new Vue({

el: ‘#app’,

router,

components: { App },

template: ‘’

})

1.3.3、使用ElementUI美化登录页面

现在开始使用 ElementUI和 css美化我们的登录界面,修改后的login.vue代码如下:

<el-input

type=“text”

v-model=“loginForm.loginName”

auto-complete=“off”

placeholder=“账号”

<el-input

type=“password”

v-model=“loginForm.password”

auto-complete=“off”

placeholder=“密码”

<el-button

type=“primary”

style=“width: 100%; border: none”

登录</el-button

需要注意:

  • src\assets 路径下新建一个一个文件夹 img,在 img 里放了一张网上找到的无版权图片作为背景图

  • App.vue 里删了一行代码,不然会有空白:

margin-top: 60px;

好了,看看我们修改之后的登录界面效果:

image-20210117150416183

OK,登录界面的面子已经做好了,但是里子还是空的,没法和后台交互。

1.4、引入axios发起请求

相信大家都对 ajax 有所了解,前后端分离情况下,前后端交互的模式是前端发出异步式请求,后端返回 json 。

axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范。在这里我们只需要知道它是非常强大的网络请求处理库,且得到广泛应用即可。

在项目目录下运行命令 npm install --save axios ,安装模块:

image-20210117152013705

main.js 里全局注册 axios:

var axios = require(‘axios’)

// 全局注册,之后可在其他组件中通过 this.$axios 发送数据

Vue.prototype.$axios = axios

那么怎么使用 axios 发起请求呢?

login.vue中添加方法:

methods: {

login () {

this.$axios

.post(‘/login’, {

loginName: this.loginForm.loginName,

password: this.loginForm.password

})

.then(successResponse => {

if (successResponse.data.code === 200) {

this.$router.replace({path: ‘/’})

}

})

.catch(failResponse => {

})

}

},

这个方法里通过 axios 向后台发起了请求,如果返回成功的结果就跳转到 / 路由下。

在登录按钮里触发这个方法:

<el-button

type=“primary”

style=“width: 100%; border: none”

@click=“login”

登录</el-button

那么现在就能向后台发起请求了吗?还没完。

1.5、前端相关配置

  • 反向代理

修改 src\main.js ,添加反向代理的配置:

// 设置反向代理,前端请求默认发送到 http://localhost:8888/api

axios.defaults.baseURL = ‘http://localhost:8088/api’

这么一来,我们在前面写的登录请求,访问的后台地址实际就是 http://localhost:8088/api/login

  • 跨域配置

前后端分离会带来一个问题—跨域,关于跨域,这里就不展开讲解。在 config\index.js 中,找到 proxyTable 位置,修改为以下内容:

proxyTable: {

‘/api’: {

target: ‘http://localhost:8088’,

changeOrigin: true,

pathRewrite: {

‘^/api’: ‘’

}

}

},

2、后端开发


2.1、统一结果封装

这里我们创建了一个 Result 类,用于异步统一返回的结果封装。一般来说,结果里面有几个要素必要的

  • 是否成功,可用 code 表示(如 200 表示成功,400 表示异常)

  • 结果消息

  • 结果数据

/**

  • @Author: 三分恶

  • @Date: 2021/1/17

  • @Description: 统一结果封装

**/

public class Result {

//相应码

private Integer code;

//信息

private String message;

//返回数据

private Object data;

//省略getter、setter、构造方法

}

实际上由于响应码是固定的,code 属性应该是一个枚举值,这里作了一些简化。

2.2、登录业务实体类

为了接收前端登录的数据,我们这里创建了一个登录用的业务实体类:

public class LoginDTO {

private String loginName;

private String password;

//省略getter、setter

}

2.3、控制层

LoginController,进行业务响应:

/**

  • @Author: 三分恶

  • @Date: 2021/1/17

  • @Description: TODO

**/

@RestController

public class LoginController {

@Autowired

LoginService loginService;

@PostMapping(value = “/api/login”)

@CrossOrigin //后端跨域

public Result login(@RequestBody LoginDTO loginDTO){

return loginService.login(loginDTO);

}

}

2.4、业务层

业务层进行实际的业务处理。

  • LoginService:

public interface LoginService {

public Result login(LoginDTO loginDTO);

}

  • LoginServiceImpl:

/**

  • @Author: 三分恶

  • @Date: 2021/1/17

  • @Description:

**/

@Service

public class LoginServiceImpl implements LoginService {

@Autowired

private UserMapper userMapper;

@Override

public Result login(LoginDTO loginDTO) {

if (StringUtils.isEmpty(loginDTO.getLoginName())){

return new Result(400,“账号不能为空”,“”);

}

if (StringUtils.isEmpty(loginDTO.getPassword())){

return new Result(400,“密码不能为空”,“”);

}

//通过登录名查询用户

QueryWrapper wrapper = new QueryWrapper();

wrapper.eq(“login_name”, loginDTO.getLoginName());

User uer=userMapper.selectOne(wrapper);

//比较密码

if (uer!=null&&uer.getPassword().equals(loginDTO.getPassword())){

return new Result(200,“”,uer);

}

return new Result(400,“登录失败”,“”);

}

}

启动后端项目:

image-20210117174026776

访问登录界面,效果如下:

登录简单效果

这样一个简答的登录就完成了,接下来,我们会对这个登录进一步完善。

四、登录功能完善

====================================================================

前面虽然实现了登录,但只是一个简单的登录跳转,实际上并不能对用户的登录状态进行判别,接下来我们进一步完善登录功能。

首先开始后端的开发。

1、后端开发


1.1、拦截器

在前后端分离的情况下,比较流行的认证方案是 JWT认证 认证,和传统的session认证不同,jwt是一种无状态的认证方法,也就是服务端不再保存任何认证信息。出于篇幅考虑,我们这里不再引入 JWT ,只是简单地判断一下前端的请求头里是否存有 token 。对JWT 认证感兴趣的可以查看文章:SpringBoot学习笔记(十三:JWT )

  • 创建 interceptor 包,包下新建拦截器 LoginInterceptor

/**

  • @Author: 三分恶

  • @Date: 2021/1/18

  • @Description: 用户登录拦截器

**/

public class LoginInterceptor implements HandlerInterceptor {

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {

//从header中获取token

String token = request.getHeader(“token”);

//如果token为空

if (StringUtils.isBlank(token)) {

setReturn(response,401,“用户未登录,请先登录”);

return false;

}

//在实际使用中还会:

// 1、校验token是否能够解密出用户信息来获取访问者

// 2、token是否已经过期

return true;

}

@Override

public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {

}

//返回json格式错误信息

private static void setReturn(HttpServletResponse response, Integer code, String msg) throws IOException {

HttpServletResponse httpResponse = (HttpServletResponse) response;

httpResponse.setHeader(“Access-Control-Allow-Credentials”, “true”);

httpResponse.setHeader(“Access-Control-Allow-Origin”, HttpContextUtil.getOrigin());

//UTF-8编码

httpResponse.setCharacterEncoding(“UTF-8”);

response.setContentType(“application/json;charset=utf-8”);

Result result = new Result(code,msg,“”);

ObjectMapper objectMapper = new ObjectMapper();

String json = objectMapper.writeValueAsString(result);

httpResponse.getWriter().print(json);

}

}

  • 为了能给前端返回 json 格式的结果,这里还用到了一个工具类,新建 util 包,util 包下新建工具类 HttpContextUtil

/**

  • @Author: 三分恶

  • @Date: 2021/1/18

  • @Description: http上下文

**/

public class HttpContextUtil {

public static HttpServletRequest getHttpServletRequest() {

return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

}

public static String getDomain() {

HttpServletRequest request = getHttpServletRequest();

StringBuffer url = request.getRequestURL();

return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString();

}

public static String getOrigin() {

HttpServletRequest request = getHttpServletRequest();

return request.getHeader(“Origin”);

}

}

1.2、拦截器配置

拦截器创建完成之后,还需要进行配置。

/**

  • @Author: 三分恶

  • @Date: 2021/1/18

  • @Description: web配置

**/

@Configuration

public class DemoWebConfig implements WebMvcConfigurer {

/**

  • 拦截器配置

  • @param registry

*/

@Override

public void addInterceptors(InterceptorRegistry registry) {

//添加拦截器

registry.addInterceptor(new LoginInterceptor()).addPathPatterns(“/api/**”)

//放行路径,可以添加多个

.excludePathPatterns(“/api/login”);

}

}

1.3、跨域配置

细致的同学可能会发现,在之前的后台接口,有一个注解@CrossOrigin ,这个注解是用来跨域的,每个接口都写一遍肯定是不太方便的,这里我们 创建跨域配置类并添加统一的跨域配置:

/**

  • @Author 三分恶

  • @Date 2021/1/25

  • @Description 跨域配置

*/

@Configuration

public class CorsConfig {

@Bean

public CorsFilter corsFilter() {

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

CorsConfiguration corsConfiguration = new CorsConfiguration();

//允许源,这里允许所有源访问,实际应用会加以限制

corsConfiguration.addAllowedOrigin(“*”);

//允许所有请求头

corsConfiguration.addAllowedHeader(“*”);

//允许所有方法

corsConfiguration.addAllowedMethod(“*”);

source.registerCorsConfiguration(“/**”, corsConfiguration);

return new CorsFilter(source);

}

}

1.3、登录service

这样一来,后端就需要生成一个 token 返回给前端,所以更改 LoginServiceImpl 里的登录方法。

@Service

public class LoginServiceImpl implements LoginService {

@Autowired

private UserMapper userMapper;

@Override

public Result login(LoginDTO loginDTO) {

if (StringUtils.isEmpty(loginDTO.getLoginName())){

return new Result(400,“账号不能为空”,“”);

}

if (StringUtils.isEmpty(loginDTO.getPassword())){

return new Result(400,“密码不能为空”,“”);

}

//通过登录名查询用户

QueryWrapper wrapper = new QueryWrapper();

wrapper.eq(“login_name”, loginDTO.getLoginName());

User uer=userMapper.selectOne(wrapper);

//比较密码

if (uer!=null&&uer.getPassword().equals(loginDTO.getPassword())){

LoginVO loginVO=new LoginVO();

loginVO.setId(uer.getId());

//这里token直接用一个uuid

//使用jwt的情况下,会生成一个jwt token,jwt token里会包含用户的信息

loginVO.setToken(UUID.randomUUID().toString());

loginVO.setUser(uer);

return new Result(200,“”,loginVO);

}

return new Result(401,“登录失败”,“”);

}

}

其中对返回的data 封装了一个VO:

/**

  • @Author: 三分恶

  • @Date: 2021/1/18

  • @Description: 登录VO

**/

public class LoginVO implements Serializable {

private Integer id;

private String token;

private User user;

//省略getter、setter

}

最后,测试一下登录接口:

image-20210118231814997

OK,没有问题。

2、前端开发


前面我们使用了后端拦截器,接下来我们尝试用前端实现相似的功能。

实现前端登录器,需要在前端判断用户的登录状态。我们可以像之前那样在组件的 data 中设置一个状态标志,但登录状态应该被视为一个全局属性,而不应该只写在某一组件中。所以我们需要引入一个新的工具——Vuex,它是专门为 Vue 开发的状态管理方案,我们可以把需要在各个组件中传递使用的变量、方法定义在这里。

2.1引入Vuex

首先在终端里使用命令 npm install vuex --save 来安装 Vuex 。

在 src 目录下新建一个文件夹 store,并在该目录下新建 index.js 文件,在该文件中引入 vue 和 vuex,代码如下:

import Vue from ‘vue’

import Vuex from ‘vuex’

Vue.use(Vuex)

接下来,在index.js 里设置我们需要的状态变量和方法。为了实现登录拦截器,我们需要一个记录token的变量量。同时为了全局使用用户信息,我们还需要一个记录用户信息的变量。还需要改变变量值的mutations。完整的代码如下:

import Vue from ‘vue’

import Vuex from ‘vuex’

Vue.use(Vuex)

export default new Vuex.Store({

state: {

token: sessionStorage.getItem(“token”),

user: JSON.parse(sessionStorage.getItem(“user”))

},

mutations: {

// set

SET_TOKENN: (state, token) => {

state.token = token

sessionStorage.setItem(“token”, token)

},

SET_USER: (state, user) => {

state.user = user

sessionStorage.setItem(“user”, JSON.stringify(user))

},

REMOVE_INFO : (state) => {

state.token = ‘’

state.user = {}

sessionStorage.setItem(“token”, ‘’)

sessionStorage.setItem(“user”, JSON.stringify(‘’))

}

},

getters: {

},

actions: {

},

modules: {

}

})

这里我们还用到了 sessionStorage,使用sessionStorage ,关掉浏览器的时候会被清除掉,和 localStorage 相比,比较利于保证实时性。

2.2、修改路由配置

为了能够区分哪些路由需要被拦截,我们在路由里添上一个元数据requireAuth来做是否需要拦截的判断:

{

path: ‘/’,

name: ‘HelloWorld’,

component: HelloWorld,

meta: {

requireAuth: true

}

},

完整的 src\router\index.js 代码如下:

import Vue from ‘vue’

import Router from ‘vue-router’

import HelloWorld from ‘@/components/HelloWorld’

//导入登录页面组件

import Login from ‘@/views/login.vue’

Vue.use(Router)

export default new Router({

routes: [

{

path: ‘/’,

name: ‘HelloWorld’,

component: HelloWorld,

meta: {

requireAuth: true

}

},

//添加登录页面路由

{

path:‘/login’,

name: ‘Login’,

component: Login

}

]

})

2.3、使用钩子函数判断是否拦截

上面我们添加了 requireAuth , 接下来就要用到它了。

钩子函数及在某些时机会被调用的函数。这里我们使用 router.beforeEach(),意思是在访问每一个路由前调用。

打开 src\main.js ,首先添加对 store 的引用

import store from ‘./store’

并修改vue对象里的内容,使 store 能全局使用:

new Vue({

el: ‘#app’,

router,

// 注意这里

store,

components: { App },

template: ‘’

})

解下来,我们写beforeEach() 函数,逻辑很简单,判断是否需要登录,如果是,判断 store中是否存有token ,是则放行,否则跳转到登录页。

//钩子函数,访问路由前调用

router.beforeEach((to, from, next) => {

//路由需要认证

if (to.meta.requireAuth) {

//判断store里是否有token

if (store.state.token) {

next()

} else {

next({

path: ‘login’,

query: { redirect: to.fullPath }

})

}

} else {

next()

}

}

)

完整的 main.js 代码如下:

// The Vue build version to load with the import command

// (runtime-only or standalone) has been set in webpack.base.conf with an alias.

import Vue from ‘vue’

import App from ‘./App’

import router from ‘./router’

//引入ElementUI

import ElementUI from ‘element-ui’

import ‘element-ui/lib/theme-chalk/index.css’

import store from ‘./store’

var axios = require(‘axios’)

// 全局注册,之后可在其他组件中通过 this.$axios 发送数据

Vue.prototype.$axios = axios

// 设置反向代理,前端请求默认发送到 http://localhost:8888/api

axios.defaults.baseURL = ‘http://localhost:8088/api’

Vue.config.productionTip = false

/* eslint-disable no-new */

Vue.use(ElementUI)

//钩子函数,访问路由前调用

router.beforeEach((to, from, next) => {

//路由需要认证

if (to.meta.requireAuth) {

//判断store里是否有token

if (store.state.token) {

next()

} else {

next({

path: ‘login’,

query: { redirect: to.fullPath }

})

}

} else {

next()

}

}

)

new Vue({

el: ‘#app’,

router,

// 注意这里

store,

components: { App },

template: ‘’

})

2.4、请求封装

我们前面写的后端拦截器,对请求进行了拦截,要求请求头里携带token,这个怎么处理呢?

答案是封装axios

在 src 目录下新建目录 utils ,在uitls 目录下新建文件 request.js 。

首先导入 axiosstore:

import axios from ‘axios’

import store from ‘@/store’

接下来在请求拦截器中,给请求头添加 token :

// request 请求拦截

service.interceptors.request.use(

config => {

if (store.state.token) {

config.headers[‘token’] = window.sessionStorage.getItem(“token”)

}

return config

},

error => {

// do something with request error

console.log(error) // for debug

return Promise.reject(error)

}

)

完整的request.js:

import axios from ‘axios’

import store from ‘@/store’

//const baseURL=“localhost:8088/api”

//创建axios实例

const service = axios.create({

baseURL: process.env.BASE_API, // api的base_url

})

// request 请求拦截

service.interceptors.request.use(

config => {

if (store.getters.getToken) {

config.headers[‘token’] = window.sessionStorage.getItem(“token”)

}

return config

},

error => {

// do something with request error

console.log(error) // for debug

return Promise.reject(error)

}

)

//response响应拦截

axios.interceptors.response.use(response => {

let res = response.data;

console.log(res)

if (res.code === 200) {

return response

} else {

return Promise.reject(response.data.msg)

}

},

error => {

console.log(error)

if (error.response.data) {

error.message = error.response.data.msg

}

if (error.response.status === 401) {

router.push(“/login”)

}

return Promise.reject(error)

}

)

export default service

注意创建axios实例里用到了 baseUrl ,在 config\dev.env.js 里修改配置:

module.exports = merge(prodEnv, {

NODE_ENV: ‘“development”’,

BASE_API: ‘“http://localhost:8088/api”’,

})

这样一封装,我们就不用每个请求都手动来塞 token,或者来做一些统一的异常处理,一劳永逸。 而且我们的 api 可以根据 env 环境变量动态切换。

2.5、封装api

request.js 既然已经封装了,那么接下来就要开始用它。

我们可以像上面的 axios 添加到 main.js 中,这样就能被全局调用。但是有更好的用法。

一般项目中,viess 下放的是我们各个业务模块的视图,对应这些业务模块,我们创建对应的 api 来封装对后台的请求,这样即使业务模块很多,但关系仍然是比较清晰的。

在 src 下新建 api 文件夹,在 api 文件夹下新建 user.js,在user.js 中我们封装了登录的后台请求:

import request from ‘@/utils/request’

export function userLogin(data) {

return request({

url: ‘/login’,

method: ‘post’,

data

})

}

当然,事实上登录用 request.js 不合适,因为request.js 拦截了token,但登录就是为了获取token——所以😅凑合着看吧,谁叫现在就这一个接口呢。

2.6、login.vue

之前的登录组件中,我们只是判断后端返回的状态码,如果是 200,就重定向到首页。在经过前面的配置后,我们需要修改一下登录逻辑,以最终实现登录拦截。

修改后的逻辑如下:

1.点击登录按钮,向后端发送数据

2.受到后端返回的成功代码时,触发 store 中的 mutation ,存储token 和user,

3.获取登录前页面的路径并跳转,如果该路径不存在,则跳转到首页

修改后的 login() 方法如下:

login() {

var _this = this;

userLogin({

loginName: this.loginForm.loginName,

password: this.loginForm.password,

}).then((resp) => {

let code=resp.data.code;

if(code===200){

let data=resp.data.data;

let token=data.token;

let user=data.user;

//存储token

_this.$store.commit(‘SET_TOKENN’, token);

//存储user,优雅一点的做法是token和user分开获取

_this.$store.commit(‘SET_USER’, user);

console.log(_this.$store.state.token);

var path = this.$route.query.redirect

this.$router.replace({path: path === ‘/’ || path === undefined ? ‘/’ : path})

}

});

完整的login.vue:

<el-input

type=“text”

v-model=“loginForm.loginName”

auto-complete=“off”

placeholder=“账号”

<el-input

type=“password”

v-model=“loginForm.password”

auto-complete=“off”

placeholder=“密码”

<el-button

type=“primary”

style=“width: 100%; border: none”

@click=“login”

登录</el-button

2.7、HelloWorld.vue

大家应该还记得,到目前为止,我们 的 / 路径还是指向 HelloWorld.vue 这个组件,为了演示 vuex 状态的全局使用,我们做一些更改,添加一个生命周期的钩子函数,来获取 store中存储的用户名:

computed: {

userName() {

return this.$store.state.user.userName

}

}

完整的 HelloWorld.vue

{{userName}}

我们看一下修改之后的整体效果:

访问首页会自动跳转到登录页,登录成功之后,会记录登录状态。

F12 打开谷歌开发者工具:

  • 打开 Application ,在 Session Storage 中看到我们存储的信息

image-20210123000856893

  • 打开vue 开发工具,在 Vuex 中也能看到我们 store中的数据

image-20210123001144379

  • 再次登录,打开Network,可以发现异步式请求请求头里已经添加了 token

image-20210123103330123

再次说一下,这里偷了懒,登录用封装的公共请求方法是不合理的,毕竟登录就是为了获取token,request.js又对token进行了拦截,所以我怼我自己😂 比较好的做法可以参考 vue-element-admin ,在 store 中写 action 用来登录。

五、用户管理功能

====================================================================

上面我们已经写了一个简单的登录功能,通过这个功能,基本可以对SpringBoot+Vue前后端分离开发有有一个初步了解,在实际工作中,一般的工作都是基于基本框架已经成型的项目,登录、鉴权、动态路由、请求封装这些基础功能可能都已经成型。所以后端的日常工作就是写接口写业务 ,前端的日常工作就是 调接口写界面,通过接下来的用户管理功能,我们能熟悉这些日常的开发。

1、后端开发


后端开发,crud就完了。

1.1、自定义分页查询

按照官方文档,来进行MP的分页。

1.1.1、分页配置

首先需要对分页进行配置,创建分页配置类

/**

  • @Author 三分恶

  • @Date 2021/1/23

  • @Description MP分页设置

*/

@Configuration

@MapperScan(“cn.fighter3.mapper..mapper”)

public class MybatisPlusConfig {

@Bean

public PaginationInterceptor paginationInterceptor() {

PaginationInterceptor paginationInterceptor = new PaginationInterceptor();

// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false

// paginationInterceptor.setOverflow(false);

// 设置最大单页限制数量,默认 500 条,-1 不受限制

// paginationInterceptor.setLimit(500);

// 开启 count 的 join 优化,只针对部分 left join

paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));

return paginationInterceptor;

}

}

1.1.2、自定义sql

作为Mybatis的增强工具,MP自然是支持自定义sql的。其实在MP中,单表操作基本上是不用自己写sql。这里只是为了演示MP的自定义sql,毕竟在实际应用中,批量操作、多表操作还是更适合自定义sql实现。

  • 修改pom.xml,在 中添加:

src/main/java

**/*.xml

true

src/main/resources

  • 配置文件:在application.properties中添加mapper扫描路径及实体类别名包

mybatis-plus

mybatis-plus.mapper-locations=classpath:cn/fighter3/mapper/*.xml

mybatis-plus.type-aliases-package=cn.fighter3.entity

  • 在UserMapper.java 中定义分页查询的方法

IPage selectUserPage(Page page,String keyword);

  • 在UserMapper.java 同级目录下新建 UserMapper.xml文件
<?xml version="1.0" encoding="UTF-8"?>

select * from user

or login_name like CONCAT(‘%’,#{keyword},‘%’)

or user_name like CONCAT(‘%’,#{keyword},‘%’)

or email like CONCAT(‘%’,#{keyword},‘%’)

or address like CONCAT(‘%’,#{keyword},‘%’)

这个查询也比较简单,根据关键字查询用户。

OK,我们的自定义分页查询就完成了,可以写个单元测试测一下。

1.2、控制层

新建UserControler,里面也没什么东西,增删改查的接口:

/**

  • @Author 三分恶

  • @Date 2021/1/23

  • @Description 用户管理

*/

@RestController

public class UserController {

@Autowired

private UserService userService;

/**

  • 分页查询

  • @param queryDTO

  • @return

*/

@PostMapping(“/api/user/list”)

public Result userList(@RequestBody QueryDTO queryDTO){

return new Result(200,“”,userService.selectUserPage(queryDTO));

}

/**

  • 添加

  • @param user

  • @return

*/

@PostMapping(“/api/user/add”)

public Result addUser(@RequestBody User user){

return new Result(200,“”,userService.addUser(user));

}

/**

  • 更新

  • @param user

  • @return

*/

@PostMapping(“/api/user/update”)

public Result updateUser(@RequestBody User user){

return new Result(200,“”,userService.updateUser(user));

}

/**

  • 删除

  • @param id

  • @return

*/

@PostMapping(“/api/user/delete”)

public Result deleteUser(Integer id){

return new Result(200,“”,userService.deleteUser(id));

}

/**

  • 批量删除

  • @param ids

  • @return

*/

@PostMapping(“/api/user/delete/batch”)

public Result batchDeleteUser(@RequestBody List ids){

userService.batchDelete(ids);

return new Result(200,“”,“”);

}

}

这里写的也比较简单,直接调用服务层的方法。

1.3、服务层

接口这里就不再贴出了,实现类如下:

/**

  • @Author 三分恶

  • @Date 2021/1/23

  • @Description

*/

@Service

public class UserServiceImpl implements UserService {

@Autowired

private UserMapper userMapper;

/**

  • 分页查询

**/

@Override

public IPage selectUserPage(QueryDTO queryDTO) {

Page page=new Page<>(queryDTO.getPageNo(),queryDTO.getPageSize());

return userMapper.selectUserPage(page,queryDTO.getKeyword());

}

@Override

public Integer addUser(User user) {

return userMapper.insert(user);

}

@Override

public Integer updateUser(User user) {

return userMapper.updateById(user);

}

@Override

public Integer deleteUser(Integer id) {

return userMapper.deleteById(id);

}

@Override

public void batchDelete(List ids) {

userMapper.deleteBatchIds(ids);

}

}

这里也比较简单,也没什么业务逻辑。

实际上,业务层至少也会做一些参数校验的工作——我见过有的系统,只是在客户端进行了参数校验,实际上,服务端参数校验是必需的(如果不做,会被怼😔),因为客户端校验相比较服务端校验是不可靠的。

在分页查询 public IPage<User> selectUserPage(QueryDTO queryDTO) 里用了一个业务对象,这种写法,也可以用一些参数校验的插件。

1.4、业务实体

上面用到了一个业务实体对象,创建一个 业务实体类QueryDTO ,定义了一些参数,这个类主要用于前端向后端传输数据,可以可以使用一些参数校验插件添加参数校验规则。

/**

  • @Author 三分恶

  • @Date 2021/1/23

  • @Description 查询业务实体

  • 这里仅仅定义了三个参数,在实际应用中可以定义多个参数

*/

public class QueryDTO {

private Integer pageNo; //页码

private Integer pageSize; //页面大小

private String keyword; //关键字

//省略getter、setter

}

简单测一下,后端👌

image-20210126172536248

2、前端开发


2.1、首页

在前面,登录之后,跳转到HelloWorld,还是比较简陋的。本来想直接跳到用户管理的视图,觉得不太好看,所以还是写了一个首页,当然这一部分不是重点。

见过一些后台管理系统的都知道,后台管理系统大概都是像下面的布局:

后台布局

在ElementUI中提供了这样的布局组件Container 布局容器:

image-20210126173415562

大家都知道根组件是 App.vue ,当然在App.vue中写整体布局是不合适的,因为还有登录页面,所以在 views 下新建 home.vue,采用Container 布局容器来进行布局,使用NavMenu 导航菜单来创建侧边栏。

当然,比较好的做法是home.vue里不写什么内容,将顶部和侧边栏都抽出来作为子页面(组件)。

Just A Demo

<el-avatar

icon=“el-icon-user-solid”

style=“color: #222; float: right; padding: 20px”

{{ this.$store.state.user.userName }}</el-avatar

<el-menu

:default-active=“$route.path”

router

text-color=“black”

active-text-color=“red”

<el-menu-item

v-for=“(item, i) in navList”

:key=“i”

:index=“item.name”

{{ item.title }}

注意 <el-main> 用了路由占位符 <router-view></router-view> ,在路由src\router\index.js里进行配置,就可以加载我们的子路由了:

{

path: ‘/’,

name: ‘Default’,

redirect: ‘/home’,

component: Home

},

{

path: ‘/home’,

name: ‘Home’,

component: Home,

meta: {

requireAuth: true

},

redirect: ‘/index’,

children:[

{

path:‘/index’,

name:‘Index’,

component:() => import(‘@/views/home/index’),

meta:{

requireAuth:true

}

},

}

]

},

首页本来不想放什么东西,后来想想,还是放了点大家爱看的——没别的意思,快过年了,各位姐夫过年好。🏮😀

image-20210126174723686

图片来自冰冰微博,见水印。

2.2、用户列表

views下新建 user 目录,在 user 目录下新建 index.vue ,然后添加为home的子路由:

{

path: ‘/home’,

name: ‘Home’,

component: Home,

meta: {

requireAuth: true

},

redirect: ‘/index’,

children:[

{

path:‘/index’,

name:‘Index’,

component:() => import(‘@/views/home/index’),

meta:{

requireAuth:true

}

},

{

path:‘/user’,

name:‘User’,

component:()=>import(‘@/views/user/index’),

meta:{

requireAuth:true

}

}

]

},

接下来开始用户列表功能的编写。

  • 首先封装一下api,在user.js中添加调用分页查询接口的api

//获取用户列表

export function userList(data) {

return request({

url: ‘/user/list’,

method: ‘post’,

data

})

}

  • user/index.vue 中导入userList

import { userList} from “@/api/user”;

  • 为了在界面初始化的时候加载用户列表,使用了生命周期钩子来调用接口获取用户列表,代码直接一锅炖了

export default {

data() {

return {

userList: [], // 用户列表

total: 0, // 用户总数

// 获取用户列表的参数对象

queryInfo: {

keyword: “”, // 查询参数

pageNo: 1, // 当前页码

pageSize: 5, // 每页显示条数

},

}

created() { // 生命周期函数

this.getUserList()

},

methods: {

getUserList() {

userList(this.queryInfo)

.then((res) => {

if (res.data.code === 200) {

//用户列表

this.userList = res.data.data.records;

this.total = res.data.data.total;

} else {

this.$message.error(res.data.message);

}

})

.catch((err) => {

console.log(err);

});

},

}

  • 取到的数据,我们用一个表格组件来进行绑定

<el-table

:data=“userList”

border

stripe

效果如下,点击用户管理:

image-20210126184434700

2.3、分页

在上面的图里,我们看到了在最下面有分页栏,我们接下来看看分页栏的实现。

我们这里使用了 Pagination 分页组件:

image-20210126184833582

<el-pagination

@size-change=“handleSizeChange”

@current-change=“handleCurrentChange”

:current-page=“queryInfo.pageNo”

:page-sizes=“[1, 2, 5, 10]”

:page-size=“queryInfo.pageSize”

layout=“total, sizes, prev, pager, next, jumper”

:total=“total”

两个监听事件:

// 监听 pageSize 改变的事件

handleSizeChange(newSize) {

// console.log(newSize)

this.queryInfo.pageSize = newSize;

// 重新发起请求用户列表

this.getUserList();

},

// 监听 当前页码值 改变的事件

handleCurrentChange(newPage) {

// console.log(newPage)

this.queryInfo.pageNo = newPage;

// 重新发起请求用户列表

this.getUserList();

},

2.4、检索用户

搜索框已经绑定了queryInfo.keyword,只需要给顶部的搜索区域添加按钮点击和清空事件——重新获取用户列表:

<el-input

placeholder=“请输入内容”

v-model=“queryInfo.keyword”

clearable

@clear=“getUserList”

<el-button

slot=“append”

icon=“el-icon-search”

@click=“getUserList”

效果如下:

image-20210126185429397

2.5、添加用户

  • 还是先写api,导入后面就略过了

//添加用户

export function userAdd(data) {

return request({

url: ‘/user/add’,

method: ‘post’,

data

})

}

  • 添加用户我们用到了两个组件 Dialog 对话框组件和 Form 表单组件。

<el-dialog

title=“添加用户”

:visible.sync=“addDialogVisible”

width=“30%”

@close=“addDialogClosed”

<el-button @click=“addDialogVisible = false”>取 消

<el-button type=“primary” @click=“addUser”>确 定

  • 使用 addDialogVisible 控制对话框可见性,使用userForm 绑定修改用户表单:

addDialogVisible: false, // 控制添加用户对话框是否显示

userForm: {

//用户

loginName: “”,

userName: “”,

password: “”,

sex: “”,

email: “”,

address: “”,

},

  • 两个函数,addUser 添加用户,addDialogClosed 在对话框关闭时清空表单

//添加用户

addUser() {

userAdd(this.userForm)

.then((res) => {

if (res.data.code === 200) {

this.addDialogVisible = false;

this.getUserList();

this.$message({

message: “添加用户成功”,

type: “success”,

});

} else {

this.$message.error(“添加用户失败”);

}

})

.catch((err) => {

this.$message.error(“添加用户异常”);

console.log(err);

});

},

// 监听 添加用户对话框的关闭事件

addDialogClosed() {

// 表单内容重置为空

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

核心竞争力,怎么才能提高呢?

成年人想要改变生活,逆转状态?那就开始学习吧~

万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。

为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。

前端面试题汇总

JavaScript

性能

linux

前端资料汇总

完整版PDF资料免费分享,只需你点赞支持,动动手指点击此处就可免费领取了

前端工程师岗位缺口一直很大,符合岗位要求的人越来越少,所以学习前端的小伙伴要注意了,一定要把技能学到扎实,做有含金量的项目,这样在找工作的时候无论遇到什么情况,问题都不会大。

_convert/0d294435c46a25f502759a8ea31fda57.png)

2.5、添加用户

  • 还是先写api,导入后面就略过了

//添加用户

export function userAdd(data) {

return request({

url: ‘/user/add’,

method: ‘post’,

data

})

}

  • 添加用户我们用到了两个组件 Dialog 对话框组件和 Form 表单组件。

<el-dialog

title=“添加用户”

:visible.sync=“addDialogVisible”

width=“30%”

@close=“addDialogClosed”

<el-button @click=“addDialogVisible = false”>取 消

<el-button type=“primary” @click=“addUser”>确 定

  • 使用 addDialogVisible 控制对话框可见性,使用userForm 绑定修改用户表单:

addDialogVisible: false, // 控制添加用户对话框是否显示

userForm: {

//用户

loginName: “”,

userName: “”,

password: “”,

sex: “”,

email: “”,

address: “”,

},

  • 两个函数,addUser 添加用户,addDialogClosed 在对话框关闭时清空表单

//添加用户

addUser() {

userAdd(this.userForm)

.then((res) => {

if (res.data.code === 200) {

this.addDialogVisible = false;

this.getUserList();

this.$message({

message: “添加用户成功”,

type: “success”,

});

} else {

this.$message.error(“添加用户失败”);

}

})

.catch((err) => {

this.$message.error(“添加用户异常”);

console.log(err);

});

},

// 监听 添加用户对话框的关闭事件

addDialogClosed() {

// 表单内容重置为空

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-ORbQXkwa-1712258890264)]

[外链图片转存中…(img-IHTxdVyj-1712258890264)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

[外链图片转存中…(img-oYhQnQz7-1712258890264)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

核心竞争力,怎么才能提高呢?

成年人想要改变生活,逆转状态?那就开始学习吧~

万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。

为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。

前端面试题汇总

JavaScript

性能

linux

前端资料汇总

完整版PDF资料免费分享,只需你点赞支持,动动手指点击此处就可免费领取了

前端工程师岗位缺口一直很大,符合岗位要求的人越来越少,所以学习前端的小伙伴要注意了,一定要把技能学到扎实,做有含金量的项目,这样在找工作的时候无论遇到什么情况,问题都不会大。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值