2021-07-06

springboot+vue前后端分离项目实战

目的:

这个项目也是模仿微人事,也是b站里面的项目,我写出来,就带大家一起去学习这个项目

学习的地址:https://www.bilibili.com/video/BV1HA411p7aA?t=242&p=14

下面就有我带大家去学习这个项目,不是视频说这么仔细,但是我会带大家从0基础去学习这个项目,去理解这个项目的流程和项目的需要的技术的原理,视频,相信大家去看,好多也是迷茫的,不知道这个视频里面说的什么,项目流程是什么?没有基础的看完保证可以提高好多,文章有点长,希望大家可以看完

介绍vue和构建前端的vue项目

什么是vue?
Vue.js就是一个用于搭建类似于网页版知乎这种表单项繁多,
且内容需要根据用户的操作进行修改的网页版应用。
Vue 的核心库只关注视图层
单页应用程序(SPA)
顾名思义,单页应用一般指的就是一个页面就是应用
当然也可以是一个子应用,比如说知乎的一个页面就可
以视为一个子应用。单页应用程序中一般交互处理非常
多,而且页面中的内容需要根据用户的操作动态变化。
有人可能这样问:你前面说的网页版知乎我也可以用JQuery+html写啊,为什么要用Vue.js呢?

讲到JQuery,就不得不说到JavaScript的DOM操作了。如果你用JQuery来开发一个知乎,那么你就需要用JQuery中的各种DOM操作方法去操作HTML的DOM结构了。
现在我们把一个网页应用抽象一下,那么HTML中的DOM其实就是视图,一个网页就是通过DOM的组合与嵌套,形成了最基本的视图结构,再通过CSS的修饰,在基本的视图结构上“化妆”让他们看起来更加美观。最后涉及到交互部分,就需要用到JavaScript来接受用户的交互请求,并且通过事件机制来响应用户的交互操作,并且在事件的处理函数中进行各种数据的修改,比如说修改某个DOM中的innerHTML或者innerText部分。

我们把HTML中的DOM就可以与其他的部分独立开来划分出一个层次,这个层次就叫做视图层。
我们为什么要把视图层抽取出来并且单独去关注它呢?

因为在像知乎这种页面元素非常多,结构很庞大的网页中,数据和视图如果全部混杂在一起,像传统开发一样全部混合在HTML中,那么要对它们进行处理会十分的费劲,并且如果其中有几个结构之间存在藕断丝连的关系,那么会导致代码上出现更大的问题,这什么问题呢?

你是否还记得你当初写JQuery的时候,有写过
('#xxx').parent().parent().parent()
这种代码呢?当你第一次写的时候,你觉得页面元素不多,不就是找这个元素的爸爸的爸爸的爸爸吗,我大不了在注释里面写清楚这个元素的爸爸的爸爸的爸爸不就好了。但是万一过几天之后你的项目组长或者你的产品经理突然对你做的网页提出修改要求,这个修改要求将会影响页面的结构,也就是DOM的关联与嵌套层次要发生改变,那么
(‘#xxx’).parent().parent().parent()
可能就会变成
$(‘#xxx’).parent().parent().parent().parent().parent()了。

这还不算什么,等以后产品迭代越来越快,修改越来越多,而且页面中类似的关联和嵌套DOM元素不止一个,那么修改起来将非常费劲。而且JQuery选择器查找页面元素以及DOM操作本身也是有性能损失的,可能到时候打开这个页面,会变得越来越卡,而你却无从下手。

当你在编写项目的时候遇到了这种问题,你一定会抱怨,为什么世上会有HTML这种像盗梦空间一样的需要无数div嵌套才能做出页面的语言,为什么当初学JQuery看中的是它简洁的DOM操作,现在却一点也不觉得它有多简洁,难道我学的是假的JQuery?为什么写个代码这么难,你想砸电脑,你想一键盘拍在产品狗的脑袋上,责怪他天天改需求才让你原本花清香茶清味的代码变得如此又臭又长,还有页面之间传递数据,都不方便的,还有好多重复的标签  这个用vue
就可以解决

这个时候如果你学过Vue.js,那么这些
抱怨将不复存在。
组件化开发
现在我们做单页应用,页面交互和结构十分复杂
,一个页面上就有许许多多的模块需要编写,而且往往一
个模块的代码量和工作量就非常庞大,如果还按照原先的
方法来开发,那么会累死人。而且遇到以后的产品需求变更,修改起来也非常麻烦,生怕动了其中一个div之后,其他div跟着雪崩,整
个页面全部乱套,或者由于JavaScript的事件冒泡机制,
导致修改一些内层的DOM事件处理函数之后,出现各种莫名其妙的诡异BUG。

在面向对象编程中,我们可以使用面向对象的思想将各
种模块打包成类或者把一个大的业务模块拆分成更多更
小的几个类。在面向过程编程中,我们也可以把一些大功能拆分成许多函数,然后分配给不同的人来开发。

在前端应用,我们是否也可以像编程一样把模块封装呢
?这就引入了组件化开发的思想。

Vue.js通过组件,把一个单页应用中的各种模块拆分到一
个一个单独的组件(component)中,我们只要先在父级
应用中写好各种组件标签(占坑),并且在组件标签中写
好要传入组件的参数(就像给函数传入参数一样,这个参数叫做组件的属性),然后再分别写好各种组件的实现(填坑),然后整个应用
就算做完了。

什么是node.js
Node.js 就是运行在服务端的 JavaScript。

Node.js 是一个基于Chrome JavaScript 
运行时建立的一个平台。

Node.js是一个事件驱动I/O服务端JavaScript
环境,基于Google的V8引擎,V8引擎执行Javas
cript的速度非常快,性能非常好,这个是构建vue项目必须的要安装的
node.js下载地址:http://nodejs.cn/download/
window安装教程:https://blog.csdn.net/cai454692590/article/details/86093297
测试nodejs 是否安装
在cmd中输入node -v
测试npm是否自动安装
npm -v
配置下载源—全局安装nrm
npm install nrm -g
进行
npm install -g @vue/cli3.0安装vue3
测试安装是否成功
vue -V

创建VUE项目
vue create 项目名
项目配置
选择预设
Please pick a preset: (Use arrow keys) ----使用键盘上下键选择 回车确定
default (babel, eslint) ----默认只安装babel和eslint
Manually select features 
---- 自定义手动选择插件(1,4,5,6)
Check the features needed for your project:
----空格选择,a全选,i反选 回车确定
Babel ---- 将高级版本ES转换为浏览器识别的JS语法
TypeScript---- JS的超集,提供了JS面向对象支持
Progressive Web App (PWA) Support----渐进web app支持
Router ----路由、请求所对应的地址
Vuex ---- 数据状态管理器、用于多页面传参
CSS Pre-processors ----CSS预处理,将高级CSS语法转换为浏览器识别CSS语法
Linter / Formatter ----语法检测
Unit Testing ----单元测试
E2E Testing ----测试
选择路由模式
Use history mode for router?
History模式---- yes
Hash模式---- no 路由的后方有#只刷新部分内容(建议采用)
选择CSS预处理
Pick a CSS pre-processor
Sass/SCSS (with dart-sass)
Sass/SCSS (with node-sass)
Less
Stylus
选择插件的配置存放
Where do you prefer placing config for Babel, ESLint, etc
In dedicated config files ----独立的配置文件
In package.json ----放在package.json中
是否保存预设
Save this as a preset for future projects ----是否把你选择的作为预设
N不保存
Y保存
选择Babel Router 其他选择 y全部选择n进行enter
输入npm run serve 就构建成功了

前端项目的基础配置

正向代理和反向代理 2021-07-06_第1张图片 2021-07-06_第2张图片

配置跨域 编写vue.conf.js文件
这里
前端的项目端口8080  后端接口是8081 使用请求转发进行配置
let proxyObj={}

proxyObj['/']={
    //进行匹配路径
    ws:false,
    //目标地址
    target:'http://localhost:8081',
    //发送请求头host  会被设置target
    changeOrigin: true,
    //不重写地址
    pathReWrite:{
        '^/':'/'
    }
}

module.exports={
    devServer:{
        host: 'localhost',
        port: 8080,
        proxy: proxyObj
    }
}
请求代理机制
   // 请求代理机制:
     proxyTable: {
'/api/**': {
//当你的请求是以api开头的时候,将请求转发到当前服务器的8088端口上,只是路径做一个替换,pathRewrite
         target: 'http://localhost:8088/',
        pathRewrite:{
          '^/api''/static/mock'
          //一旦你的请求是以api开头的,就转接到/static/mock这个文件夹下;这个功能是webpack-dev-server提供的     
          }
          }
   },
   
代理
首先我们要明确一个概念,所谓代理就是一个代表、一个渠道;

此时就涉及到两个角色,一个是被代理角色,一个是目标角色。
被代理角色通过这个代理访问目标角色完成一些任务的过程称为代理操作过程;如同生活中的专卖店,客人到 adidas 专卖店买了一双鞋,这个专卖店就是代理,被代理角色就是 adidas 厂家,目标角色就是用户。
简而言之,就是adidas老板找来专卖店这个代理来卖鞋子给客人这个目标角色。

安装element ui
npm i element-ui -S
安装axios
npm  install axios
配置axios请求拦截器和封装请求的工具类(api.js)
//请求拦截器  发送ajax请求
axios.interceptors.request.use(config=>{
    if (window.sessionStorage.getItem("tokenStr")){
        //jwt存在就保存在请求的头部 
        config.headers['Authorization']=window.sessionStorage.getItem("tokenStr");
    }
    //继续发送请求
    return config;
},error => {
    console.log(error)
})

//添加相应请求拦截器
axios.interceptors.response.use(success=> {
    console.log("============="+success.data.toString())
    //这里对 error 预期结果是包含具体错误信息和状态码
    //业务逻辑错误
    if (success.status&& success.status==200){
        if (success.data.code==500 || success.data.code==401 || success.data.code==403){
            Message.error({message:success.data.message});
            return;
        }
        if (success.data.message){
            Message.success({message:success.data.message});
        }
        //判断数据是不是null

    }
    return success.data;
},error => {
    //接口失败了
    console.log(error+"错误")
    if (error.response.code==504|| error.response.code==404){
        Message.error({message:'服务器被吃了'})
    }else if (error.response.code==403){
        Message.error({message:"权限不够,"})
    }else if (error.response.code==401){
        Message.error({message:'请登入'})
        router.replace("/");
        //没有登入跳转到登入页面
    }else{
        if (error.response.data.message){
            Message.error({message:error.response.data.message})
        }else {
            Message.error({message:'未知错误'})
        }
    }

    return;
})
let base='';
//传递json格式的post请求
export const postRequst=(url,params)=>{
    return axios({
        method:'post',
        url:`${base}${url}`,
        data: params
    })
}
//更新
export const putRequst=(url,params)=>{
    return axios({
            methods:'put',
            url:`${base}${url}`,
            data: params
        }
    )
}
//get
export const getRequst=(url,params)=>{
    return axios({
        method:'get',
        url:`${base}${url}`,
        data: params
    })
}
//删除
export const deleteRequst=(url,params)=>{
    return axios({
        method:'delete',
        url:`${base}${url}`,
        data: params
    })
}
在main.js进行配置
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue'
import router from './router'
import store from "./store";
import axios from "axios";
//清楚缓存
Vue.config.productionTip = false
Vue.use(ElementUI)
new Vue({
  router,
  render: h => h(App)
}).$mount('#app')
删除views下面的所有的vue创建login.vue





    .loginContainer{
        border-radius: 15px;
        background-clip: padding-box;
        margin: 180px  auto;
        width: 350px;
        padding: 15px 35px 15px 35px;
        background: #fff;
        border: 1px solid #eaeaea;
        box-shadow: 0 0 25px #cac6c6;
    }
    .loginRemenber{
        text-align: left;
        margin: 0px 0px 15px  0px;
    }
    .el-form-item__content{
        display: flex;
        align-items: center;
    }


遇到的知识点
这里
window.sessionStorage(会话存储):暂时储存,浏览器关闭之后会清除
window.localStorage (本地存储):本地储存,浏览器关闭之后依旧不会清除,只能人为删除平时储存的话建议使用sessionStorage;
   rules:{
                //进行判断表单是不是空 空的话显示message
                    username: [{required:true,message:'请输入用户名字',trigger:'blur'}],
                    password: [{required:true,message:'请输入用户密码',trigger:'blur'}],
                    code: [{required:true,message:'请输入验证码',trigger:'blur'}]
                }
  可以结合this.$refs.loginFrom.validate((valid) => {}去使用,去判断是不是表单是不是null
  记住这个是有:rules="rules" ref="loginFrom" :model="loginFrom"进行绑定表单数据
  跳转页面使用的是
    this.$router.replace('/home')
    进行跳转登入成功的路由
路由router.js
import Vue from 'vue'
import Router from 'vue-router'
import Login from './views/Login.vue'
import Home from "./views/Home.vue";
Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: '登入',
      component: Login,
      hidden:true
    },{
      path: '/home',
      name: 'home',
      component: Home,
    
    },

  ]
})

后端的配置和登入

application.yml的配置
server:
  port: 8081
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/object?serverTimezone=GMT&useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8
    username: root
    password: 123456
  hikari:
    # 连接池名
    pool-name: DateHikariCP
    # 最小空闲连接数
    minimum-idle: 5
    # 空闲连接存活最大时间,默认600000(10分钟)
    idle-timeout: 180000
    # 最大连接数,默认10
    maximum-pool-size: 10
    # 从连接池返回的连接的自动提交
    auto-commit: true
    # 连接最大存活时间,0表示永久存活,默认1800000(30分钟)
    max-lifetime: 1800000
    # 连接超时时间,默认30000(30秒)
    connection-timeout: 30000
    # 测试连接是否可用的查询语句
    connection-test-query: SELECT 1
  redis:
    #超时的时间
    timeout: 10000ms
    #服务器地址
    host: 127.0.0.1
    #服务端接口
    port: 6379
    #数据库
    database: 0
    lettuce:
      pool:
        #最大连接数
        max-active: 8
        #最大空闲连接
        max-idle: 200
        #最小空闲连接
        min-idle: 5



# Mybatis-plus配置
mybatis-plus:
  #配置Mapper映射文件
  mapper-locations: classpath:mapper/*.xml
  # 配置MyBatis数据返回类型别名(默认别名是类名)
#  type-aliases-package: com.example.server.pojo
  configuration:
    # 自动驼峰命名
    map-underscore-to-camel-case: false

## Mybatis SQL 打印(方法接口所在的包,不是Mapper.xml所在的包)
logging:
  level:
    com.example.server.mapper: debug

jwt:
  # JWT存储的请求头
  tokenHeader: Authorization
  # JWT 加解密使用的密钥
  secret: yeb-secret
  # JWT的超期限时间(60*60*24)
  expiration: 604800
  # JWT 负载中拿到开头
  tokenHead: Bearer
redis的配置类(用来保存用户的权限和菜单的)
redis的主从复制的原理:https://www.cnblogs.com/daofaziran/p/10978628.html

redis怎么跟mysql保存同步:
https://mp.weixin.qq.com/s?__biz=MzA4MTAwMzA1Mw==&mid=2247484221&idx=1&sn=ac9c7a1d8c84b478627b7ab08d1c837a&chksm=9f9ad47fa8ed5d69bc636e5b8da0bf59a5825c6a01ff2c20d095ce697aa7933d24d40327a3c0&token=990738943&lang=zh_CN#rd
redis的缓存雪崩解决方法;
https://mp.weixin.qq.com/s?__biz=MzA4MTAwMzA1Mw==&mid=2247484236&idx=1&sn=53722e0631d348c2729c791854586775&chksm=9f9ad40ea8ed5d18290980863583f5970d32c7bb80e4ed17db4bfdb20355b26ab8f97e2b3be7&token=990738943&lang=zh_CN#rd

 
      org.springframework.boot
      spring-boot-starter-data-redis
    

    
      org.apache.commons
      commons-pool2
    

万能配置
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate template=new RedisTemplate<>();
        //配置序列化模式
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=new Jackson2JsonRedisSerializer(Object.class);
        template.setConnectionFactory(redisConnectionFactory);
        ObjectMapper objectMapper=new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);        //String 序列化
        StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();        //key 采用String  序列化
        template.setKeySerializer(stringRedisSerializer);        //posh的key 的采用string 的序列化
        template.setHashKeySerializer(stringRedisSerializer);        //value 的序列化 采用json
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的序列化采用json
        template.setHashKeySerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return  template;
    }
}
Swagger的配置
依赖
  
      io.springfox
      springfox-swagger2
      2.7.0
    

    
      com.github.xiaoymin
      swagger-bootstrap-ui
      1.9.3
    

万能配置    
@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.example.server.controller"))
                .paths(PathSelectors.any())
                .build()
                .securityContexts(securityContexts())
                .securitySchemes(securitySchemes());
    }

    private List securityContexts() {
        //认证路径
        List result = new ArrayList<>();
        result.add(getContextByPath("/hello/.*"));
        return result;
    }

    private SecurityContext getContextByPath(String pathRegex) {
        return SecurityContext.builder()
                .securityReferences(defaultAuth())
                //认证路径
                .forPaths(PathSelectors.regex(pathRegex))
                .build();

    }

    private List defaultAuth() {
        List result = new ArrayList<>();
        AuthorizationScope authorizationScope = new AuthorizationScope("global""accessEveryThing");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        result.add(new SecurityReference("Authorization", authorizationScopes));
        return result;
    }


    public List securitySchemes() {
        //设置请求头信息
        List result = new ArrayList<>();
        ApiKey apiKey = new ApiKey("Authorization""Authorization""Header");
        result.add(apiKey);
        return result;
    }


    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("云E办接口文档")
                .description("云E办接口文档")
                .contact(new Contact("朱董董""http://localhost:8081/""[email protected]"))
                .version("1.0")
                .build();
    }
}
jwt的工具类(这个用来用户登入时候的验证)
 
      io.jsonwebtoken
      jjwt
      0.9.1
    

@Component
public class JwtTokerUtil {
    private final String CLAIM_KEY_USERNAME = "sub";
    private final String CLAIM_KEY_CREATED = "created";
    @Value("${jwt.secret}")
    private String secret;
    @Value("${jwt.expiration}")
    private Long expiration;
    /*
    jwt:
  # JWT存储的请求头
  tokenHeader: Authorization
  # JWT 加解密使用的密钥
  secret: yeb-secret
  # JWT的超期限时间(60*60*24)
  expiration: 604800
  # JWT 负载中拿到开头
  tokenHead: Bearer
     */


    /**
     * 根据用户信息生成token
     *
     * @param userDetails
     * @return
     */
    public String generateToken(UserDetails userDetails) {
        Map claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }


    /**
     * 根据token获取用户名
     *
     * @param token
     * @return
     */
    public String getUserNameFormToken(String token) {
        Claims claims = getClaimsFormToken(token);
        return claims.getSubject();
    }


    /**
     * 判断token是否有效
     *
     * @param token
     * @return
     */
    public boolean validateToken(String token, UserDetails userDetails) {
        String username = getUserNameFormToken(token);
        return username.equals(userDetails.getUsername()) && !isTokenExpiration(token);
    }

    /**
     * 判断token是否可以刷新
     *
     * @param token
     * @return
     */
    public boolean canRefresh(String token) {
        return !isTokenExpiration(token);
    }


    /**
     * 刷新token
     *
     * @param token
     * @return
     */
    public String refreshToken(String token) {
        Claims claims = getClaimsFormToken(token);
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }


    /**
     * 判断token是否失效
     *
     * @param token
     * @return
     */
    private boolean isTokenExpiration(String token) {
        Date expDate = getExpirationFromToken(token);
        return expDate.before(new Date());
    }

    /**
     * 从token中获取失效时间
     *
     * @param token
     * @return
     */
    private Date getExpirationFromToken(String token) {
        Claims claims = getClaimsFormToken(token);
        return claims.getExpiration();
    }


    /**
     * 根据token获取荷载
     *
     * @param token
     * @return
     */
    private Claims getClaimsFormToken(String token) {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }


    /**
     * 根据荷载生成token
     *
     * @param claims
     * @return
     */
    private String generateToken(Map claims) {
        return Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, secret)
                .setExpiration(generateExpiration())
                .compact();
    }

    /**
     * 生成失效时间
     *
     * @return
     */
    private Date generateExpiration() {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

}
什么是jwt?
Json web token (JWT), 是为了在网络应用环境间
传递声明而执行的一种基于JSON的开放标准((RFC 7519).
该token被设计为紧凑且安全的,特别适用于分布式站点
的单点登录(SSO)场景。JWT的声明一般被用来在
身份提供者和服务提供者间传递被认证的用户身份信
息,以便于从资源服务器获取资源,也可以增加一些
额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
jwt的构成:
第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).
header
jwt的头部承载两部分信息:

声明类型,这里是jwt
声明加密的算法 通常直接使用 HMAC SHA256
playload
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,
这些有效信息包含三个部分
标准中注册的声明
公共的声明
私有的声明
signature
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

header (base64后的)
payload (base64后的)
secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

sql

网盘地址:
链接:https://pan.baidu.com/s/1voMgGiBqIa3cpVaUCTe5DQ 
提取码:a4xq

使用mybait-puls自动生成增删改查

public class CodeGenartor {

    /**
     * 


     * 读取控制台内容
     * 


     */
    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入" + tip + ":");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotEmpty(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }

    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/yeb-generator/src/main/java");
        //包结构
//        gc.setOutputDir("D:\\test");
        gc.setAuthor("关注公众号:MarkerHub");
        gc.setOpen(false);
        gc.setSwagger2(true);
        gc.setBaseResultMap(true);
        gc.setBaseColumnList(true);
//        实体属性 Swagger2 注解
//        gc.setServiceName("%sService");
        mpg.setGlobalConfig(gc);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/object?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=UTC");
        // dsc.setSchemaName("public");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("123456");
        mpg.setDataSource(dsc);

        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent("com.example").setEntity("pojo").setMapper("service").setService("service.imp").
                setController("controller");
        mpg.setPackageInfo(pc);

        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };

        // 如果模板引擎是 freemarker
        String templatePath = "/templates/mapper.xml.ftl";
        // 如果模板引擎是 velocity
        // String templatePath = "/templates/mapper.xml.vm";

        // 自定义输出配置
        List focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
                return projectPath + "/yeb-generator/src/main/resources/mapper/"
                //包结构
                        + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
            }
        });

        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);

        // 配置模板
        TemplateConfig templateConfig = new TemplateConfig();

        templateConfig.setXml(null);
        mpg.setTemplate(templateConfig);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.no_change);
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
        strategy.setControllerMappingHyphenStyle(true);
        strategy.setTablePrefix("t_");
        mpg.setStrategy(strategy);
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
    }
    }
生成以后 移入相应的包下面
验证码功能
    
      com.github.penggle
      kaptcha
      2.3.2
    

    @Configuration
public class CaptchaConfig {
    /*
    验证码配置
     */
    @Bean
    public DefaultKaptcha getDefaultKaptcha() {
        //验证码生成器
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        //配置
        Properties properties = new Properties();
        //是否有边框
        properties.setProperty("kaptcha.border""yes");
        //设置边框颜色
        properties.setProperty("kaptcha.border.color""105,179,90");
        //边框粗细度,默认为1
        properties.setProperty("kaptcha.border.thickness","1");
        //验证码
        properties.setProperty("kaptcha.session.key""code");
        //验证码文本字符颜色 默认为黑色
        properties.setProperty("kaptcha.textproducer.font.color""blue");
        //设置字体样式
        properties.setProperty("kaptcha.textproducer.font.names""宋体,楷体,微软雅黑");
        //字体大小,默认40
        properties.setProperty("kaptcha.textproducer.font.size""30");
        //验证码文本字符内容范围 默认为abced2345678gfynmnpwx
        properties.setProperty("kaptcha.textproducer.char.string","");
        //字符长度,默认为5
        properties.setProperty("kaptcha.textproducer.char.length""4");
        //字符间距 默认为2
        properties.setProperty("kaptcha.textproducer.char.space""4");
        //验证码图片宽度 默认为200
        properties.setProperty("kaptcha.image.width""100");
        //验证码图片高度 默认为40
        properties.setProperty("kaptcha.image.height""40");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}
控制层
@RestController
public class CaptchaCotroller {
    @Autowired
    private DefaultKaptcha defaultKaptcha;
    @ApiOperation(value = "验证码")
    @GetMapping(value = "/captcha")
    public void captcha(HttpServletRequest request, HttpServletResponse response) {
        // 定义response输出类型为image/jpeg类型
        response.setDateHeader("Expires", 0);
        // Set standard HTTP/1.1 no-cache headers.
        response.setHeader("Cache-Control""no-store, no-cache, must-revalidate");
        // Set IE extended HTTP/1.1 no-cache headers (use addHeader).
        response.addHeader("Cache-Control""post-check=0, pre-check=0");
        // Set standard HTTP/1.0 no-cache header.
        response.setHeader("Pragma""no-cache");
        // return a jpeg
        response.setContentType("image/jpeg");

        //-------------------生成验证码 begin --------------------------
        //获取验证码文本内容
        String text = defaultKaptcha.createText();
        System.out.println("验证码内容:" + text);
        //将验证码放入session中
        request.getSession().setAttribute("captcha", text);
        //根据文本内容创建图形验证码
        BufferedImage image = defaultKaptcha.createImage(text);
        ServletOutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            //输出流输出图片,格式jpg
            ImageIO.write(image, "jpg", outputStream);
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != outputStream) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        //-------------------生成验证码 end ----------------------------
    }
}
万能的返回类型的类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class RespBean {
    private Integer code;
    private String message;
    private Object obj;



    /**
     * 返回成功对象
     *
     * @param message
     * @return
     */
    public static RespBean success(String message) {
        return new RespBean(200, message, null);
    }


    /**
     * 返回成功对象
     *
     * @param message
     * @param obj
     * @return
     */
    public static RespBean success(String message, Object obj) {
        return new RespBean(200, message, obj);
    }


    /**
     * 返回失败对象
     *
     * @param message
     * @return
     */
    public static RespBean error(String message) {
        return new RespBean(500, message, null);
    }


    /**
     * 返回失败对象
     *
     * @param message
     * @param obj
     * @return
     */
    public static RespBean error(String message, Object obj) {
        return new RespBean(500, message, obj);
    }


}


springSecurity实现权限管理和登入功能

应用程序安全性差不多可以归结为两个独立的问题:
身份认证(你是谁)和授权(authorization)
(你可以做什么?)。 有时人们会说“访问控制”而
不是“授权”,这可能会造成困惑,但是以这种方式思
考可能会有所帮助,因为“授权”在其他地方又有其他
含义。 Spring Security的体系结构旨在将身份认证与授权分开,并且具有许
多策略和扩展点

身份认证
认证的主要策略接口是AuthenticationManager,该接口只有一个方法
在AuthenticationManager接口的authenticate()方法中可以有3种处理情况:

如果认证成功,则返回一个Authentication对象(通常将其authenticated属性设置为true)。
如果认证失败,则抛出AuthenticationException异常。
果无法判断成功或失败,则返回null。
AuthenticationException是运行时异常。它通常由应用
程序以通用方式处理,具体取决于应用程序的风格或目的。 换
句话说,通常不希望用户代码捕获并处理它。 例如,一个Web
UI将呈现一个页面,该页面说明身份验证失败,而后端HTTP
服务将发送401响应,是否携带WWW-Authenticate标头则取决于上下文。

AuthenticationManager最常用的实现是ProviderManager,
它委派了AuthenticationProvider实例链。AuthenticationProvider
有点像AuthenticationManager,但是它还有一个额外的方法
,允许调用者查询是否支持给定的Authentication类型:
定制Authentication Managers
Spring Security提供了一些配置助手,可以快速获取在应用程序中设置的通用Authentication Managers功能。 最常用的帮助程序是AuthenticationManagerBuilder,它非常适合设置内存中、JDBC或LDAP用户详细信息,或添加自定义UserDetailsService
博客地址:https://zhuanlan.zhihu.com/p/100014456
配置springSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //登入授权过滤器
    @Autowired
    private IAdminService iAdminService;
    @Autowired
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
    @Autowired
    private RestAuthorizetionEntryPoint restAuthorizetionEntryPoint;
    @Autowired
    private CustomUrlDecisionManager customUrlDecisionManager;
    @Autowired
    private CustomFilter customFilter;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
    }
    //配置过滤的资源
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(
                "/*",
                "/login",
                "/logout",
                "/css/**",
                "/js/**",
                "/index.html",
                "/favicon.ico",
                "/doc.html",
                "/webjars/**",
                "/swagger-resources/**",
                "/v2/api-docs/**",
                "/captcha",
                "/export",
                "/ws/**",
                "/email/**",
                "/topic/**"
        );
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //使用jwt 不需要cscf
        http.csrf().disable()
                //基于token 不需要 session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().
                authorizeRequests().
//所有请求需要认证
                anyRequest()
                .authenticated().
                //动态权限
                withObjectPostProcessor(new ObjectPostProcessor() {
                    @Override
                    public  O postProcess(O o) {
                        o.setAccessDecisionManager(customUrlDecisionManager);
                        o.setSecurityMetadataSource(customFilter);
                        return o;
                    }
                }).
                and().
                //不使用缓存
                headers().cacheControl();
        //添加jwt  登入授权过滤器
        http.addFilterBefore(jwtAuthencationTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        //添加自定义没有登入和没有授权的过滤器
        http.exceptionHandling().accessDeniedHandler(restfulAccessDeniedHandler).
                authenticationEntryPoint(restAuthorizetionEntryPoint);
    }
//每一次登入的时候从这个地方进入 进行登入验证
    @Override
    @Bean
    public UserDetailsService userDetailsService() {
        return username -> {
            Admin admin = iAdminService.getAdmonByUserName(username);
            if (null == admin) {
                throw new UsernameNotFoundException("用户名或密码错误!");
            }
            admin.setRoles(iAdminService.getRoles(admin.getId()));
            return admin;
        };
    }
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Bean
    public JwtAuthencationTokenFilter jwtAuthencationTokenFilter(){
        return new JwtAuthencationTokenFilter();
    }
}
配置jwt的过滤器
这个地方每一次请求的时候 就进行jwt验证  
public class JwtAuthencationTokenFilter extends OncePerRequestFilter {

    @Value("${jwt.tokenHeader}")
    private String tokenHeader;
    @Value("${jwt.tokenHead}")
    private String tokenHead;
    @Autowired
    private JwtTokerUtil jwtTokerUtil;
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取请求头
        String authHeader = request.getHeader(tokenHeader);
        System.out.println("请求头"+authHeader);
        //存在token
        if ((!StringUtils.isEmpty(authHeader)) && (authHeader.startsWith(tokenHead))) {
           //截取jwt进行判断
            String token = authHeader.substring(tokenHead.length());
            System.out.println("令牌"+token);
            //获取用户名
            String username = jwtTokerUtil.getUserNameFormToken(token);

            //存在token但是未登录
            if (null != username && null == SecurityContextHolder.getContext().getAuthentication()) {
                //登录
      &n
  1. List item
  2. List item

bsp;         UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                //判断token是否有效
                if (jwtTokerUtil.validateToken(token, userDetails)) {
                    //更新Security用户对象
                    UsernamePasswordAuthenticationToken authenticationToken =
                            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                }
            }
        }
              //进行放行

              filterChain.doFilter(request,response);

    }
}
登入的控制层
@Api(tags = “LoginController”)
@RestController
public class LoginController {
    @Autowired
    private IAdminService iAdminService;
    @Autowired
    private AdminMapper adminMapper;
    @ApiOperation(value = “登入以返回token”)
    @PostMapping("/login")
    public RespBean  login(@RequestBody AdminLoginParam adminLoginParam, HttpServletRequest httpServletRequest){
        System.out.println(adminLoginParam.toString());
        return iAdminService.login(adminLoginParam.getUsername(),adminLoginParam.getPassword(),adminLoginParam.getCode(),httpServletRequest);
    }
    @ApiOperation(value = “退出登入”)
    @PostMapping("/logout")
    public RespBean logout(){
        return RespBean.success(“注销成功”);
    }
    @ApiOperation(value = “获取当前登入用户信息”)
    @GetMapping("/amin/info")
    public Admin getAdminInfo(Principal principal){
        if (principalnull){
            return null;
        }
        String username=principal.getName();
        Admin admin=iAdminService.getAdmonByUserName(username);
        admin.setPassword(null);
        admin.setRoles(iAdminService.getRoles(admin.getId()));
        return admin;
    }

}
业务层
登入的思路:
   1前端传递数据 
   没有jwt的时候 进行自动过去
   进行数据库验证 
   @Bean
    public UserDetailsService userDetailsService(){}这个方法进行验证的
    2业务层调用login方法进行验证
    
@Service
public class AdminServiceImpl extends ServiceImpl implements IAdminService {
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private AdminMapper adminMapper;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private JwtTokerUtil jwtTokerUtil;
    @Value("${jwt.tokenHead}")
    private String tokenHead;
    private static final Integer DEFAULT_ROLE_ID = 9;
    @Autowired
    private RoleMapper roleMapper;

    @Override
    public RespBean login(String username, String password, String captcha, HttpServletRequest httpServletRequest) {
        // 判断前台是否传递验证码
        if (StringUtils.isBlank(captcha)) {
            // 没有验证码
            return RespBean.error(“请输入验证码!”);
        }

        // 获取验证码
        String trueCaptcha = (String)httpServletRequest.getSession().getAttribute(“captcha”);
        // 校验验证码
        if (!captcha.equals(trueCaptcha)) {
            // 验证码不正确
            return RespBean.error(“验证码错误!”);
        }

        UserDetails userDetails=userDetailsService.loadUserByUsername(username);
//        if (userDetails
null || passwordEncoder.matches(password,userDetails.getPassword())){
//            return RespBean.error(“用户密码不正确”);
//        }
//        if (!userDetails.isEnabled()){
//            return RespBean.error(“账号被禁言”);
//        }
        //更新security用户
        //更新security上下文的用户对象
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        String s = jwtTokerUtil.generateToken(userDetails);
        Map map = new HashMap();
        map.put(“token”,s);
        map.put(“tokeHead”,tokenHead);

        return RespBean.success(“登入成功”,map);
    }

    /
    获取用户
     
/
    @Override
    public Admin getAdmonByUserName(String username) {
        return adminMapper.selectOne(new QueryWrapper().eq(“username”, username).eq(“enabled”,1));
    }

    @Override
    public List getRoles(Integer adminId) {
        return roleMapper.getRoles(adminId);
    }


}
dao
根据用户id获取用户权限
ublic interface RoleMapper extends BaseMapper {

    List getRoles(Integer adminId);
}
  “getRoles” resultType=“com.example.server.pojo.Role”>
         SELECT
r.id,
r.name,
r.nameZh
from
t_role as r
LEFT JOIN  t_admin_role AS ar on r.id=ar.rid
WHERE ar.adminId=#{adminId};
  

获取用户的菜单和权限保存在redis里面
@Service
public class MenuServiceImpl extends ServiceImpl implements IMenuService {
    @Autowired
    private MenuMapper menuMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Override
    public List

 getMenusByAdminId() {
        //获取当前登入的用户
        Integer adminid = ((Admin) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getId();
        ValueOperations stringObjectValueOperations = redisTemplate.opsForValue();
        List menus =(List) stringObjectValueOperations.get(“menu_” + adminid);
        if (CollectionUtils.isEmpty(menus)){
            //判断是null  进入数据库进行读取
          menus = menuMapper.getMenusByAdminId(adminid);
          //将数据传递到redis
            stringObjectValueOperations.set(“menu_”+adminid,menus);
        }
        return menus;
    }

    @Override
    public List getMenusWitRole() {
        return  menuMapper.getMenusWitRole();
    }
}
dao
public interface MenuMapper extends BaseMapper {

    List getMenusByAdminId(Integer id);
    List getMenusWitRole();
}
mapper
“Menus” type=“com.example.server.pojo.Menu”>
    “id” property=“id” />
    “url” property=“url” />
    “path” property=“path” />
    “component” property=“component” />
    “name” property=“name” />
    “iconCls” property=“iconCls” />
    “keepAlive” property=“keepAlive” />
    “requireAuth” property=“requireAuth” />
    “parentId” property=“parentId” />
    “enabled” property=“enabled” />
    “children” ofType=“com.example.server.pojo.Menu”>
        “id2” property=“id” />
        “url2” property=“url” />
        “path2” property=“path” />
        “component2” property=“component” />
        “name2” property=“name” />
        “iconCls2” property=“iconCls” />
        “keepAlive2” property=“keepAlive” />
        “requireAuth2” property=“requireAuth” />
        “parentId2” property=“parentId” />
        “enabled2” property=“enabled” />
    


    “MenusWithRole” type=“com.example.server.pojo.Menu” extends=“BaseResultMap”>
        “randoms” ofType=“com.example.server.pojo.Role”>
            “rid”  property=“id”>
            “rname” property=“name”>
            “rnameZh” property=“nameZh”>
        

    
    
    “Base_Column_List”>
        id, url, path, component, name, iconCls, keepAlive, requireAuth, parentId, enabled
    
    “getMenusByAdminId”  resultMap=“Menus”>
SELECT
DISTINCT

m1.,m2.id as id2,
m2.url as url2,
m2.path as path2,
m2.component as component2,
m2.name as name2,
m2.requireAuth as requireAuth2,


m2.parentId as parentId2,
m2.enabled as enabled2

FROM
t_menu m1,
t_menu m2,
t_admin_role ar,
t_menu_role mr
WHERE
m1.id=m2.parentId
and
m2.id=mr.mid
AND
mr.rid=ar.rid
and ar.adminId=1
AND m2.enabled=TRUE
    
    “getMenusWitRole” resultMap=“MenusWithRole”>
        SELECT
m.
,
r.id as rid,
r.name as rname,
r.nameZh as rnameZh


FROM
t_menu m,
t_menu_role mr,
t_role r
WHERE
m.id=mr.mid
and
r.id=mr.rid
ORDER BY m.id

    
登入方法的实体类
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain =true)
@ApiModel(value = “AdminLogin对象”,description = “”)
/
@EqualsAndHashCode(callSuper = false)
1. 此注解会生成equals(Object other) 和 hashCode()方法。

2. 它默认使用非静态,非瞬态的属性

3. 可通过参数exclude排除一些属性

4. 可通过参数of指定仅使用哪些属性

5. 它默认仅使用该类中定义的属性且不调用父类的方法

6. 可通过callSuper=true解决上一点问题。让其生成的方法中调用父类的方法。
Accessors翻译是存取器。通过该注解可以控制getter和setter方法的形式。

特别注意如果不是常规的get|set,如使用此类配置(chain = true或者chain = true)。
在用一些扩展工具会有问题,比如 BeanUtils.populate 将map转换为bean的时候无法使用。具体问题可以查看转换源码分析
 
/
public class AdminLoginParam {
    /
    用户登入实体类
     
/
    @ApiModelProperty(value = “用户名”, required = true)
    private String username;
    @ApiModelProperty(value = “密码”, required = true)
    private String password;
    @ApiModelProperty(value = “验证码”, required = true)
    private String code;

}
权限和角色的验证 
先根据url获取用户的权限
@Slf4j
@Component
public class CustomFilter implements FilterInvocationSecurityMetadataSource {
    /
    1.Collection getAttributes(Object object) throws IllegalArgumentException;
获取某个受保护的安全对象object的所需要的权限信息,是一组ConfigAttribute对象的集合,
如果该安全对象object不被当前SecurityMetadataSource对象支持,则抛出异常IllegalArgumentException。
该方法通常配合boolean supports(Class clazz)一起使用,先使用boolean
supports(Class clazz)确保安全对象能被当前SecurityMetadataSource支持,然后再调用该方法。

2.Collection getAllConfigAttributes()
获取该SecurityMetadataSource对象中保存的针对所有安全对象的权限信息的集合。
该方法的主要目的是被AbstractSecurityInterceptor用于启动时校验每个ConfigAttribute对象。

3.boolean supports(Class clazz)
这里clazz表示安全对象的类型,该方法用于告知调用者当前SecurityMetadataSo
urce是否支持此类安全对象,只有支持的时候,才能对这类安全对象调用getAttributes方法。
     
/
    @Autowired
    private IMenuService iMenuService;
    AntPathMatcher antPathMatcher=new AntPathMatcher();
    @Override
    public Collection getAttributes(Object o) throws IllegalArgumentException {

        String requestUrl = ((FilterInvocation) o).getRequestUrl();
        //获取权限
        List menusWitRole = iMenuService.getMenusWitRole();
        log.info(“数据库读取的权限”+menusWitRole.toString());
        for (Menu menu:menusWitRole) {
            //判断请求的url 是不是跟菜单的角色想匹配
            if (antPathMatcher.match(menu.getUrl(),requestUrl)){
                String [] strings=menu.getRandoms().stream().map(Role::getName).toArray(String[]::new);
                log.info(“获取的权限”+strings.toString());
                return SecurityConfig.createList(strings);
            }

        }
        //没有匹配上就url默认登入就可以了

        return SecurityConfig.createList(“ROLE_LOGIN”);
    }

    @Override
    public Collection getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class aClass) {
        return false;
    }
    /
    根据请求的url分析 需要的角色
    权限控制
     
/

}
再判断用户的角色
@Slf4j
@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
    /
   权限控制
   判断角色
    
/
    @Override
    public void decide(Authentication authentication, Object o, Collection collection) throws AccessDeniedException, InsufficientAuthenticationException {
        log.info(“AccessDecisionManager”+“权限控制”);

        for (ConfigAttribute configAttribute:collection) {

            //当前url需要的角色
            String attribute = configAttribute.getAttribute();
            log.info(“当前url需要的角色”+attribute);
            //判断角色 是不是登入 可访问的角色
            if (“ROLE_LOGIN”.equals(attribute)){
        //判断是不是登入
                if (authentication instanceof AnonymousAuthenticationToken){
                throw new AccessDeniedException(“没有登入,请登入”);
                }else {
                    return;
                }
            }
            //判断用户是不是url 需要的用户
            Collection authorities = authentication.getAuthorities();
            for (GrantedAuthority grantedAuthority: authorities) {
                if (grantedAuthority.getAuthority().equals(attribute)){
                    return;
                }

            }
        }
        throw new AccessDeniedException(“权限不够”);
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return false;
    }

    @Override
    public boolean supports(Class aClass) {
        return false;
    }

}
再根据用户名字查询用户拥有的菜单(控制层)
@RestController
@RequestMapping("/system/cfg")
public class MenuController {
    @Autowired
    private IMenuService iMenuService;
     @ApiOperation(value =“通过用户id查询菜单” )
    @GetMapping("/menu")
    public List getMenusByAdminId(){
         System.out.println(“111111111+接口”);
         return iMenuService.getMenusByAdminId();
     }
}

日志

验证码内容:afmg
请求头BearereyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImNyZWF0ZWQiOjE2MjU0NTY0MjI5ODgsImV4cCI6MTYyNjA2MTIyMn0.Sh2UNNVg5t7TtHtbYtrLowqeEprZdVpEy4roXnFic4V2z4OgYdvsQDsneEL9Jtu2PE6TMu0RGIdcEv1W3TA5fg
令牌eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImNyZWF0ZWQiOjE2MjU0NTY0MjI5ODgsImV4cCI6MTYyNjA2MTIyMn0.Sh2UNNVg5t7TtHtbYtrLowqeEprZdVpEy4roXnFic4V2z4OgYdvsQDsneEL9Jtu2PE6TMu0RGIdcEv1W3TA5fg
2021-07-05 15:15:43.695 DEBUG 14392 --- [io-8081-exec-10] c.e.server.mapper.AdminMapper.selectOne  : ==>  Preparing: SELECT id,name,phone,telephone,address,enabled,username,password,userFace,remark FROM t_admin WHERE (username = ? AND enabled = ?) 
2021-07-05 15:15:43.695 DEBUG 14392 --- [io-8081-exec-10] c.e.server.mapper.AdminMapper.selectOne  : ==> Parameters: admin(String), 1(Integer)
2021-07-05 15:15:43.696 DEBUG 14392 --- [io-8081-exec-10] c.e.server.mapper.AdminMapper.selectOne  : <==      Total: 1
2021-07-05 15:15:43.696 DEBUG 14392 --- [io-8081-exec-10] c.e.server.mapper.RoleMapper.getRoles    : ==>  Preparing: SELECT r.id, r.`name`, r.nameZh from t_role as r LEFT JOIN t_admin_role AS ar on r.id=ar.rid WHERE ar.adminId=?; 
2021-07-05 15:15:43.697 DEBUG 14392 --- [io-8081-exec-10] c.e.server.mapper.RoleMapper.getRoles    : ==> Parameters: 1(Integer)
2021-07-05 15:15:43.697 DEBUG 14392 --- [io-8081-exec-10] c.e.server.mapper.RoleMapper.getRoles    : <==      Total: 1
这个是角色类获取权限[ROLE_admin]
2021-07-05 15:15:43.699 DEBUG 14392 --- [io-8081-exec-10] c.e.s.mapper.MenuMapper.getMenusWitRole  : ==>  Preparing: SELECT m.*, r.id as rid, r.`name` as rname, r.nameZh as rnameZh FROM t_menu m, t_menu_role mr, t_role r WHERE m.id=mr.mid and r.id=mr.rid ORDER BY m.id 
2021-07-05 15:15:43.700 DEBUG 14392 --- [io-8081-exec-10] c.e.s.mapper.MenuMapper.getMenusWitRole  : ==> Parameters: 
2021-07-05 15:15:43.705 DEBUG 14392 --- [io-8081-exec-10] c.e.s.mapper.MenuMapper.getMenusWitRole  : <==      Total: 53
2021-07-05 15:15:43.706  INFO 14392 --- [io-8081-exec-10] c.e.server.config.security.CustomFilter  : 数据库读取的权限[Menu(id=7, url=/employee/basic/**, path=/emp/basic, component=EmpBasic, name=基本资料, iconCls=null, keepAlive=null, requireAuth=true, parentId=2, enabled=true, children=null, randoms=[Role(id=2, name=ROLE_personnel, nameZh=人事专员), Role(id=6, name=ROLE_admin, nameZh=系统管理员), Role(id=4, name=ROLE_train, nameZh=培训主管), Role(id=1, name=ROLE_manager, nameZh=部门经理), Role(id=3, name=ROLE_recruiter, nameZh=招聘主管)]), Menu(id=8, url=/employee/advanced/**, path=/emp/adv, component=EmpAdv, name=高级资料, iconCls=null, keepAlive=null, requireAuth=true, parentId=2, enabled=true, children=null, randoms=[Role(id=2, name=ROLE_personnel, nameZh=人事专员), Role(id=1, name=ROLE_manager, nameZh=部门经理), Role(id=4, name=ROLE_train, nameZh=培训主管)]), Menu(id=9, url=/personnel/emp/**, path=/per/emp, component=PerEmp, name=员工资料, iconCls=null, keepAlive=null, requireAuth=true, parentId=3, enabled=true, children=null, randoms=[Role(id=1, name=ROLE_manager, nameZh=部门经理), Role(id=6, name=ROLE_admin, nameZh=系统管理员), Role(id=2, name=ROLE_personnel, nameZh=人事专员)]), Menu(id=10, url=/personnel/ec/**, path=/per/ec, component=PerEc, name=员工奖惩, iconCls=null, keepAlive=null, requireAuth=true, parentId=3, enabled=true, children=null, randoms=[Role(id=2, name=ROLE_personnel, nameZh=人事专员), Role(id=1, name=ROLE_manager, nameZh=部门经理), Role(id=6, name=ROLE_admin, nameZh=系统管理员)]), Menu(id=11, url=/personnel/train/**, path=/per/train, component=PerTrain, name=员工培训, iconCls=null, keepAlive=null, requireAuth=true, parentId=3, enabled=true, children=null, randoms=[Role(id=6, name=ROLE_admin, nameZh=系统管理员), Role(id=4, name=ROLE_train, nameZh=培训主管), Role(id=1, name=ROLE_manager, nameZh=部门经理)]), Menu(id=12, url=/personnel/salary/**, path=/per/salary, component=PerSalary, name=员工调薪, iconCls=null, keepAlive=null, requireAuth=true, parentId=3, enabled=true, children=null, randoms=[Role(id=1, name=ROLE_manager, nameZh=部门经理), Role(id=2, name=ROLE_personnel, nameZh=人事专员), Role(id=6, name=ROLE_admin, nameZh=系统管理员)]), Menu(id=13, url=/personnel/remove/**, path=/per/mv, component=PerMv, name=员工调动, iconCls=null, keepAlive=null, requireAuth=true, parentId=3, enabled=true, children=null, randoms=[Role(id=6, name=ROLE_admin, nameZh=系统管理员), Role(id=1, name=ROLE_manager, nameZh=部门经理), Role(id=2, name=ROLE_personnel, nameZh=人事专员)]), Menu(id=14, url=/salary/sob/**, path=/sal/sob, component=SalSob, name=工资账套管理, iconCls=null, keepAlive=null, requireAuth=true, parentId=4, enabled=true, children=null, randoms=[Role(id=6, name=ROLE_admin, nameZh=系统管理员), Role(id=1, name=ROLE_manager, nameZh=部门经理)]), Menu(id=15, url=/salary/sobcfg/**, path=/sal/sobcfg, component=SalSobCfg, name=员工账套设置, iconCls=null, keepAlive=null, requireAuth=true, parentId=4, enabled=true, children=null, randoms=[Role(id=1, name=ROLE_manager, nameZh=部门经理), Role(id=6, name=ROLE_admin, nameZh=系统管理员)]), Menu(id=16, url=/salary/table/**, path=/sal/table, component=SalTable, name=工资表管理, iconCls=null, keepAlive=null, requireAuth=true, parentId=4, enabled=true, children=null, randoms=[Role(id=6, name=ROLE_admin, nameZh=系统管理员), Role(id=1, name=ROLE_manager, nameZh=部门经理)]), Menu(id=17, url=/salary/month/**, path=/sal/month, component=SalMonth, name=月末处理, iconCls=null, keepAlive=null, requireAuth=true, parentId=4, enabled=true, children=null, randoms=[Role(id=1, name=ROLE_manager, nameZh=部门经理), Role(id=6, name=ROLE_admin, nameZh=系统管理员)]), Menu(id=18, url=/salary/search/**, path=/sal/search, component=SalSearch, name=工资表查询, iconCls=null, keepAlive=null, requireAuth=true, parentId=4, enabled=true, children=null, randoms=[Role(id=1, name=ROLE_manager, nameZh=部门经理), Role(id=6, name=ROLE_admin, nameZh=系统管理员)]), Menu(id=19, url=/statistics/all/**, path=/sta/all, component=StaAll, name=综合信息统计, iconCls=null, keepAlive=null, requireAuth=true, parentId=5, enabled=true, children=null, randoms=[Role(id=6, name=ROLE_admin, nameZh=系统管理员), Role(id=1, name=ROLE_manager, nameZh=部门经理)]), Menu(id=20, url=/statistics/score/**, path=/sta/score, component=StaScore, name=员工积分统计, iconCls=null, keepAlive=null, requireAuth=true, parentId=5, enabled=true, children=null, randoms=[Role(id=1, name=ROLE_manager, nameZh=部门经理), Role(id=6, name=ROLE_admin, nameZh=系统管理员)]), Menu(id=21, url=/statistics/personnel/**, path=/sta/pers, component=StaPers, name=人事信息统计, iconCls=null, keepAlive=null, requireAuth=true, parentId=5, enabled=true, children=null, randoms=[Role(id=6, name=ROLE_admin, nameZh=系统管理员), Role(id=1, name=ROLE_manager, nameZh=部门经理)]), Menu(id=22, url=/statistics/recored/**, path=/sta/record, component=StaRecord, name=人事记录统计, iconCls=null, keepAlive=null, requireAuth=true, parentId=5, enabled=true, children=null, randoms=[Role(id=6, name=ROLE_admin, nameZh=系统管理员), Role(id=1, name=ROLE_manager, nameZh=部门经理)]), Menu(id=23, url=/system/basic/**, path=/sys/basic, component=SysBasic, name=基础信息设置, iconCls=null, keepAlive=null, requireAuth=true, parentId=6, enabled=true, children=null, randoms=[Role(id=1, name=ROLE_manager, nameZh=部门经理), Role(id=6, name=ROLE_admin, nameZh=系统管理员)]), Menu(id=24, url=/system/cfg/**, path=/sys/cfg, component=SysCfg, name=系统管理, iconCls=null, keepAlive=null, requireAuth=true, parentId=6, enabled=true, children=null, randoms=[Role(id=1, name=ROLE_manager, nameZh=部门经理), Role(id=6, name=ROLE_admin, nameZh=系统管理员)]), Menu(id=25, url=/system/log/**, path=/sys/log, component=SysLog, name=操作日志管理, iconCls=null, keepAlive=null, requireAuth=true, parentId=6, enabled=true, children=null, randoms=[Role(id=6, name=ROLE_admin, nameZh=系统管理员), Role(id=1, name=ROLE_manager, nameZh=部门经理)]), Menu(id=26, url=/system/admin/**, path=/sys/admin, component=SysAdmin, name=操作员管理, iconCls=null, keepAlive=null, requireAuth=true, parentId=6, enabled=true, children=null, randoms=[Role(id=1, name=ROLE_manager, nameZh=部门经理), Role(id=6, name=ROLE_admin, nameZh=系统管理员)]), Menu(id=27, url=/system/data/**, path=/sys/data, component=SysData, name=备份恢复数据库, iconCls=null, keepAlive=null, requireAuth=true, parentId=6, enabled=true, children=null, randoms=[Role(id=1, name=ROLE_manager, nameZh=部门经理), Role(id=6, name=ROLE_admin, nameZh=系统管理员)]), Menu(id=28, url=/system/init/**, path=/sys/init, component=SysInit, name=初始化数据库, iconCls=null, keepAlive=null, requireAuth=true, parentId=6, enabled=true, children=null, randoms=[Role(id=1, name=ROLE_manager, nameZh=部门经理), Role(id=6, name=ROLE_admin, nameZh=系统管理员)])]
2021-07-05 15:15:43.706  INFO 14392 --- [io-8081-exec-10] c.e.server.config.security.CustomFilter  : 获取的权限[Ljava.lang.String;@7673a328
2021-07-05 15:15:43.706  INFO 14392 --- [io-8081-exec-10] c.e.s.config.CustomUrlDecisionManager    : AccessDecisionManager权限控制
2021-07-05 15:15:43.706  INFO 14392 --- [io-8081-exec-10] c.e.s.config.CustomUrlDecisionManager    : 当前url需要的角色ROLE_manager
2021-07-05 15:15:43.706  INFO 14392 --- [io-8081-exec-10] c.e.s.config.CustomUrlDecisionManager    : 当前url需要的角色ROLE_admin
111111111+接口
请求头BearereyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImNyZWF0ZWQiOjE2MjU0NTY0MjI5ODgsImV4cCI6MTYyNjA2MTIyMn0.Sh2UNNVg5t7TtHtbYtrLowqeEprZdVpEy4roXnFic4V2z4OgYdvsQDsneEL9Jtu2PE6TMu0RGIdcEv1W3TA5fg
令牌eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImNyZWF0ZWQiOjE2MjU0NTY0MjI5ODgsImV4cCI6MTYyNjA2MTIyMn0.Sh2UNNVg5t7TtHtbYtrLowqeEprZdVpEy4roXnFic4V2z4OgYdvsQDsneEL9Jtu2PE6TMu0RGIdcEv1W3TA5fg
2021-07-05 15:15:59.243 DEBUG 14392 --- [nio-8081-exec-1] c.e.server.mapper.AdminMapper.selectOne  : ==>  Preparing: SELECT id,name,phone,telephone,address,enabled,username,password,userFace,remark FROM t_admin WHERE (username = ? AND enabled = ?) 
2021-07-05 15:15:59.244 DEBUG 14392 --- [nio-8081-exec-1] c.e.server.mapper.AdminMapper.selectOne  : ==> Parameters: admin(String), 1(Integer)
2021-07-05 15:15:59.247 DEBUG 14392 --- [nio-8081-exec-1] c.e.server.mapper.AdminMapper.selectOne  : <==      Total: 1
2021-07-05 15:15:59.249 DEBUG 14392 --- [nio-8081-exec-1] c.e.server.mapper.RoleMapper.getRoles    : ==>  Preparing: SELECT r.id, r.`name`, r.nameZh from t_role as r LEFT JOIN t_admin_role AS ar on r.id=ar.rid WHERE ar.adminId=?; 
2021-07-05 15:15:59.249 DEBUG 14392 --- [nio-8081-exec-1] c.e.server.mapper.RoleMapper.getRoles    : ==> Parameters: 1(Integer)
2021-07-05 15:15:59.342 DEBUG 14392 --- [nio-8081-exec-1] c.e.server.mapper.RoleMapper.getRoles    : <==      Total: 1
这个是角色类获取权限[ROLE_admin]
AdminLoginParam(username=admin, password=123, code=afmg)
2021-07-05 15:15:59.370 DEBUG 14392 --- [nio-8081-exec-1] c.e.server.mapper.AdminMapper.selectOne  : ==>  Preparing: SELECT id,name,phone,telephone,address,enabled,username,password,userFace,remark FROM t_admin WHERE (username = ? AND enabled = ?) 
2021-07-05 15:15:59.371 DEBUG 14392 --- [nio-8081-exec-1] c.e.server.mapper.AdminMapper.selectOne  : ==> Parameters: admin(String), 1(Integer)
2021-07-05 15:15:59.371 DEBUG 14392 --- [nio-8081-exec-1] c.e.server.mapper.AdminMapper.selectOne  : <==      Total: 1
2021-07-05 15:15:59.372 DEBUG 14392 --- [nio-8081-exec-1] c.e.server.mapper.RoleMapper.getRoles    : ==>  Preparing: SELECT r.id, r.`name`, r.nameZh from t_role as r LEFT JOIN t_admin_role AS ar on r.id=ar.rid WHERE ar.adminId=?; 
2021-07-05 15:15:59.372 DEBUG 14392 --- [nio-8081-exec-1] c.e.server.mapper.RoleMapper.getRoles    : ==> Parameters: 1(Integer)
2021-07-05 15:15:59.372 DEBUG 14392 --- [nio-8081-exec-1] c.e.server.mapper.RoleMapper.getRoles    : <==      Total: 1
这个是角色类获取权限[ROLE_admin]

执行以后就是这样 2021-07-06_第3张图片

前端的最后的部分

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它
采用集中式存储管理应用的所有组件的状态,并以相应的规则
保证状态以一种可预测的方式发生变化。
Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若
store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
安装vuex
npm install  Vuex
配置vuex获取用户的菜单index.js
import Vue from "vue"
import Vuex from 'vuex'
import da from "element-ui/src/locale/lang/da";
Vue.use(Vuex)
export default new Vuex.Store({
//数据保存在这里,页面通过 this.$store.state来获取 可取出
    state:{
    //定义数组
        routes:[]
    },
    //同步
    mutations:{
        initRoutes(state,data){
       state.routes=data;
        }
    },
    actions:{

    }
})
main.js
import Vue from 'vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue'
import router from './router'
import store from "./store";
import axios from "axios";

import {initMenu} from "./utils/menus";

Vue.config.productionTip = false
Vue.use(ElementUI)

// Vue.prototype.postRequest = postRequest;
// Vue.prototype.putRequest = putRequest;
// Vue.prototype.getRequest = getRequest;
// Vue.prototype.deleteRequest = deleteRequest;
//导航守卫  进行判断jwt进行切换路由
router.beforeEach(((to, from, next) => {
每一次进行
  if (window.sessionStorage.getItem("tokenStr")){
  //进行加载菜单 menus.js里面的方法
    initMenu(router,store);
    next();
  }else {
    next();
  }

  console.log(to);
  console.log(from);
  next();
}))
//创建和挂载根实例。通过router配置参数注入路由
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

难点
导航守卫就是路由跳转过程中的一些钩子函数,再直白点路由跳转是一个大的过程,这个大的过程分为跳转前中后等等细小的过程,在每一个过程中都有一函数,这个函数能让你操作一些其他的事儿的时机,这就是导航守卫
全局路由钩子:beforeEach(to,from, next)、beforeResolve(to,from, next)、afterEach(to,from);

独享路由钩子:beforeEnter(to,from, next);

组件内路由钩子:beforeRouteEnter(to,from, next)、beforeRouteUpdate(to,from, next)、beforeRouteLeave(to,from, next)

当点击切换路由时:
beforeRouterLeave-->beforeEach-->
beforeEnter-->beforeRouteEnter-->
beforeResolve-->afterEach-->beforeCreate-->
created-->beforeMount-->mounted-->beforeRouteEnter
的next的回调

menus.js  封装菜单类 将后端传递的参数进行封装
import {getRequst} from "./api";
import da from "element-ui/src/locale/lang/da";
import router from "../router";
import el from "element-ui/src/locale/lang/el";
export const initMenu=(router,store)=>{
    if (store.state.routes.length>0){
        return;
    }
    getRequst("/system/cfg/menu").then(data=>{
        console.log("nn"+data)
        if (data){
            //格式化
            let  fmtRoutes=forMatRouter(data);
            //添加到路由
            router.addRoutes(fmtRoutes);
            //将数据保存在vuex中
            store.commit('initRoutes',fmtRoutes);
        }
    })
}
export const forMatRouter=(router)=>{
//获取后端传递的参数
    let fmtRotes=[];
    router.forEach(router=>{
        let{
            path,
            component,
            name,
            iconCls,
            children,
        }=router;
        if (children &&  children instanceof  Array){
            //递归
            children=forMatRouter(children)
        }
        let fmRouter={
            path:path,
            name:name,
            iconCls:iconCls,
            children:children,
            component(resolve){
              if (component.startsWith('Emp')){
                  require(['../views/emp/'+component+'.vue'],resolve);
              }else if (component.startsWith('Per')){
                  require(['../views/per/'+component+'.vue'],resolve);
              }else if (component.startsWith('Sal')){
                  require(['../views/sal/'+component+'.vue'],resolve);
              }else if(component.startsWith('Sys')){
                  require(['../views/sys/'+component+'.vue'],resolve);
              }else if (component.startsWith("Sta")){
                  require(['../views/sta/'+component+'.vue'],resolve);
              }
            }
        }
        fmtRotes.push(fmRouter)
    });
    return fmtRotes;
}
路由  router.js
import Vue from 'vue'
import Router from 'vue-router'
import Login from './views/Login.vue'
import Home from "./views/Home.vue";
import Test1 from "./views/Test1";
import Test2 from "./views/Test2";
Vue.use(Router)
//创建router实例,然后传'routes'配置
export default new Router({
//定义路由
  routes: [
    {
      path: '/',
      name: '登入',
      component: Login,
      hidden:true
    },{
      path: '/home',
      name: '主页面',
      component: Home,
      children:[
      //下面的选择卡
        {
          path: '/test1',
          name: 'Tset1',
          component: Test1
        },
        {
          path: '/test2',
          name: 'Tset2',
          component: Test2
        }
      ]
    },

  ]
})
App.vue



主页面
Home.vue

                      "children.path"  v-for="(children,indexj) in item.children">
                          {{children.name}}
                      
                  
              
          

      
         
      

      
        
    








要不想要他一直加载去掉unique-opened就可以了
可能有人没有看懂流程(不懂可以问我)
什么是路由?
路由中有三个基本的概念 route, routes, router。
1, route,它是一条路由,由这个英文单词也可以看出来,它是单数, Home按钮 => home内容, 这是一条route, about按钮 => about 内容, 这是另一条路由。

2, routes 是一组路由,把上面的每一条路由组合起来,形成一个数组。[{home 按钮 =>home内容 }, { about按钮 => about 内容}]

3, router 是一个机制,相当于一个管理者,它来管理路由。因为routes 只是定义了一组路由,它放在哪里是静止的,当真正来了请求,怎么办? 就是当用户点击home 按钮的时候,怎么办?这时router 就起作用了,它到routes 中去查找,去找到对应的 home 内容,所以页面中就显示了 home 内容。

4,客户端中的路由,实际上就是dom 元素的显示和隐藏。当页面中显示home 内容的时候,about 中的内容全部隐藏,反之也是一样。客户端路由有两种实现方式:基于hash 和基于html5 history api.

菜单的数据 2021-07-06_第4张图片 流程图

这样前后端分离的springboot加vue的权限和登入验证就结束了,下一节继续说怎么做表格,使用vue,继续分析这个项目

有什么不懂地方可以联系我,适合新手去理解项目,去明白项目的流程

你可能感兴趣的:(java,vue.js)