后端实现 环境要求:
新建SpringBoot项目 打开Idea,FIle -> New -> Project -> Spring Initializr。设置项目名,SDK等,点击下一步,选择Web -> Spring Web,点击Finish。
)
SpringBoot项目大概分为四层:
(1)DAO层:包括XxxMapper.java(数据库访问接口类),XxxMapper.xml(数据库链接实现);(有人喜欢命名为Dao,有人喜欢用Mapper)
(2)Bean层:model层,映射实体类。存放从数据库中取出的数据的对象称为Model Object(MO);
(3)Service层:也叫服务层,业务层,包括XxxService.java(业务接口类),XxxServiceImpl.java(业务实现类);(可以在service文件夹下新建impl文件放业务实现类,也可以把业务实现类单独放一个文件夹下,更清晰)
(4)Controller层:与前端交互。
依照上面四层,创建目录结构如下:
建立并连接数据库 (略)安装Mysql,建立数据库springboot,创建表t_user(含三个字段:id、username、password),向表中添加内容。
打开IDE右侧的Database标签,连接数据库
修改配置文件application.properties
springboot项目的配置文件支持properties、yaml、yml三种格式。SpringBoot的很多配置都有默认值,如果想修改默认配置,在application.properties(yml、yaml) 文件中配置.
将application.properties改为application.yml,并添加如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 spring: datasource: url: jdbc:mysql:///springboot username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.test.bean
根据数据库表格,生成MO。 下载安装Mybatisx插件:File -> setting -> Plugins,搜索MyBatisx。
右键表格,点击MybatisX-Generator。
生成的代码使用了一些库,打开项目根目录下的的pom.xml文件,(在 <dependencies></dependencies
>标签内)添加相关依赖:
mybatis:持久层框架,支持自定义SQL、存储过程以及高级映射。就是你写了sql可以直接转对象,不用你手工转,而且支持一对一,一对多的关系表的ORM
Lombok:可以为类中的字段自动生成get、set 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > 3.0.0</version > </dependency > <dependency > <groupId > com.mysql</groupId > <artifactId > mysql-connector-j</artifactId > <scope > runtime</scope > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-jdbc</artifactId > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > </dependency > <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-generator</artifactId > <version > 3.4.1</version > </dependency >
参考:
MybatisX快速开发插件 | 官方文档
满满的MybatisX干货哦~:点击小鸟实现跳转
MyBatisX插件介绍
Mybatisx会生成几个文件,修改代码,通过账户(username)密码(password),从数据库中获取id:
1 2 3 4 5 6 7 8 9 10 11 package com.example.demo.mapper;import com.example.demo.bean.TUser;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import org.apache.ibatis.annotations.Mapper;@Mapper public interface TUserMapper extends BaseMapper <TUser > { TUser getInfo (String name,String password) ; }
1 2 3 4 5 6 7 8 9 10 package com.example.demo.service;import com.example.demo.bean.TUser;import com.baomidou.mybatisplus.extension.service.IService;public interface TUserService extends IService <TUser > { TUser loginIn (String name,String password) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ackage com.example.demo.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.example.demo.bean.TUser;import com.example.demo.service.TUserService;import com.example.demo.mapper.TUserMapper;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Service public class TUserServiceImpl extends ServiceImpl <TUserMapper , TUser > implements TUserService { @Autowired private TUserMapper userMapper; @Override public TUser loginIn (String name, String password) { return userMapper.getInfo(name,password); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.example.demo.mapper.TUserMapper" > <resultMap id ="BaseResultMap" type ="com.example.demo.bean.TUser" > <id property ="id" column ="id" jdbcType ="INTEGER" /> <result property ="username" column ="username" jdbcType ="VARCHAR" /> <result property ="password" column ="password" jdbcType ="VARCHAR" /> </resultMap > <sql id ="Base_Column_List" > id,username,password </sql > <select id ="getInfo" parameterType ="String" resultType ="com.example.demo.bean.TUser" > SELECT * FROM t_user WHERE username = #{name} AND password = #{password} </select > </mapper >
测试是否能成功读取数据库信息 在/src/test/java/com.example.test/DemoApplicationTests中进行测试,注意代码中loginIn参数,修改为自己的表中含有的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.example.test;import com.example.test.bean.UserBean;import com.example.test.mapper.UserMapper;import com.example.test.service.UserService;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTest class DemoApplicationTests { @Autowired UserService userService; @Test public void contextLoads () { UserBean userBean = userService.loginIn("zhangsan" ,"123" ); System.out.println("该用户ID为:" ); System.out.println(userBean.getId()); } }
前端代码解析 – 登录篇
vue-element-admin
Vscode
Vscode插件
Live Server: 更改代码会自动刷新浏览器
Vetur
Vue Language Features
vue-helper
”首先我们不管什么权限,来实现最基础的登录功能:随便找一个空白页面撸上两个input的框,再放置一个登录按钮。我们将登录按钮上绑上click事件,点击登录之后向服务端提交账号和密码进行验证。如果你觉得还要写的更加完美点,你可以在向服务端提交之前对账号和密码做一次简单的校验。“
找到登录按钮相应``m `代码位置:在views/login/index.vue中,找到其绑定的click事件:
该事件会调用store/modules/user.js中的login方法,该方法又会调用api/user.js中的login方法:
打开api/user.js后,可以看到url,这就是登陆时前端的请求url,修改url内容:
其中的 request
的实现来自 /src/utils/request.js
,此文件已封装了axios。
请求拦截器 /src/utils/request.js
文件中含有请求拦截器(限制未登录状态下对核心功能页面的访问)和响应拦截器。
一个简单请求拦截器的逻辑如下:
用户访问 URL,检测是否为登录页面,如果是登录页面则不拦截
如果用户访问的不是登录页面,检测用户是否已登录,如果未登录则跳转到登录页面import axios from ‘axios’
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 import { MessageBox, Message } from 'element-ui' import store from '@/store' import { getToken } from '@/utils/auth' const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 5000 }) service.interceptors.request.use( config => { if (store.getters.token) { config.headers['token' ] = getToken() } return config }, error => { console .log(error) return Promise .reject(error) } ) service.interceptors.response.use( response => { const res = response.data return res }, error => { console .log('err' + error) Message({ message: error.message, type: 'error' , duration: 5 * 1000 }) return Promise .reject(error) } ) export default service
注意文件中响应拦截部分,判定的响应码是20000,因此需要后端返回的成功响应码为20000。如果不匹配,则前端无法登录,因为响应被拦截了。
保存数据 登录成功后,服务端会返回一个 token(该token的是一个能唯一标示用户身份的一个key),之后我们将token存储在本地cookie之中,这样下次打开页面或者刷新页面的时候能记住用户的登录状态,不用再去登录页面重新登录了。
ps:为了保证安全性,我司现在后台所有token有效期(Expires/Max-Age)都是Session,就是当浏览器关闭了就丢失了。重新打开游览器都需要重新登录验证,后端也会在每周固定一个时间点重新刷新token,让后台用户全部重新登录一次,确保后台用户不会因为电脑遗失或者其它原因被人随意使用账号。
实现前端登录器,需要在前端判断用户的登录状态。登录状态应该被视为一个全局属性,而不应该只写在某一组件中。项目使用了Vuex,它可以把需要在各个组件中传递使用的变量、方法定义在这里。之前我一直没有使用它,所以在不同组件传值的问题上十分头疼,要写很多多余的代码来调用不同组件的值,所以推荐大家从一开始就去熟悉这种管理方式。
这里我个人建议不要为了用 vuex 而用 vuex。就拿我司的后台项目来说,它虽然比较庞大,几十个业务模块,几十种权限,但业务之间的耦合度是很低的,文章模块和评论模块几乎是俩个独立的东西,所以根本没有必要使用 vuex 来存储data,每个页面里存放自己的 data 就行。当然有些数据还是需要用 vuex 来统一管理的,如登录token,用户信息,或者是一些全局个人偏好设置等,还是用vuex管理更加的方便,具体当然还是要结合自己的业务场景的。总之还是那句话,不要为了用vuex而用vuex!
。
获取用户信息 permission.js中有个钩子函数 router.beforeEach()
,会在访问每个路由前拦截,判断是否已获得token,没有,就转向登录,如果有 token, 就会把这个 token 返给后端去拉取 user_info去获取用户的基本信息。
就如前面所说的,我只在本地存储了一个用户的 token,并没有存储别的用户信息(如用户权限,用户名,用户头像等)。有些人会问为什么不把一些其它的用户信息也存一下?主要出于如下的考虑:
假设我把用户权限和用户名也存在了本地,但我这时候用另一台电脑登录修改了自己的用户名,之后再用这台存有之前用户信息的电脑登录,它默认会去读取本地 cookie 中的名字,并不会去拉去新的用户信息。
这样能保证用户信息是最新的。 当然如果是做了单点登录的功能的话,用户信息存储在本地也是可以的。当你一台电脑登录时,另一台会被提下线,所以总会重新登录获取最新的内容。
从代码层面我建议还是把 login和get_user_info两件事分开比较好,在这个后端全面微服务的年代,后端同学也想写优雅的代码~
跨域 当进行网络请求时,接口路径会自动和baseURL进行拼接。接下来修改它baseURL为后端的,让前端能够去访问后端的API:
查看后端的端口:
添加跨域支持:打开webpack配置文档 ,将代码复制到根目录下的vue.config.js配置文件中的devServer下,更改target。
参考:
vue-element-admin | github
vue-element-admin 的使用记录(一)
前后端交互 前后端分离的意思是前后端之间通过 RESTful API 传递 JSON 数据进行交流。在开发的时候,前端用前端的服务器(Nginx),后端用后端的服务器(Tomcat),当我开发前端内容的时候,可以把前端的请求通过前端服务器转发给后端(称为 反向代理 ),这样就能实时观察结果,并且不需要知道后端怎么实现,而只需要知道接口提供的功能,两边的开发人员(两个我)就可以各司其职啦。
在后端部分提供前端请求的内容。
打开前端项目中/src/modules/user.js文件:
login方法想要获取token,getInfo方法想要获取roles, name, avatar, introduction,那我们就在后端中提供这些内容。
打开后端项目,创建utils文件夹,创建两个类R(设置token,roles等)和ResultCode(设置响应码),在controller中创建LoginController类(和前端交互)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 package com.example.demo.utils;import lombok.Data;import java.util.HashMap;import java.util.Map;@Data public class R { private Boolean success; private Integer code; private String message; private Map<String, Object> data = new HashMap<String, Object>(); private R () {} public static R ok () { R r = new R(); r.setSuccess(true ); r.setCode(ResultCode.SUCCESS); r.setMessage("成功" ); return r; } public static R error () { R r = new R(); r.setSuccess(false ); r.setCode(ResultCode.ERROR); r.setMessage("失败" ); return r; } public R success (Boolean success) { this .setSuccess(success); return this ; } public R message (String message) { this .setMessage(message); return this ; } public R code (Integer code) { this .setCode(code); return this ; } public R data (String key, Object value) { this .data.put(key, value); return this ; } public R data (Map<String, Object> map) { this .setData(map); return this ; } }
1 2 3 4 5 6 7 8 9 10 package com.example.demo.utils;public class ResultCode { public static Integer SUCCESS = 20000 ; public static Integer ERROR = 20001 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package com.example.demo.controller;import com.example.demo.service.TUserService;import com.example.demo.utils.R;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;@RestController @RequestMapping("/user") public class LoginController { @Autowired TUserService userService; @PostMapping("login") public R login () { return R.ok().data("token" ,"da-jia-wan-shang-hao" ); } @GetMapping("info") public R info () { return R.ok().data("roles" , new String[]{"admin" }).data("name" ,"admin" ).data("avatar" ,"https://image.baidu.com/search/detail?ct=503316480&z=0&ipn=d&word=%E5%B0%8F%E5%9B%BE%E7%89%87&step_word=&hs=0&pn=19&spn=0&di=7146857200093233153&pi=0&rn=1&tn=baiduimagedetail&is=0%2C0&istype=2&ie=utf-8&oe=utf-8&in=&cl=2&lm=-1&st=-1&cs=3755132173%2C1946217785&os=3112775561%2C2508984481&simid=3755132173%2C1946217785&adpicid=0&lpn=0&ln=1638&fr=&fmq=1668228130827_R&fm=&ic=&s=undefined&hd=&latest=©right=&se=&sme=&tab=0&width=&height=&face=undefined&ist=&jit=&cg=&bdtype=0&oriquery=&objurl=https%3A%2F%2Fgimg2.baidu.com%2Fimage_search%2Fsrc%3Dhttp%3A%2F%2Fi.qqkou.com%2Fi%2F2a3755132173x1946217785b26.jpg%26refer%3Dhttp%3A%2F%2Fi.qqkou.com%26app%3D2002%26size%3Df9999%2C10000%26q%3Da80%26n%3D0%26g%3D0n%26fmt%3Dauto%3Fsec%3D1670820133%26t%3D3620b9f317582311d2807e4c0eab71fb&fromurl=ippr_z2C%24qAzdH3FAzdH3Fqqh57_z%26e3Bv54AzdH3Fp57xtwg2AzdH3F8d9d8bm_z%26e3Bip4s&gsm=1e&rpstart=0&rpnum=0&islist=&querylist=&nojc=undefined&dyTabStr=MCwxLDMsNiw0LDIsNSw3LDgsOQ%3D%3D" ).data("introduction" ,"haha" ); } }
前端发出请求“/user/login”,后端接收之后进行路径匹配,调用LoginController.login方法(未实现对账号密码的验证 ),该方法返回一个类对象R,该对象中有个类型为map的字段data, 其中包含了一个pair: {"token","da-jia-wan-shang-hao"}
。
好了,启动后端项目,启动前端项目,点击登录按钮,即可跳转到首页。
遇到的错误:
role.some is not a function:需要后端返回的roles是数组,见roles.some is not a function | github issue
登录之后报错:Request failed with status code 404:见下图。和本主题无关,先不管它。